From 07d663d667c084645ff04a791366a9519e2e3d41 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Mon, 3 Jun 2019 22:43:34 -0400 Subject: [PATCH 01/89] Add event and transaction decoding (initial commit, don't use) --- .../lib/data/actions/index.js | 4 +- .../truffle-debugger/lib/data/reducers.js | 14 +- .../truffle-debugger/lib/data/sagas/index.js | 20 +- .../lib/data/selectors/index.js | 4 +- .../truffle-decode-utils/src/constants.ts | 2 +- packages/truffle-decode-utils/src/contexts.ts | 63 ++- .../truffle-decode-utils/src/definition.ts | 34 ++ packages/truffle-decode-utils/src/values.ts | 24 ++ .../truffle-decoder-core/lib/allocate/abi.ts | 380 ++++++++++++++++++ .../lib/allocate/calldata.ts | 197 --------- .../lib/decode/{calldata.ts => abi.ts} | 112 +++--- .../truffle-decoder-core/lib/decode/event.ts | 30 ++ .../truffle-decoder-core/lib/decode/index.ts | 59 ++- .../truffle-decoder-core/lib/decode/memory.ts | 34 +- .../lib/decode/special.ts | 22 +- .../truffle-decoder-core/lib/decode/stack.ts | 6 +- .../lib/decode/storage.ts | 51 +-- .../lib/interface/decoding.ts | 119 ++++++ .../lib/interface/index.ts | 10 +- .../lib/read/{memory.ts => bytes.ts} | 0 .../truffle-decoder-core/lib/read/index.ts | 62 ++- .../lib/types/allocation.ts | 94 ++++- .../truffle-decoder-core/lib/types/evm.ts | 20 +- .../truffle-decoder-core/lib/types/pointer.ts | 86 ++-- .../lib/{decoder.ts => contract.ts} | 221 +++++----- packages/truffle-decoder/lib/index.ts | 7 +- packages/truffle-decoder/lib/types.ts | 25 +- packages/truffle-decoder/lib/utils.ts | 12 + packages/truffle-decoder/lib/wire.ts | 264 ++++++++++++ 29 files changed, 1409 insertions(+), 567 deletions(-) create mode 100644 packages/truffle-decoder-core/lib/allocate/abi.ts delete mode 100644 packages/truffle-decoder-core/lib/allocate/calldata.ts rename packages/truffle-decoder-core/lib/decode/{calldata.ts => abi.ts} (60%) create mode 100644 packages/truffle-decoder-core/lib/decode/event.ts create mode 100644 packages/truffle-decoder-core/lib/interface/decoding.ts rename packages/truffle-decoder-core/lib/read/{memory.ts => bytes.ts} (100%) rename packages/truffle-decoder/lib/{decoder.ts => contract.ts} (72%) create mode 100644 packages/truffle-decoder/lib/wire.ts diff --git a/packages/truffle-debugger/lib/data/actions/index.js b/packages/truffle-debugger/lib/data/actions/index.js index 1cf21f07596..bcec130a0bb 100644 --- a/packages/truffle-debugger/lib/data/actions/index.js +++ b/packages/truffle-debugger/lib/data/actions/index.js @@ -57,11 +57,11 @@ export function defineType(node) { } export const ALLOCATE = "ALLOCATE"; -export function allocate(storage, memory, calldata) { +export function allocate(storage, memory, abi) { return { type: ALLOCATE, storage, memory, - calldata + abi }; } diff --git a/packages/truffle-debugger/lib/data/reducers.js b/packages/truffle-debugger/lib/data/reducers.js index 800dcd5ee3b..6ddcb4aa5c3 100644 --- a/packages/truffle-debugger/lib/data/reducers.js +++ b/packages/truffle-debugger/lib/data/reducers.js @@ -80,7 +80,7 @@ function userDefinedTypes(state = [], action) { const DEFAULT_ALLOCATIONS = { storage: {}, memory: {}, - calldata: {} + abi: {} }; function allocations(state = DEFAULT_ALLOCATIONS, action) { @@ -88,7 +88,7 @@ function allocations(state = DEFAULT_ALLOCATIONS, action) { return { storage: action.storage, memory: action.memory, - calldata: action.calldata + abi: action.abi }; } else { return state; @@ -102,11 +102,11 @@ const info = combineReducers({ }); const GLOBAL_ASSIGNMENTS = [ - [{ builtin: "msg" }, { special: "msg" }], - [{ builtin: "tx" }, { special: "tx" }], - [{ builtin: "block" }, { special: "block" }], - [{ builtin: "this" }, { special: "this" }], - [{ builtin: "now" }, { special: "timestamp" }] //we don't have an alias "now" + [{ builtin: "msg" }, { location: "special", special: "msg" }], + [{ builtin: "tx" }, { location: "special", special: "tx" }], + [{ builtin: "block" }, { location: "special", special: "block" }], + [{ builtin: "this" }, { location: "special", special: "this" }], + [{ builtin: "now" }, { location: "special", special: "timestamp" }] //we don't have an alias "now" ].map(([idObj, ref]) => makeAssignment(idObj, ref)); const DEFAULT_ASSIGNMENTS = { diff --git a/packages/truffle-debugger/lib/data/sagas/index.js b/packages/truffle-debugger/lib/data/sagas/index.js index 329c493f149..17c65703f56 100644 --- a/packages/truffle-debugger/lib/data/sagas/index.js +++ b/packages/truffle-debugger/lib/data/sagas/index.js @@ -67,9 +67,7 @@ export function* decode(definition, ref) { userDefinedTypes, state, mappingKeys, - storageAllocations: allocations.storage, - memoryAllocations: allocations.memory, - calldataAllocations: allocations.calldata, + allocations, contexts, currentContext, internalFunctionsTable @@ -288,10 +286,9 @@ function* variablesAndMappingsSaga() { assignment = makeAssignment( { astId: varId, stackframe: currentDepth }, { - stack: { - from: top - DecodeUtils.Definition.stackSize(node) + 1, - to: top - } + location: "stack", + from: top - DecodeUtils.Definition.stackSize(node) + 1, + to: top } ); assignments = { [assignment.id]: assignment }; @@ -550,7 +547,7 @@ function* variablesAndMappingsSaga() { let memberAllocation = allocations[structId].members[node.referencedDeclaration]; - slot.offset = memberAllocation.pointer.storage.from.slot.offset.clone(); + slot.offset = memberAllocation.pointer.range.from.slot.offset.clone(); debug("slot %o", slot); yield put( @@ -639,10 +636,9 @@ function assignParameters(parameters, top, functionDepth) { for (let parameter of reverseParameters) { let words = DecodeUtils.Definition.stackSize(parameter); let pointer = { - stack: { - from: currentPosition - words + 1, - to: currentPosition - } + location: "stack", + from: currentPosition - words + 1, + to: currentPosition }; let assignment = makeAssignment( { astId: parameter.id, stackframe: functionDepth }, diff --git a/packages/truffle-debugger/lib/data/selectors/index.js b/packages/truffle-debugger/lib/data/selectors/index.js index 19d39d847f7..622ca233587 100644 --- a/packages/truffle-debugger/lib/data/selectors/index.js +++ b/packages/truffle-debugger/lib/data/selectors/index.js @@ -316,9 +316,9 @@ const data = createSelectorTree({ memory: createLeaf(["/state"], state => state.info.allocations.memory), /* - * data.info.allocations.calldata + * data.info.allocations.abi */ - calldata: createLeaf(["/state"], state => state.info.allocations.calldata) + abi: createLeaf(["/state"], state => state.info.allocations.abi) }, /** diff --git a/packages/truffle-decode-utils/src/constants.ts b/packages/truffle-decode-utils/src/constants.ts index 9b03ccfd527..c920272064a 100644 --- a/packages/truffle-decode-utils/src/constants.ts +++ b/packages/truffle-decode-utils/src/constants.ts @@ -3,7 +3,7 @@ import BN from "bn.js"; export namespace Constants { export const WORD_SIZE = 0x20; export const ADDRESS_SIZE = 20; - export const SELECTOR_SIZE = 4; + export const SELECTOR_SIZE = 4; //function selectors, not event selectors export const PC_SIZE = 4; export const MAX_WORD = new BN(-1).toTwos(WORD_SIZE * 8); export const ZERO_ADDRESS = "0x" + "00".repeat(ADDRESS_SIZE); diff --git a/packages/truffle-decode-utils/src/contexts.ts b/packages/truffle-decode-utils/src/contexts.ts index 8a4f16087e3..635fae09717 100644 --- a/packages/truffle-decode-utils/src/contexts.ts +++ b/packages/truffle-decode-utils/src/contexts.ts @@ -16,9 +16,7 @@ export namespace Contexts { export type Context = DecoderContext | DebuggerContext; export interface DecoderContexts { - [index: string]: DecoderContext; - //we'll allow this to be a hash *or* a contract ID; it's just an arbitrary - //identifier + [context: string]: DecoderContext; } export interface DebuggerContexts { @@ -261,4 +259,63 @@ export namespace Contexts { //finally, return this mess! return newContexts; } + + export function matchesAbi(abiEntry: abiItem, node: AstDefinition, referenceDeclarations: AstReferences): boolean { + //first: does the basic type match? + switch(node.nodeType) { + case "FunctionDefinition": + if(node.visibility !== "external" && node.visibility !== "public") { + return false; + } + if(abiEntry.type !== node.kind) { + return false; + } + break; + case "EventDefinition": + if(abiEntry.type !== "event") { + return false; + } + break; + default: + return false; + } + //if we've made it this far, the next question is, does the name match? + if(node.name !== abiEntry.name) { + return false; + } + //finally, we've got to start checking input types (we don't check output types) + return matchesAbiParameters(abiEntry.inputs, node.parameters.parameters, referenceDeclarations); + } + + function matchesAbiParameters(abiParameters: AbiParameter[], nodeParameters: AstDefinition[], referenceDeclarations: AstReferences): boolean { + if(abiParameters.length !== nodeParameters.length) { + return false; + } + for(let i = 0; i < abiParameters.length; i++) { + if(!matchesAbiType(abiParameters[i], nodeParameters[i], referenceDeclarations) { + return false; + } + } + return true; + } + + //TODO: add error-handling + function matchesAbiType(abiParameter: AbiParameter, nodeParameter: AstDefinition, referenceDeclarations: AstReferences): boolean { + if(DefinitionUtils.toAbiType(nodeParameter, referenceDeclarations) !== abiParameter.type) { + return false; + } + if(abiParameter.type.beginsWith("tuple")) { + let referenceDeclaration = referenceDeclarations[DefinitionUtils.typeId(nodeParameter)]; + return matchesAbiParameters(abiParameter.components, referenceDeclaration.members, referenceDeclarations); + } + else { + return true; + } + } + + function isNoArgumentConstructor(abiEntry: AbiItem) { + return abiEntry.type === "constructor" && abiEntry.inputs.length === 0; + } + + } diff --git a/packages/truffle-decode-utils/src/definition.ts b/packages/truffle-decode-utils/src/definition.ts index ca13565c3b3..85169aa9eb7 100644 --- a/packages/truffle-decode-utils/src/definition.ts +++ b/packages/truffle-decode-utils/src/definition.ts @@ -26,6 +26,10 @@ export namespace Definition { return typeIdentifier(definition).match(/t_([^$_0-9]+)/)[1]; } + export function typeClassLongForm(definition: AstDefinition): string { + return typeIdentifier(definition).match(/t_([^$_]+)/)[1]; + } + //for user-defined types -- structs, enums, contracts //often you can get these from referencedDeclaration, but not //always @@ -316,6 +320,36 @@ export namespace Definition { return fallback.stateMutability === "payable"; } + //note: this is only meant for types that can go in the ABI + //TODO add error handling + export function toAbiType(defintion: AstDefinition, referenceDeclarations: AstReferences): string { + let basicType = typeClassLongForm(definition); //get that whole first segment! + switch(basicType) { + case "contract": + return "address"; + case "struct": + return "tuple"; //the more detailed checking will be handled elsewhere + case "enum": + let id = typeId(definition); + let referenceDeclaration = referenceDeclarations[id]; + let numOptions = referenceDeclaration.members.length; + let bits = 8 * Math.ceil(Math.log2(numOptions) / 8); + return `uint${bits}`; + case "array": + let baseType = toAbiType(baseDefinition(definition), referenceDeclarations); + return isDynamicArray(definition) + ? `${baseType}[]` + : `${baseType}[${staticLength(definition)}]`; + default: + return basicType; + //note that: int/uint/fixed/ufixed/bytes will have their size and such left on; + //address will have "payable" left off; + //external functions will be reduced to "function" (and internal functions shouldn't + //be passed in!) + //(mappings shouldn't be passed in either obviously) + } + } + //spoofed definitions we'll need //we'll give them id -1 to indicate that they're spoofed diff --git a/packages/truffle-decode-utils/src/values.ts b/packages/truffle-decode-utils/src/values.ts index 4071ba50d1a..421591aee8b 100644 --- a/packages/truffle-decode-utils/src/values.ts +++ b/packages/truffle-decode-utils/src/values.ts @@ -955,6 +955,19 @@ export namespace Values { } } + export class IndexedReferenceTypeError extends GenericErrorDirect { + type: Types.ReferenceType; + raw: BN; + message() { + return `Cannot decode indexed parameter of reference type ${this.type.typeClass} (raw value ${this.raw.toString()})`; + } + constructor(referenceType: Types.ReferenceType, raw: BN) { + super(); + this.type = referenceType; + this.raw = raw; + } + } + //Read errors export class UnsupportedConstantError extends GenericErrorDirect { definition: AstDefinition; @@ -979,4 +992,15 @@ export namespace Values { this.to = to; } } + + export class ReadErrorTopic extends GenericErrorDirect { + topic: number; + message() { + return `Can't read event topic ${this.topic}`; + } + constructor(topic: number) { + super(); + this.topic = topic; + } + } } diff --git a/packages/truffle-decoder-core/lib/allocate/abi.ts b/packages/truffle-decoder-core/lib/allocate/abi.ts new file mode 100644 index 00000000000..09e3abeef81 --- /dev/null +++ b/packages/truffle-decoder-core/lib/allocate/abi.ts @@ -0,0 +1,380 @@ +import debugModule from "debug"; +const debug = debugModule("decoder-core:allocate:abi"); + +import * as Pointer from "../types/pointer"; +import * as Allocations from "../types/allocation"; +import { AstDefinition, AstReferences } from "truffle-decode-utils"; +import * as DecodeUtils from "truffle-decode-utils"; +import partition from "lodash.partition"; + +export function getAbiAllocations(referenceDeclarations: AstReferences): Allocations.AbiAllocations { + let allocations: Allocations.AbiAllocations = {}; + for(const node of Object.values(referenceDeclarations)) { + if(node.nodeType === "StructDefinition") { + allocations = allocateStruct(node, referenceDeclarations, allocations); + } + } + return allocations; +} + +function allocateStruct(structDefinition: AstDefinition, referenceDeclarations: AstReferences, existingAllocations: Allocations.AbiAllocations): Allocations.AbiAllocations { + return allocateMembers(structDefinition, structDefinition.members, referenceDeclarations, existingAllocations); +} + +//note: we will still allocate circular structs, even though they're not allowed in the abi, because it's +//not worth the effort to detect them. However on mappings or internal functions, we'll vomit (allocate null) +function allocateMembers(parentNode: AstDefinition, definitions: AstDefinition[], referenceDeclarations: AstReferences, existingAllocations: Allocations.AbiAllocations, start: number = 0): Allocations.AbiAllocations { + let dynamic: boolean = false; + //note that we will mutate the start argument also! + + //don't allocate things that have already been allocated + if(parentNode.id in existingAllocations) { + return existingAllocations; + } + + let allocations = {...existingAllocations}; //otherwise, we'll be adding to this, so we better clone + + let memberAllocations: Allocations.AbiMemberAllocations = {} + + for(const member of definitions) + { + let length: number; + let dynamicMember: boolean; + [length, dynamicMember, allocations] = abiSizeAndAllocate(member, referenceDeclarations, allocations); + + //vomit on illegal types in calldata -- note the short-circuit! + if(length === undefined) { + allocations[definition.id] = null; + return allocations; + } + + let pointer: Pointer.AbiPointer = { + location: "abi", + start, + length, + }; + + memberAllocations[member.id] = { + definition: member, + pointer + } + + start += length; + dynamic = dynamic || dynamicMember; + } + + allocations[definition.id] = { + definition, + members: memberAllocations, + length: dynamic ? DecodeUtils.EVM.WORD_SIZE : start, + dynamic + }; + + return allocations; +} + +//NOTE: This wrapper function is for use by the decoder ONLY, after allocation is done. +//The allocator should (and does) instead use a direct call to storageSizeAndAllocate, +//not to the wrapper, because it may need the allocations returned. +//the first return value is the length (in bytes); the second is whether the type is dynamic +export function abiSize(definition: AstDefinition, referenceDeclarations?: AstReferences, allocations?: Allocations.AbiAllocations): [number | undefined, boolean | undefined] { + let [size, dynamic] = abiSizeAndAllocate(definition, referenceDeclarations, allocations); //throw away allocations + return [size, dynamic]; +} + +//first return value is the actual size. +//second return value is whether the type is dynamic +//both will be undefined if type is a mapping or internal function +//third return value is resulting allocations, INCLUDING the ones passed in +//TODO: add error handling +function abiSizeAndAllocate(definition: AstDefinition, referenceDeclarations?: AstReferences, existingAllocations?: Allocations.AbiAllocations): [number | undefined, boolean | undefined, Allocations.AbiAllocations] { + switch (DecodeUtils.Definition.typeClass(definition)) { + case "bool": + case "address": + case "contract": + case "int": + case "uint": + case "fixed": + case "ufixed": + case "enum": + return [DecodeUtils.EVM.WORD_SIZE, false, existingAllocations]; + + case "string": + return [DecodeUtils.EVM.WORD_SIZE, true, existingAllocations]; + + case "bytes": + return [DecodeUtils.EVM.WORD_SIZE, DecodeUtils.Definition.specifiedSize(definition) == null, + existingAllocations]; + + case "mapping": + return [undefined, undefined, existingAllocations]; + + case "function": + switch (DecodeUtils.Definition.visibility(definition)) { + case "external": + return [DecodeUtils.EVM.WORD_SIZE, false, existingAllocations]; + case "internal": + return [undefined, undefined, existingAllocations]; + } + + case "array": { + if(DecodeUtils.Definition.isDynamicArray(definition)) { + return [DecodeUtils.EVM.WORD_SIZE, true, existingAllocations]; + } + else { + //static array case + const length: number = DecodeUtils.Definition.staticLength(definition); + const baseDefinition: AstDefinition = definition.baseType || definition.typeName.baseType; + const [baseSize, dynamic, allocations] = abiSizeAndAllocate(baseDefinition, referenceDeclarations, existingAllocations); + return [length * baseSize, dynamic, allocations]; + } + } + + case "struct": { + const referenceId: number = DecodeUtils.Definition.typeId(definition); + let allocations: Allocations.AbiAllocations = existingAllocations; + let allocation: Allocations.AbiAllocation | null | undefined = allocations[referenceId]; + if(allocation === undefined) { + //if we don't find an allocation, we'll have to do the allocation ourselves + const referenceDeclaration: AstDefinition = referenceDeclarations[referenceId]; + allocations = allocateStruct(referenceDeclaration, referenceDeclarations, existingAllocations); + allocation = allocations[referenceId]; + } + //having found our allocation, if it's not null, we can just look up its size and dynamicity + if(allocation !== null) { + return [allocation.length, allocation.dynamic, allocations]; + } + //if it is null, this type doesn't go in the abi + else { + return [undefined, undefined, allocations]; + } + } + } +} + +//like abiSize, but for a Type object; also assumes you've already done allocation +//(note: function for dynamic is separate, see below) +//also, does not attempt to handle types that don't occur in calldata +export function abiSizeForType(dataType: DecodeUtils.Types.Type, allocations: Allocations.AbiAllocations): number { + switch(dataType.typeClass) { + case "array": + switch(dataType.kind) { + case "dynamic": + return DecodeUtils.EVM.WORD_SIZE; + case "static": + const length = dataType.length.toNumber(); //if this is too big, we have a problem! + const baseSize = abiSizeForType(dataType.baseType, allocations); + return length * baseSize; + } + case "struct": + const allocation = allocations[dataType.id]; + if(!allocation) { + throw new DecodeUtils.Values.DecodingError( + new DecodeUtils.Values.UserDefinedTypeNotFoundError(dataType) + ); + } + return allocation.length; + default: + return DecodeUtils.EVM.WORD_SIZE; + } +} + +//again, this function does not attempt to handle types that don't occur in the abi +export function isTypeDynamic(dataType: DecodeUtils.Types.Type, allocations: Allocations.AbiAllocations): boolean { + switch(dataType.typeClass) { + case "string": + return true; + case "bytes": + return dataType.kind === "dynamic"; + case "array": + return dataType.kind === "dynamic" || isTypeDynamic(dataType.baseType, allocations); + case "struct": + const allocation = allocations[dataType.id]; + if(!allocation) { + throw new DecodeUtils.Values.DecodingError( + new DecodeUtils.Values.UserDefinedTypeNotFoundError(dataType) + ); + } + return allocation.dynamic; + default: + return false; + } +} + +//allocates an external call +//NOTE: returns just a single allocation; assumes primary allocation is already complete! +//TODO add error-handling +//TODO: check accesses to abi & node members +function allocateCalldata( + abiEntry: AbiItem, + linearizedBaseContracts: number[], + referenceDeclarations: AstReferences, + abiAllocations: AbiAllocations, + constructorContext?: DecodeUtils.Contexts.DecoderContext +): Allocations.CalldataAllocation { + //first: determine the corresponding function node + let node: AstDefinition; + //base contracts are listed from most derived to most base, so we + //have to reverse before processing, but reverse() is in place, so we + //clone with slice first + let linearizedBaseContractsFromBase: number[] = contract.linearizedBaseContracts.slice().reverse(); + for(const contractId of linearizedBaseContractsFromBase) { + node = referenceDeclarations[contractId].nodes.find( + functionNode => DecodeUtils.Contexts.matchesAbi( + abiEntry, functionNode, referenceDeclarations + ) + ); + if(node !== undefined) { + break; + } + } + if(node === undefined) { + //before we declare this an error-case... maybe it's just an implicit constructor? + //and we can return an empty allocation? + if(isNoArgumentConstructor(abiEntry)) { + let rawLength = constructorContext.binary.length; + offset = (rawLength - 2)/2; //number of bytes in 0x-prefixed bytestring + return { + offset, + arguments: {} + }; + } + else { + //TODO: error-handling + } + } + //now: determine the offset + let offset: number; + switch(abiEntry.type) { + case "function": + offset = DecodeUtils.EVM.SELECTOR_SIZE; + break; + case "constructor": + let rawLength = constructorContext.binary.length; + offset = (rawLength - 2)/2; //number of bytes in 0x-prefixed bytestring + break; + //we'll ignore event and fallback, which are not applicable here + } + //now: perform the allocation! + const abiAllocation = allocateMembers(node, node.parameters.parameters, referenceDeclarations, abiAllocations, offset)[node.id]; + //finally: transform it appropriately + return { + definition: abiAllocation.definition, + offset, + arguments: Object.assign({}, ...Object.entries(abiAllocation.members).map( + ([id, { definition, { start, length }}]) => ({ + [id]: { + definition, + pointer: { + location: "calldata", + start, + length + } + } + }) + )) + }; +} + +//allocates an event +//NOTE: returns just a single allocation; assumes primary allocation is already complete! +//TODO add error-handling +//TODO: check accesses to abi & node members +function allocateEvent( + abiEntry: AbiItem, + referenceDeclarations: AstReferences, + linearizedBaseContracts: number[], + abiAllocations: AbiAllocations +): Allocations.EventAllocation { + //first: determine the corresponding function node + let node: AstDefinition; + //base contracts are listed from most derived to most base, so we + //have to reverse before processing, but reverse() is in place, so we + //clone with slice first + const linearizedBaseContractsFromBase: number[] = contract.linearizedBaseContracts.slice().reverse(); + for(const contractId of linearizedBaseContractsFromBase) { + node = referenceDeclarations[contractId].nodes.find( + functionNode => DecodeUtils.Contexts.matchesAbi( + abiEntry, functionNode, referenceDeclarations + ) + ); + if(node !== undefined) { + break; + } + } + //now: split the list of parameters into indexed and non-indexed + //but first attach positions so we can reconstruct the list later + const rawParameters = node.parameters.parameters; + const [indexed, nonIndexed] = rawParameters.partition(parameter => parameter.indexed); + //now: perform the allocation for the non-indexed parameters! + const abiAllocation = allocateMembers(node, nonIndexed, referenceDeclarations, abiAllocations)[node.id]; + //now: transform it appropriately + let eventAllocation = { + definition: abiAllocation.definition, + arguments: Object.assign({}, ...Object.entries(abiAllocation.members).map( + ([id, { definition, { start, length }}]) => ({ + [id]: { + definition, + position: rawParameters.findIndex( + parameter => parameter.id === definition.id + ), + pointer: { + location: "eventdata", + start, + length + } + } + }) + )) + }; + //finally: add in the indexed parameters and return + Object.assign(eventAllocation.arguments, + ...indexed.map( (node, position) => //yes, that's the two-argument map style! + ({[node.id]: { + definition: node, + position: rawParameters.findIndex( + parameter => parameter.id === definition.id + ), + pointer: { + location: "eventtopic", + topic: position + 1 //signature takes up topic 0, so we skip that + } + }}) + ) + ); + return eventAllocation; +} + +//NOTE: this is for a single contract! +//run multiple times to handle multiple contracts +export function getCalldataAllocations( + abi: Abi, + referenceDeclarations: AstReferences, + linearizedBaseContracts: number[], + abiAllocations: Allocations.AbiAllocations, + constructorContext: DecoderContext +): Allocations.CalldataContractAllocations { + return Object.assign({}, ...abi + .filter(abiEntry => abiEntry.type === "function" || abiEntry.type === "constructor") + .map(abiEntry => + abiEntry.type === "constructor" + ? { constructorAllocation: allocateCalldata(abiEntry, referenceDeclarations, linearizedBaseContracts, abiAllocations, constructorContext) } + : { [abiCoder.encodeFunctionSignature(abiEntry)] : + allocateCalldata(abiEntry, referenceDeclarations, linearizedBaseContracts, abiAllocations) + }; + ) + ); +} + +//NOTE: this is for a single contract! +//run multiple times to handle multiple contracts +export function getEventAllocations(abi: Abi, referenceDeclarations: AstReferences, linearizedBaseContracts: number[], abiAllocations: Allocations.AbiAllocations): Allocations.EventContractAllocations { + return Object.assign({}, ...abi + .filter(abiEntry => abiEntry.type === "event") + .map(abiEntry => + ({ [abiCoder.encodeEventSignature(abiEntry)] : + allocateEvent(abiEntry, referenceDeclarations, linearizedBaseContracts, abiAllocations) + }) + ) + ); +} diff --git a/packages/truffle-decoder-core/lib/allocate/calldata.ts b/packages/truffle-decoder-core/lib/allocate/calldata.ts deleted file mode 100644 index 7ddd3b43dc7..00000000000 --- a/packages/truffle-decoder-core/lib/allocate/calldata.ts +++ /dev/null @@ -1,197 +0,0 @@ -import debugModule from "debug"; -const debug = debugModule("decoder-core:allocate:calldata"); - -import { CalldataPointer } from "../types/pointer"; -import { CalldataAllocations, CalldataAllocation, CalldataMemberAllocations } from "../types/allocation"; -import { AstDefinition, AstReferences } from "truffle-decode-utils"; -import * as DecodeUtils from "truffle-decode-utils"; - -export function getCalldataAllocations(referenceDeclarations: AstReferences): CalldataAllocations { - let allocations: CalldataAllocations = {}; - for(const node of Object.values(referenceDeclarations)) { - if(node.nodeType === "StructDefinition") { - allocations = allocateStruct(node, referenceDeclarations, allocations); - } - } - return allocations; -} - -//note: we will still allocate circular structs, even though they're not allowed in calldata, because it's -//not worth the effort to detect them. However on mappings or internal functions, we'll vomit (allocate null) -function allocateStruct(definition: AstDefinition, referenceDeclarations: AstReferences, existingAllocations: CalldataAllocations): CalldataAllocations { - let start: number = 0; - let dynamic: boolean = false; - - //don't allocate things that have already been allocated - if(definition.id in existingAllocations) { - return existingAllocations; - } - - let allocations = {...existingAllocations}; //otherwise, we'll be adding to this, so we better clone - - let memberAllocations: CalldataMemberAllocations = {} - - for(const member of definition.members) - { - let length: number; - let dynamicMember: boolean; - [length, dynamicMember, allocations] = calldataSizeAndAllocate(member, referenceDeclarations, allocations); - - //vomit on illegal types in calldata -- note the short-circuit! - if(length === undefined) { - allocations[definition.id] = null; - return allocations; - } - - let pointer: CalldataPointer = { - calldata: { - start, - length - } - }; - - memberAllocations[member.id] = { - definition: member, - pointer - } - - start += length; - dynamic = dynamic || dynamicMember; - } - - allocations[definition.id] = { - definition, - members: memberAllocations, - length: dynamic ? DecodeUtils.EVM.WORD_SIZE : start, - dynamic - }; - - return allocations; -} - -//NOTE: This wrapper function is for use by the decoder ONLY, after allocation is done. -//The allocator should (and does) instead use a direct call to storageSizeAndAllocate, -//not to the wrapper, because it may need the allocations returned. -//the first return value is the length (in bytes); the second is whether the type is dynamic -export function calldataSize(definition: AstDefinition, referenceDeclarations?: AstReferences, allocations?: CalldataAllocations): [number | undefined, boolean | undefined] { - let [size, dynamic] = calldataSizeAndAllocate(definition, referenceDeclarations, allocations); //throw away allocations - return [size, dynamic]; -} - -//first return value is the actual size. -//second return value is whether the type is dynamic -//both will be undefined if type is a mapping or internal function -//third return value is resulting allocations, INCLUDING the ones passed in -function calldataSizeAndAllocate(definition: AstDefinition, referenceDeclarations?: AstReferences, existingAllocations?: CalldataAllocations): [number | undefined, boolean | undefined, CalldataAllocations] { - switch (DecodeUtils.Definition.typeClass(definition)) { - case "bool": - case "address": - case "contract": - case "int": - case "uint": - case "fixed": - case "ufixed": - case "enum": - return [DecodeUtils.EVM.WORD_SIZE, false, existingAllocations]; - - case "string": - return [DecodeUtils.EVM.WORD_SIZE, true, existingAllocations]; - - case "bytes": - return [DecodeUtils.EVM.WORD_SIZE, DecodeUtils.Definition.specifiedSize(definition) == null, - existingAllocations]; - - case "mapping": - return [undefined, undefined, existingAllocations]; - - case "function": - switch (DecodeUtils.Definition.visibility(definition)) { - case "external": - return [DecodeUtils.EVM.WORD_SIZE, false, existingAllocations]; - case "internal": - return [undefined, undefined, existingAllocations]; - } - - case "array": { - if(DecodeUtils.Definition.isDynamicArray(definition)) { - return [DecodeUtils.EVM.WORD_SIZE, true, existingAllocations]; - } - else { - //static array case - const length: number = DecodeUtils.Definition.staticLength(definition); - const baseDefinition: AstDefinition = definition.baseType || definition.typeName.baseType; - const [baseSize, dynamic, allocations] = calldataSizeAndAllocate(baseDefinition, referenceDeclarations, existingAllocations); - return [length * baseSize, dynamic, allocations]; - } - } - - case "struct": { - const referenceId: number = DecodeUtils.Definition.typeId(definition); - let allocations: CalldataAllocations = existingAllocations; - let allocation: CalldataAllocation | null | undefined = allocations[referenceId]; - if(allocation === undefined) { - //if we don't find an allocation, we'll have to do the allocation ourselves - const referenceDeclaration: AstDefinition = referenceDeclarations[referenceId]; - allocations = allocateStruct(referenceDeclaration, referenceDeclarations, existingAllocations); - allocation = allocations[referenceId]; - } - //having found our allocation, if it's not null, we can just look up its size and dynamicity - if(allocation !== null) { - return [allocation.length, allocation.dynamic, allocations]; - } - //if it is null, this type doesn't go in calldata - else { - return [undefined, undefined, allocations]; - } - } - } -} - -//like calldataSize, but for a Type object; also assumes you've already done allocation -//(note: function for dynamic is separate, see below) -//also, does not attempt to handle types that don't occur in calldata -export function calldataSizeForType(dataType: DecodeUtils.Types.Type, allocations: CalldataAllocations): number { - switch(dataType.typeClass) { - case "array": - switch(dataType.kind) { - case "dynamic": - return DecodeUtils.EVM.WORD_SIZE; - case "static": - const length = dataType.length.toNumber(); //if this is too big, we have a problem! - const baseSize = calldataSizeForType(dataType.baseType, allocations); - return length * baseSize; - } - case "struct": - const allocation = allocations[dataType.id]; - if(!allocation) { - throw new DecodeUtils.Values.DecodingError( - new DecodeUtils.Values.UserDefinedTypeNotFoundError(dataType) - ); - } - return allocation.length; - default: - return DecodeUtils.EVM.WORD_SIZE; - } -} - -//again, this function does not attempt to handle types that don't occur in calldata -export function isTypeDynamic(dataType: DecodeUtils.Types.Type, allocations: CalldataAllocations): boolean { - switch(dataType.typeClass) { - case "string": - return true; - case "bytes": - return dataType.kind === "dynamic"; - case "array": - return dataType.kind === "dynamic" || isTypeDynamic(dataType.baseType, allocations); - case "struct": - const allocation = allocations[dataType.id]; - if(!allocation) { - throw new DecodeUtils.Values.DecodingError( - new DecodeUtils.Values.UserDefinedTypeNotFoundError(dataType) - ); - } - return allocation.dynamic; - default: - return false; - } -} diff --git a/packages/truffle-decoder-core/lib/decode/calldata.ts b/packages/truffle-decoder-core/lib/decode/abi.ts similarity index 60% rename from packages/truffle-decoder-core/lib/decode/calldata.ts rename to packages/truffle-decoder-core/lib/decode/abi.ts index 65df6203413..45b74c335c3 100644 --- a/packages/truffle-decoder-core/lib/decode/calldata.ts +++ b/packages/truffle-decoder-core/lib/decode/abi.ts @@ -1,30 +1,30 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:decode:calldata"); +const debug = debugModule("decoder-core:decode:abi"); import read from "../read"; import * as DecodeUtils from "truffle-decode-utils"; import { Types, Values } from "truffle-decode-utils"; import decodeValue from "./value"; -import { CalldataPointer, DataPointer } from "../types/pointer"; -import { CalldataMemberAllocation } from "../types/allocation"; -import { calldataSizeForType, isTypeDynamic } from "../allocate/calldata"; +import { AbiPointer, DataPointer } from "../types/pointer"; +import { AbiMemberAllocation } from "../types/allocation"; +import { abiSizeForType, isTypeDynamic } from "../allocate/abi"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; -export default function* decodeCalldata(dataType: Types.Type, pointer: CalldataPointer, info: EvmInfo, base: number = 0): IterableIterator { +export default function* decodeAbi(dataType: Types.Type, pointer: AbiPointer, info: EvmInfo, base: number = 0): IterableIterator { if(Types.isReferenceType(dataType)) { let dynamic: boolean; try { - dynamic = isTypeDynamic(dataType, info.calldataAllocations); + dynamic = isTypeDynamic(dataType, info.allocations.abi); } catch(error) { //error: Values.DecodingError return new Values.GenericError(error.error); } if(dynamic) { - return yield* decodeCalldataReferenceByAddress(dataType, pointer, info, base); + return yield* decodeAbiReferenceByAddress(dataType, pointer, info, base); } else { - return yield* decodeCalldataReferenceStatic(dataType, pointer, info); + return yield* decodeAbiReferenceStatic(dataType, pointer, info); } } else { @@ -33,9 +33,14 @@ export default function* decodeCalldata(dataType: Types.Type, pointer: CalldataP } } -export function* decodeCalldataReferenceByAddress(dataType: Types.ReferenceType, pointer: DataPointer, info: EvmInfo, base: number = 0): IterableIterator { - const { state } = info; +export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, pointer: DataPointer, info: EvmInfo, base: number = 0): IterableIterator { + const { allocations: { abi: allocations }, state } = info; debug("pointer %o", pointer); + //this variable holds the location we should look to *next* + const location = pointer.location === "eventdata" + ? "eventdata" + : "calldata"; //stack pointers point to calldata, not the stack + let rawValue: Uint8Array; try { rawValue = yield* read(pointer, state); @@ -49,7 +54,7 @@ export function* decodeCalldataReferenceByAddress(dataType: Types.ReferenceType, let dynamic: boolean; try { - dynamic = isTypeDynamic(dataType, info.calldataAllocations); + dynamic = isTypeDynamic(dataType, allocations); } catch(error) { //error: Values.DecodingError return new Values.GenericError(error.error); @@ -57,18 +62,17 @@ export function* decodeCalldataReferenceByAddress(dataType: Types.ReferenceType, if(!dynamic) { //this will only come up when called from stack.ts let size: number; try { - size = calldataSizeForType(dataType, info.calldataAllocations); + size = abiSizeForType(dataType, allocations); } catch(error) { //error: Values.DecodingError return new Values.GenericError(error.error); } let staticPointer = { - calldata: { - start: startPosition, - length: size - } + location, + start: startPosition, + length: size } - return yield* decodeCalldataReferenceStatic(dataType, staticPointer, info); + return yield* decodeAbiReferenceStatic(dataType, staticPointer, info); } let length: number; let rawLength: Uint8Array; @@ -79,10 +83,9 @@ export function* decodeCalldataReferenceByAddress(dataType: Types.ReferenceType, //initial word contains length try { rawLength = (yield* read({ - calldata: { - start: startPosition, - length: DecodeUtils.EVM.WORD_SIZE - } + location, + start: startPosition, + length: DecodeUtils.EVM.WORD_SIZE }, state)); } catch(error) { //error: Values.DecodingError @@ -90,8 +93,10 @@ export function* decodeCalldataReferenceByAddress(dataType: Types.ReferenceType, } length = DecodeUtils.Conversion.toBN(rawLength).toNumber(); - let childPointer: CalldataPointer = { - calldata: { start: startPosition + DecodeUtils.EVM.WORD_SIZE, length } + let childPointer: AbiPointer = { + location, + start: startPosition + DecodeUtils.EVM.WORD_SIZE, + length } return yield* decodeValue(dataType, childPointer, info); @@ -103,10 +108,9 @@ export function* decodeCalldataReferenceByAddress(dataType: Types.ReferenceType, //initial word contains array length try { rawLength = (yield* read({ - calldata: { - start: startPosition, - length: DecodeUtils.EVM.WORD_SIZE - } + location, + start: startPosition, + length: DecodeUtils.EVM.WORD_SIZE }, state)); } catch(error) { //error: Values.DecodingError @@ -127,7 +131,7 @@ export function* decodeCalldataReferenceByAddress(dataType: Types.ReferenceType, let baseSize: number; try { - baseSize = calldataSizeForType(dataType.baseType, info.calldataAllocations); + baseSize = abiSizeForType(dataType.baseType, allocations); } catch(error) { //error: Values.DecodingError return new Values.GenericError(error.error); @@ -136,12 +140,13 @@ export function* decodeCalldataReferenceByAddress(dataType: Types.ReferenceType, let decodedChildren: Values.Value[] = []; for(let index = 0; index < length; index++) { decodedChildren.push( - (yield* decodeCalldata( + (yield* decodeAbi( dataType.baseType, - { calldata: { + { + location, start: startPosition + index * baseSize, length: baseSize - }}, + }, info, startPosition )) ); //pointer base is always start of list, never the length @@ -149,13 +154,14 @@ export function* decodeCalldataReferenceByAddress(dataType: Types.ReferenceType, return new Values.ArrayValueProper(dataType, decodedChildren); case "struct": - return yield* decodeCalldataStructByPosition(dataType, startPosition, info); + return yield* decodeAbiStructByPosition(dataType, startPosition, info); } } -export function* decodeCalldataReferenceStatic(dataType: Types.ReferenceType, pointer: CalldataPointer, info: EvmInfo): IterableIterator { +export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer: AbiPointer, info: EvmInfo): IterableIterator { debug("static"); debug("pointer %o", pointer); + const location = pointer.location; switch (dataType.typeClass) { case "array": @@ -164,7 +170,7 @@ export function* decodeCalldataReferenceStatic(dataType: Types.ReferenceType, po const length = (dataType).length.toNumber(); let baseSize: number; try { - baseSize = calldataSizeForType(dataType.baseType, info.calldataAllocations); + baseSize = abiSizeForType(dataType.baseType, info.allocations.abi); } catch(error) { //error: Values.DecodingError return new Values.GenericError(error.error); @@ -173,12 +179,13 @@ export function* decodeCalldataReferenceStatic(dataType: Types.ReferenceType, po let decodedChildren: Values.Value[] = []; for(let index = 0; index < length; index++) { decodedChildren.push( - (yield* decodeCalldata( + (yield* decodeAbi( dataType.baseType, - { calldata: { - start: pointer.calldata.start + index * baseSize, + { + location, + start: pointer.start + index * baseSize, length: baseSize - }}, + }, info )) ); //static case so don't need base @@ -186,16 +193,22 @@ export function* decodeCalldataReferenceStatic(dataType: Types.ReferenceType, po return new Values.ArrayValueProper(dataType, decodedChildren); case "struct": - return yield* decodeCalldataStructByPosition(dataType, pointer.calldata.start, info); + return yield* decodeAbiStructByPosition(dataType, location, pointer.start, info); } } -//note that this function takes the start position as a *number*; it does not take a calldata pointer -function* decodeCalldataStructByPosition(dataType: Types.StructType, startPosition: number, info: EvmInfo): IterableIterator { - const { userDefinedTypes, calldataAllocations } = info; +type AbiLocation = "calldata" | "eventdata" | "abi"; + +//note that this function takes the start position as a *number*; it does not take a pointer +function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLocation, startPosition: number, info: EvmInfo): IterableIterator { + const { userDefinedTypes, allocations: { abi: allocations } } = info; + + const typeLocation = location === "eventdata" + ? null //eventdata is not a valid location for a type + : location; const typeId = dataType.id; - const structAllocation = calldataAllocations[typeId]; + const structAllocation = allocations[typeId]; if(!structAllocation) { return new Values.GenericError( new Values.UserDefinedTypeNotFoundError(dataType) @@ -205,11 +218,10 @@ function* decodeCalldataStructByPosition(dataType: Types.StructType, startPositi let decodedMembers: {[field: string]: Values.Value} = {}; for(let memberAllocation of Object.values(structAllocation.members)) { const memberPointer = memberAllocation.pointer; - const childPointer: CalldataPointer = { - calldata: { - start: startPosition + memberPointer.calldata.start, - length: memberPointer.calldata.length - } + const childPointer: AbiPointer = { + location, + start: startPosition + memberPointer.start, + length: memberPointer.length }; let memberName = memberAllocation.definition.name; @@ -220,9 +232,9 @@ function* decodeCalldataStructByPosition(dataType: Types.StructType, startPositi ); } let storedMemberType = storedType.memberTypes[memberName]; - let memberType = Types.specifyLocation(storedMemberType, "calldata"); + let memberType = Types.specifyLocation(storedMemberType, typeLocation); - decodedMembers[memberName] = (yield* decodeCalldata(memberType, childPointer, info)); + decodedMembers[memberName] = (yield* decodeAbi(memberType, childPointer, info)); } return new Values.StructValueProper(dataType, decodedMembers); } diff --git a/packages/truffle-decoder-core/lib/decode/event.ts b/packages/truffle-decoder-core/lib/decode/event.ts new file mode 100644 index 00000000000..b64361db0d2 --- /dev/null +++ b/packages/truffle-decoder-core/lib/decode/event.ts @@ -0,0 +1,30 @@ +import debugModule from "debug"; +const debug = debugModule("decoder-core:decode:event"); + +import decodeValue from "./value"; +import { Types, Values, Conversion as ConversionUtils } from "truffle-decode-utils"; +import { EventTopicPointer } from "../types/pointer"; +import { EvmInfo } from "../types/evm"; +import { DecoderRequest, GeneratorJunk } from "../types/request"; + +export default function* decode(dataType: Types.Type, pointer: EventTopicPointer, info: EvmInfo): IterableIterator { + if(Types.isReferenceType(dataType)) { + //we cannot decode reference types "stored" in topics; we have to just return an error + let bytes: Uint8Array; + try { + bytes = yield* read(pointer, state); + } + catch(error) { //error: Values.DecodingError + return new Values.GenericError(error.error); + } + let raw: BN = ConversionUtils.toBN(bytes); + return new Values.GenericError( + new Values.IndexedReferenceTypeError( + dataType, + raw + ) + ) + } + //otherwise, dispatch to decodeValue + return yield* decodeValue(dataType, pointer, info); +} diff --git a/packages/truffle-decoder-core/lib/decode/index.ts b/packages/truffle-decoder-core/lib/decode/index.ts index 67dedb7a142..cd8615c18bf 100644 --- a/packages/truffle-decoder-core/lib/decode/index.ts +++ b/packages/truffle-decoder-core/lib/decode/index.ts @@ -9,51 +9,46 @@ import { decodeLiteral } from "./stack"; import decodeCalldata from "./calldata"; import decodeConstant from "./constant"; import decodeSpecial from "./special"; -import { AstDefinition, Types, Values } from "truffle-decode-utils"; +import decodeTopic from "./event"; +import { Types, Values } from "truffle-decode-utils"; import * as Pointer from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; -export default function* decode(definition: AstDefinition, pointer: Pointer.DataPointer, info: EvmInfo): IterableIterator { - let dataType = Types.definitionToType(definition); - debug("definition %O", definition); - return yield* decodePointer(dataType, pointer, info); -} - -export function* decodePointer(dataType: Types.Type, pointer: Pointer.DataPointer, info: EvmInfo): IterableIterator { +export default function* decode(dataType: Types.Type, pointer: Pointer.DataPointer, info: EvmInfo, base: number = 0): IterableIterator { debug("type %O", dataType); debug("pointer %O", pointer); - if(Pointer.isStoragePointer(pointer)) { - return yield* decodeStorage(dataType, pointer, info) - } + switch(pointer.location) { - if(Pointer.isStackPointer(pointer)) { - return yield* decodeStack(dataType, pointer, info); - } + case "storage": + return yield* decodeStorage(dataType, pointer, info) - if (Pointer.isStackLiteralPointer(pointer)) { - return yield* decodeLiteral(dataType, pointer, info); - } + case "stack": + return yield* decodeStack(dataType, pointer, info); - if(Pointer.isConstantDefinitionPointer(pointer)) { - return yield* decodeConstant(dataType, pointer, info); - //I'd like to just use decodeValue, but unfortunately there are some special - //cases to deal with - } + case "stackliteral": + return yield* decodeLiteral(dataType, pointer, info); - if(Pointer.isSpecialPointer(pointer)) { - return yield* decodeSpecial(dataType, pointer, info); - } + case "definition": + return yield* decodeConstant(dataType, pointer, info); - //NOTE: the following two cases shouldn't come up but they've been left in as - //fallback cases + case "special": + return yield* decodeSpecial(dataType, pointer, info); - if(Pointer.isMemoryPointer(pointer)) { - return yield* decodeMemory(dataType, pointer, info); - } + case "calldata": + case "eventdata": + return yield* decodeAbi(dataType, pointer, info, base); + + case "eventtopic": + return yield* decodeTopic(dataType, pointer, info); + + case "memory": + //NOTE: this case should never actually occur, but I'm including it + //anyway as a fallback + return yield* decodeMemory(dataType, pointer, info); - if(Pointer.isCalldataPointer(pointer)) { - return yield* decodeCalldata(dataType, pointer, info); + //...and in case "abi", which shouldn't happen, we'll just run off the end + //and cause a problem :P } } diff --git a/packages/truffle-decoder-core/lib/decode/memory.ts b/packages/truffle-decoder-core/lib/decode/memory.ts index 918f12955d6..8b6d56e5ad1 100644 --- a/packages/truffle-decoder-core/lib/decode/memory.ts +++ b/packages/truffle-decoder-core/lib/decode/memory.ts @@ -41,10 +41,9 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p //initial word contains length try { rawLength = yield* read({ - memory: { - start: startPosition, - length: DecodeUtils.EVM.WORD_SIZE - } + location: "memory", + start: startPosition, + length: DecodeUtils.EVM.WORD_SIZE }, state); } catch(error) { //error: Values.DecodingError @@ -53,7 +52,9 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p length = DecodeUtils.Conversion.toBN(rawLength).toNumber(); let childPointer: MemoryPointer = { - memory: { start: startPosition + DecodeUtils.EVM.WORD_SIZE, length } + location: "memory", + start: startPosition + DecodeUtils.EVM.WORD_SIZE, + length } return yield* decodeValue(dataType, childPointer, info); @@ -64,10 +65,9 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p //initial word contains array length try { rawLength = yield* read({ - memory: { - start: startPosition, - length: DecodeUtils.EVM.WORD_SIZE - } + location: "memory", + start: startPosition, + length: DecodeUtils.EVM.WORD_SIZE }, state); } catch(error) { //error: Values.DecodingError @@ -88,10 +88,11 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p decodedChildren.push( (yield* decodeMemory( baseType, - { memory: { + { + location: "memory", start: startPosition + index * DecodeUtils.EVM.WORD_SIZE, length: DecodeUtils.EVM.WORD_SIZE - }}, + }, info )) ); @@ -100,10 +101,10 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p return new Values.ArrayValueProper(dataType, decodedChildren); case "struct": - const { memoryAllocations, userDefinedTypes } = info; + const { allocations: { memory: allocations }, userDefinedTypes } = info; const typeId = dataType.id; - const structAllocation = memoryAllocations[typeId]; + const structAllocation = allocations[typeId]; if(!structAllocation) { return new Values.GenericError( new Values.UserDefinedTypeNotFoundError(dataType) @@ -116,10 +117,9 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p for(let memberAllocation of Object.values(structAllocation.members)) { const memberPointer = memberAllocation.pointer; const childPointer: MemoryPointer = { - memory: { - start: startPosition + memberPointer.memory.start, - length: memberPointer.memory.length //always equals WORD_SIZE - } + location: "memory", + start: startPosition + memberPointer.start, + length: memberPointer.length //always equals WORD_SIZE }; let memberName = memberAllocation.definition.name; diff --git a/packages/truffle-decoder-core/lib/decode/special.ts b/packages/truffle-decoder-core/lib/decode/special.ts index a254d455021..99c0c8285ce 100644 --- a/packages/truffle-decoder-core/lib/decode/special.ts +++ b/packages/truffle-decoder-core/lib/decode/special.ts @@ -31,10 +31,11 @@ export function* decodeMagic(dataType: Types.MagicType, pointer: SpecialPointer, kind: "dynamic", location: "calldata" }, - {calldata: { + { + location: "calldata", start: 0, length: state.calldata.length - }}, + }, info )), sig: (yield* decodeValue( @@ -43,10 +44,11 @@ export function* decodeMagic(dataType: Types.MagicType, pointer: SpecialPointer, kind: "static", length: DecodeUtils.EVM.SELECTOR_SIZE }, - {calldata: { + { + location: "calldata", start: 0, length: DecodeUtils.EVM.SELECTOR_SIZE, - }}, + }, info )), sender: (yield* decodeValue( @@ -54,7 +56,7 @@ export function* decodeMagic(dataType: Types.MagicType, pointer: SpecialPointer, typeClass: "address", payable: true }, - {special: "sender"}, + {location: "special", special: "sender"}, info )), value: (yield* decodeValue( @@ -62,7 +64,7 @@ export function* decodeMagic(dataType: Types.MagicType, pointer: SpecialPointer, typeClass: "uint", bits: 256 }, - {special: "value"}, + {location: "special", special: "value"}, info )) }); @@ -73,7 +75,7 @@ export function* decodeMagic(dataType: Types.MagicType, pointer: SpecialPointer, typeClass: "address", payable: true }, - {special: "origin"}, + {location: "special", special: "origin"}, info )), gasprice: (yield* decodeValue( @@ -81,7 +83,7 @@ export function* decodeMagic(dataType: Types.MagicType, pointer: SpecialPointer, typeClass: "uint", bits: 256 }, - {special: "gasprice"}, + {location: "special", special: "gasprice"}, info )) }); @@ -92,7 +94,7 @@ export function* decodeMagic(dataType: Types.MagicType, pointer: SpecialPointer, typeClass: "address", payable: true }, - {special: "coinbase"}, + {location: "special", special: "coinbase"}, info )) }; @@ -105,7 +107,7 @@ export function* decodeMagic(dataType: Types.MagicType, pointer: SpecialPointer, typeClass: "uint", bits: 256 }, - {special: variable}, + {location: "special", special: variable}, info )); } diff --git a/packages/truffle-decoder-core/lib/decode/stack.ts b/packages/truffle-decoder-core/lib/decode/stack.ts index bc9777a5166..3182c60e2be 100644 --- a/packages/truffle-decoder-core/lib/decode/stack.ts +++ b/packages/truffle-decoder-core/lib/decode/stack.ts @@ -21,7 +21,7 @@ export default function* decodeStack(dataType: Types.Type, pointer: StackPointer catch(error) { //error: Values.DecodingError return new Values.GenericError(error.error); } - const literalPointer: StackLiteralPointer = { literal: rawValue }; + const literalPointer: StackLiteralPointer = { location: "stackliteral", literal: rawValue }; return yield* decodeLiteral(dataType, literalPointer, info); } @@ -51,7 +51,7 @@ export function* decodeLiteral(dataType: Types.Type, pointer: StackLiteralPointe if(dataType.typeClass === "bytes" || dataType.typeClass === "string") { let start = DecodeUtils.Conversion.toBN(pointer.literal.slice(0, DecodeUtils.EVM.WORD_SIZE)).toNumber(); let length = DecodeUtils.Conversion.toBN(pointer.literal.slice(DecodeUtils.EVM.WORD_SIZE)).toNumber(); - let newPointer = { calldata: { start, length }}; + let newPointer = { location: "calldata", start, length }; return yield* decodeValue(dataType, newPointer, info); } @@ -64,7 +64,7 @@ export function* decodeLiteral(dataType: Types.Type, pointer: StackLiteralPointe //HACK -- in order to read the correct location, we need to add an offset //of -32 (since, again, we're throwing away the length info), so we pass //that in as the "base" value - return yield* decodeCalldataReferenceByAddress(dataType, {literal: locationOnly}, info, -DecodeUtils.EVM.WORD_SIZE); + return yield* decodeCalldataReferenceByAddress(dataType, {location: "stackliteral", literal: locationOnly}, info, -DecodeUtils.EVM.WORD_SIZE); } else { //multivalue case -- this case is straightforward diff --git a/packages/truffle-decoder-core/lib/decode/storage.ts b/packages/truffle-decoder-core/lib/decode/storage.ts index d13776d1133..8380ab8055b 100644 --- a/packages/truffle-decoder-core/lib/decode/storage.ts +++ b/packages/truffle-decoder-core/lib/decode/storage.ts @@ -27,6 +27,8 @@ export default function* decodeStorage(dataType: Types.Type, pointer: StoragePoi //Of course, pointers to value types don't exist in Solidity, so that warning is redundant, but... export function* decodeStorageReferenceByAddress(dataType: Types.ReferenceType, pointer: DataPointer, info: EvmInfo): IterableIterator { + const allocations = info.allocations.storage; + let rawValue: Uint8Array; try { rawValue = yield* read(pointer, info.state); @@ -37,7 +39,7 @@ export function* decodeStorageReferenceByAddress(dataType: Types.ReferenceType, const startOffset = DecodeUtils.Conversion.toBN(rawValue); let rawSize: StorageTypes.StorageLength; try { - rawSize = storageSizeForType(dataType, info.userDefinedTypes, info.storageAllocations); + rawSize = storageSizeForType(dataType, info.userDefinedTypes, allocations); } catch(error) { //error: Values.DecodingError return new Values.GenericError(error.error); @@ -47,7 +49,7 @@ export function* decodeStorageReferenceByAddress(dataType: Types.ReferenceType, //coercion const size = (<{words: number}>rawSize).words; //now, construct the storage pointer - const newPointer = { storage: { + const newPointer = { location: "storage", range: { from: { slot: { offset: startOffset @@ -96,7 +98,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: debug("about to determine baseSize"); let baseSize: StorageTypes.StorageLength; try { - baseSize = storageSizeForType(dataType.baseType, info.userDefinedTypes, info.storageAllocations); + baseSize = storageSizeForType(dataType.baseType, info.userDefinedTypes, allocations); } catch(error) { //error: Values.DecodingError return new Values.GenericError(error.error); @@ -111,7 +113,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: if(StorageTypes.isWordsLength(baseSize)) { //currentSlot will point to the start of the entry being decoded let currentSlot: StorageTypes.Slot = { - path: pointer.storage.from.slot, + path: pointer.range.from.slot, offset: new BN(0), hashPath: dataType.kind === "dynamic" }; @@ -150,7 +152,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: //note we have baseSize.bytes <= DecodeUtils.EVM.WORD_SIZE let currentPosition: StorageTypes.StoragePosition = { slot: { - path: pointer.storage.from.slot, + path: pointer.range.from.slot, offset: new BN(0), hashPath: dataType.kind === "dynamic" }, @@ -184,7 +186,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: for(let childRange of ranges) { decodedChildren.push( - (yield* decodeStorage(dataType.baseType, {storage: childRange}, info)) + (yield* decodeStorage(dataType.baseType, {location: "storage", range: childRange}, info)) ); } @@ -208,20 +210,20 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: length = lengthByte / 2; debug("in-word; length %o", length); - return yield* decodeValue(dataType, { storage: { - from: { slot: pointer.storage.from.slot, index: 0 }, - to: { slot: pointer.storage.from.slot, index: length - 1} + return yield* decodeValue(dataType, { location: "storage", range: { + from: { slot: pointer.range.from.slot, index: 0 }, + to: { slot: pointer.range.from.slot, index: length - 1} }}, info); } else { length = DecodeUtils.Conversion.toBN(data).subn(1).divn(2).toNumber(); debug("new-word, length %o", length); - return yield* decodeValue(dataType, { - storage: { + return yield* decodeValue(dataType, { location: "storage", + range: { from: { slot: { - path: pointer.storage.from.slot, + path: pointer.range.from.slot, offset: new BN(0), hashPath: true }, @@ -236,7 +238,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: case "struct": { const typeId = dataType.id; - const structAllocation = info.storageAllocations[typeId]; + const structAllocation = allocations[typeId]; if(!structAllocation) { return new Values.GenericError( new Values.UserDefinedTypeNotFoundError(dataType) @@ -255,19 +257,19 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: const childRange: StorageTypes.Range = { from: { slot: { - path: pointer.storage.from.slot, - offset: memberPointer.storage.from.slot.offset.clone() + path: pointer.range.from.slot, + offset: memberPointer.range.from.slot.offset.clone() //note that memberPointer should have no path }, - index: memberPointer.storage.from.index + index: memberPointer.range.from.index }, to: { slot: { - path: pointer.storage.from.slot, - offset: memberPointer.storage.to.slot.offset.clone() + path: pointer.range.from.slot, + offset: memberPointer.range.to.slot.offset.clone() //note that memberPointer should have no path }, - index: memberPointer.storage.to.index + index: memberPointer.range.to.index }, }; @@ -281,7 +283,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: let storedMemberType = storedType.memberTypes[memberName]; let memberType = Types.specifyLocation(storedMemberType, "storage"); - decodedMembers[memberName] = (yield* decodeStorage(memberType, {storage: childRange}, info)); + decodedMembers[memberName] = (yield* decodeStorage(memberType, {location: "storage", range: childRange}, info)); } return new Values.StructValueProper(dataType, decodedMembers); @@ -294,7 +296,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: const valueType = dataType.valueType; let valueSize: StorageTypes.StorageLength; try { - valueSize = storageSizeForType(valueType, info.userDefinedTypes, info.storageAllocations); + valueSize = storageSizeForType(valueType, info.userDefinedTypes, allocations); } catch(error) { //error: Values.DecodingError return new Values.GenericError(error.error); @@ -302,7 +304,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: let decodedEntries: [Values.ElementaryValueProper, Values.Value][] = []; - const baseSlot: StorageTypes.Slot = pointer.storage.from.slot; + const baseSlot: StorageTypes.Slot = pointer.range.from.slot; debug("baseSlot %o", baseSlot); debug("base slot address %o", slotAddress(baseSlot)); @@ -315,7 +317,8 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: if(StorageTypes.isWordsLength(valueSize)) { valuePointer = { - storage: { + location: "storage", + range: { from: { slot: { key, @@ -337,7 +340,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: } else { valuePointer = { - storage: { + range: { from: { slot: { key, diff --git a/packages/truffle-decoder-core/lib/interface/decoding.ts b/packages/truffle-decoder-core/lib/interface/decoding.ts new file mode 100644 index 00000000000..df0bbbe76a2 --- /dev/null +++ b/packages/truffle-decoder-core/lib/interface/decoding.ts @@ -0,0 +1,119 @@ +import debugModule from "debug"; +const debug = debugModule("decoder-core:interface"); + +import { AstDefinition, Types, Values } from "truffle-decode-utils"; +import * as DecodeUtils from "truffle-decode-utils"; +import * as Pointer from "../types/pointer"; +import { EvmInfo } from "../types/evm"; +import { DecoderRequest, GeneratorJunk } from "../types/request"; +import { CalldataAllocation, EventAllocation } from "../types/allocation"; +import decode from "../decode"; + +export function* decodeVariable(definition: AstDefinition, pointer: Pointer.DataPointer, info: EvmInfo): IterableIterator { + let dataType = Types.definitionToType(definition); + debug("definition %O", definition); + return yield* decode(dataType, pointer, info); //no need to pass an offset +} + +export interface CalldataDecoding { + kind: "function" | "constructor" | "fallback" + name: string; + arguments?: { + [name: string]: Values.Value; + } +} + +export function* decodeCalldata(info: EvmInfo, contractId: number): IterableIterator { + const allocations = info.allocations.calldata[contractId]; + let allocation: CalldataAllocation; + let isConstructor: boolean = info.currentContext.isConstructor; + //first: is this a creation call? + if(isConstructor) { + allocation = allocations.constructorAllocation; + } + else { + //TODO: error-handling here + let rawSelector = read(info.state, + { location: "calldata", + start: 0, + length: DecodeUtils.EVM.SELECTOR_SIZE + } + ); + let selector = DecodeUtils.EVM.toHexString(rawSelector); + allocation = allocations[selector]; + } + if(allocation === undefined) { + return { kind: "fallback" }; + } + let decodedArguments = Object.assign({}, + ...Object.values(allocation).map(argumentAllocation => + ({ [argumentAllocation.definition.name]: + decode( + Types.definitionToType(argumentAllocation.definition), + argumentAllocation.pointer, + info, + allocation.offset //note the use of the offset for decoding pointers! + ) + }) + ) + ); + if(isConstructor) { + return { + kind: "constructor", + arguments: decodedArguments + }; + } + else { + return { + kind: "function", + name: allocation.definition.name, + arguments: decodedArguments + }; + } +} + +export interface EventDecoding { + kind: "event" | "anonymous"; + name?: string; + arguments?: EventDecodingArgument[]; +} + +export interface EventDecodingArgument { + name?: string; + value: Values.Value; +} + +export function* decodeEvent(info: EvmInfo, contractId: number): IterableIterator { + const allocations = info.allocations.event[contractId]; + //TODO: error-handling here + let rawSelector = read(info.state, + { location: "eventdata", + topic: 0 + } + ); + let selector = DecodeUtils.EVM.toHexString(rawSelector); + allocation = allocations[selector]; + if(allocation === undefined) { + //we can't decode + return { kind: "anonymous" }; + } + let decodedArguments: EventDecodingArgument[]; + for(argumentAllocation of Object.values(allocation)) { + const value = decode( + Types.definitionToType(argumentAllocation.definition), + argumentAllocation.pointer, + info, + 0 //offset is always 0 but let's be explicit + ); + const name = argumentAllocation.definition.name; + const position = argumentAllocation.position; + decodedArguments[position] = name === undefined + ? { value } + : { name, value }; + } + return { + kind: "event", + name: allocation.definition.name, + arguments: decodedArguments + }; +} diff --git a/packages/truffle-decoder-core/lib/interface/index.ts b/packages/truffle-decoder-core/lib/interface/index.ts index 4da52bd5c8f..90f87aec9c3 100644 --- a/packages/truffle-decoder-core/lib/interface/index.ts +++ b/packages/truffle-decoder-core/lib/interface/index.ts @@ -1,9 +1,3 @@ -import { AstDefinition, Values } from "truffle-decode-utils"; -import { DataPointer } from "../types/pointer"; -import { EvmInfo } from "../types/evm"; -import decode from "../decode"; -import { DecoderRequest, GeneratorJunk } from "../types/request"; - export { getStorageAllocations, storageSize } from "../allocate/storage"; export { getCalldataAllocations } from "../allocate/calldata"; export { getMemoryAllocations } from "../allocate/memory"; @@ -15,6 +9,4 @@ export { Slot, isWordsLength } from "../types/storage"; export { DecoderRequest, isStorageRequest, isCodeRequest } from "../types/request"; export { EvmInfo } from "../types/evm"; -export function* forEvmState(definition: AstDefinition, pointer: DataPointer, info: EvmInfo): IterableIterator { - return yield* decode(definition, pointer, info); -} +export { decodeVariable, decodeEvent, decodeCalldata } from "./decoding"; diff --git a/packages/truffle-decoder-core/lib/read/memory.ts b/packages/truffle-decoder-core/lib/read/bytes.ts similarity index 100% rename from packages/truffle-decoder-core/lib/read/memory.ts rename to packages/truffle-decoder-core/lib/read/bytes.ts diff --git a/packages/truffle-decoder-core/lib/read/index.ts b/packages/truffle-decoder-core/lib/read/index.ts index ba47ea7ce28..adf257c48ba 100644 --- a/packages/truffle-decoder-core/lib/read/index.ts +++ b/packages/truffle-decoder-core/lib/read/index.ts @@ -1,5 +1,5 @@ import * as storage from "./storage"; -import * as memory from "./memory"; +import * as bytes from "./bytes"; import * as stack from "./stack"; import * as constant from "./constant"; import * as Pointer from "../types/pointer"; @@ -8,21 +8,49 @@ import { DecoderRequest } from "../types/request"; import { Values } from "truffle-decode-utils"; export default function* read(pointer: Pointer.DataPointer, state: EvmState): IterableIterator { - if (Pointer.isStackPointer(pointer) && state.stack) { - return stack.readStack(state.stack, pointer.stack.from, pointer.stack.to); - } else if (Pointer.isStoragePointer(pointer) && state.storage) { - return yield* storage.readRange(state.storage, pointer.storage); - } else if (Pointer.isMemoryPointer(pointer) && state.memory) { - return memory.readBytes(state.memory, pointer.memory.start, pointer.memory.length); - } else if (Pointer.isCalldataPointer(pointer) && state.calldata) { - return memory.readBytes(state.calldata, pointer.calldata.start, pointer.calldata.length); - //there is no need for a separate calldata read function; the existing memory read function - //will do fine - } else if (Pointer.isStackLiteralPointer(pointer)) { - return pointer.literal; - } else if (Pointer.isConstantDefinitionPointer(pointer)) { - return constant.readDefinition(pointer.definition); - } else if (Pointer.isSpecialPointer(pointer)) { - return state.specials[pointer.special]; + switch(pointer.location) { + + case "stack": + return stack.readStack(state.stack, pointer.from, pointer.to); + + case "storage": + return yield* storage.readRange(state.storage, pointer.range); + + case "memory": + return bytes.readBytes(state.memory, pointer.start, pointer.length); + + case "calldata": + return bytes.readBytes(state.calldata, pointer.start, pointer.length); + + case "eventdata": + //similarly with eventdata + return bytes.readBytes(state.eventdata, pointer.start, pointer.length); + + case "stackliteral": + return pointer.literal; + + case "definition": + return constant.readDefinition(pointer.definition); + + case "special": + //not bothering with error handling on this oen as I don't expect errors + return state.specials[pointer.special]; + + case "eventtopic": + return readTopic(state.eventTopics, pointer.topic); + + //...and in the case of "abi", which shouldn't happen, we'll just fall off + //the end and cause a problem :P + } +} + +//this one is simple enough I'm keeping it in the same file +function readTopic(topics: Uint8Array[], index: number) { + let topic = topics[index]; + if(topic === undefined) { + throw new Values.DecodingError( + new Values.ReadErrorTopic(index); + ); } + return topic; } diff --git a/packages/truffle-decoder-core/lib/types/allocation.ts b/packages/truffle-decoder-core/lib/types/allocation.ts index b6856557245..fb163ab9dbb 100644 --- a/packages/truffle-decoder-core/lib/types/allocation.ts +++ b/packages/truffle-decoder-core/lib/types/allocation.ts @@ -1,5 +1,5 @@ import { StorageLength } from "./storage"; -import { StoragePointer, ConstantDefinitionPointer, CalldataPointer, MemoryPointer } from "./pointer"; +import * as Pointer from "./pointer"; import { AstDefinition } from "truffle-decode-utils"; //holds a collection of storage allocations for structs and contracts, indexed @@ -27,37 +27,37 @@ export interface StorageMemberAllocations { //of a contract export interface StorageMemberAllocation { definition: AstDefinition; - pointer: StoragePointer | ConstantDefinitionPointer; + pointer: Pointer.StoragePointer | Pointer.ConstantDefinitionPointer; } -//calldata types below work similar to storage types above; note these are only +//abi types below work similar to storage types above; note these are only //used for structs so there's no need to account for contracts or constants //also, we now also keep track of which structs are dynamic -//also, we allow a calldata allocation to be null to indicate a type not allowed -//in calldata +//also, we allow an abi allocation to be null to indicate a type not allowed +//in the abi -export interface CalldataAllocations { - [id: number]: CalldataAllocation | null +export interface AbiAllocations { + [id: number]: AbiAllocation | null } -export interface CalldataAllocation { +export interface AbiAllocation { definition: AstDefinition; length: number; //measured in bytes dynamic: boolean; - members: CalldataMemberAllocations; + members: AbiMemberAllocations; } -export interface CalldataMemberAllocations { - [id: number]: CalldataMemberAllocation +export interface AbiMemberAllocations { + [id: number]: AbiMemberAllocation } -export interface CalldataMemberAllocation { +export interface AbiMemberAllocation { definition: AstDefinition; - pointer: CalldataPointer; + pointer: Pointer.GenericAbiPointer; } -//and finally, memory; works the same as calldata, except we don't bother keeping -//track of size (it's always 1 word) or dynamicity (meaningless in memory) +//memory works the same as abi except we don't bother keeping track of size +//(it's always 1 word) or dynamicity (meaningless in memory) export interface MemoryAllocations { [id: number]: MemoryAllocation @@ -74,5 +74,67 @@ export interface MemoryMemberAllocations { export interface MemoryMemberAllocation { definition: AstDefinition; - pointer: MemoryPointer; + pointer: Pointer.MemoryPointer; +} + +//next we have calldata, used for the input to an external function; +//because this doesn't go inside something else we don't bother keeping +//track of length or dynamicity. There's also no need for null allocation. +//So basically this works like memory, except that we *do* store an offset +//indicating the overall start position. +//Also, we index by contract ID and then selector rather than by function ID +//(and have a special one for the constructor) + +export interface CalldataAllocations { + [id: number]: CalldataContractAllocation +} + +export interface CalldataContractAllocations { + constructorAllocation: CalldataAllocation, + [selector: string]: CalldataAllocation +} + +export interface CalldataAllocation { + definition?: AstDefinition; //may be omitted for implciit constructor + offset: number; //measured in bytes + arguments: CalldataArgumentAllocations; +} + +export interface CalldataArgumentAllocations { + [contractId: number]: CalldataArgumentAllocation +} + +export interface CalldataArgumentAllocation { + definition: AstDefinition; + pointer: Pointer.CalldataPointer; +} + +//finally we have events. these work like calldata, except that +//there's no need for an offset, and the ultimate pointer can +//be either an event data pointer or an event topic pointer +//(also, there's no constructor) +//also, we want to know event argument positions, so that's +//included, too + +export interface EventAllocations { + [contractId: number]: EventContractAllocations +} + +export interface EventContractAllocations { + [selector: string]: EventAllocation +} + +export interface EventAllocation { + definition: AstDefinition; + arguments: EventMemberAllocations; +} + +export interface EventArgumentAllocations { + [id: number]: EventArgumentAllocation +} + +export interface EventArgumentAllocation { + position: number; + definition: AstDefinition; + pointer: Pointer.EventDataPointer | Pointer.EventTopicPointer; } diff --git a/packages/truffle-decoder-core/lib/types/evm.ts b/packages/truffle-decoder-core/lib/types/evm.ts index 249176be050..6ca45de363d 100644 --- a/packages/truffle-decoder-core/lib/types/evm.ts +++ b/packages/truffle-decoder-core/lib/types/evm.ts @@ -1,15 +1,17 @@ import { AstReferences, Contexts, Types } from "truffle-decode-utils"; -import { StorageAllocations, CalldataAllocations, MemoryAllocations, StorageMemberAllocations } from "./allocation"; +import * as Allocations from "./allocation"; import { Slot } from "./storage"; export interface EvmState { - stack: Uint8Array[]; storage: WordMapping; - memory: Uint8Array; + stack?: Uint8Array[]; + memory?: Uint8Array; calldata?: Uint8Array; specials?: { [builtin: string]: Uint8Array //sorry - } + }; + eventdata?: Uint8Array; + eventtopics?: Uint8Array[]; } export interface WordMapping { @@ -20,9 +22,13 @@ export interface EvmInfo { state: EvmState; mappingKeys?: Slot[]; userDefinedTypes?: Types.TypesById; - storageAllocations?: StorageAllocations; - calldataAllocations?: CalldataAllocations; - memoryAllocations?: MemoryAllocations; + allocations: { + storage?: Allocations.StorageAllocations; + memory?: Allocations.MemoryAllocations; + abi?: Allocations.AbiAllocations; + calldata?: Allocations.CalldataAllocations; + event?: Allocations.EventAllocations; + } contexts?: Contexts.DecoderContexts; currentContext?: Contexts.DecoderContext; internalFunctionsTable?: InternalFunctions; diff --git a/packages/truffle-decoder-core/lib/types/pointer.ts b/packages/truffle-decoder-core/lib/types/pointer.ts index efa88399752..9dfe6903171 100644 --- a/packages/truffle-decoder-core/lib/types/pointer.ts +++ b/packages/truffle-decoder-core/lib/types/pointer.ts @@ -3,73 +3,61 @@ import { Range } from "./storage"; export type DataPointer = StackPointer | MemoryPointer | StoragePointer | CalldataPointer | StackLiteralPointer | ConstantDefinitionPointer - | SpecialPointer; + | SpecialPointer | EventDataPointer | EventTopicPointer | GenericAbiPointer; -export interface GenericPointer { - typeClass?: string; -} - -export interface StackPointer extends GenericPointer { - stack: { - from: number; - to: number; - } -} - -export interface MemoryPointer extends GenericPointer { - memory: { - start: number; - length: number; - } -} +export type AbiPointer = CalldataPointer | EventDataPointer | GenericAbiPointer; -export interface CalldataPointer extends GenericPointer { - calldata: { - start: number; - length: number; - } +export interface StackPointer { + location: "stack", + from: number; + to: number; } -export interface StoragePointer extends GenericPointer { - storage: Range; +export interface MemoryPointer { + location: "memory", + start: number; + length: number; } -export interface StackLiteralPointer extends GenericPointer { - literal: Uint8Array; +export interface CalldataPointer { + location: "calldata", + start: number; + length: number; } -export interface ConstantDefinitionPointer extends GenericPointer { - definition: AstDefinition; +export interface EventDataPointer { + location: "eventdata", + start: number; + length: number; } -export interface SpecialPointer extends GenericPointer { - special: string; //sorry +export interface EventTopicPointer { + location: "eventtopic", + topic: number; } -export function isStackPointer(pointer: DataPointer): pointer is StackPointer { - return typeof pointer !== "undefined" && "stack" in pointer; +export interface GenericAbiPointer { + location: "abi", + start: number; + length: number; } -export function isMemoryPointer(pointer: DataPointer): pointer is MemoryPointer { - return typeof pointer !== "undefined" && "memory" in pointer; +export interface StoragePointer { + location: "storage", + range: Range; } -export function isCalldataPointer(pointer: DataPointer): pointer is CalldataPointer { - return typeof pointer !== "undefined" && "calldata" in pointer; -} - -export function isStoragePointer(pointer: DataPointer): pointer is StoragePointer { - return typeof pointer !== "undefined" && "storage" in pointer; -} - -export function isStackLiteralPointer(pointer: DataPointer): pointer is StackLiteralPointer { - return typeof pointer !== "undefined" && "literal" in pointer; +export interface StackLiteralPointer { + location: "stackliteral", + literal: Uint8Array; } -export function isConstantDefinitionPointer(pointer: DataPointer): pointer is ConstantDefinitionPointer { - return typeof pointer !== "undefined" && "definition" in pointer; +export interface ConstantDefinitionPointer { + location: "definition", + definition: AstDefinition; } -export function isSpecialPointer(pointer: DataPointer): pointer is SpecialPointer { - return typeof pointer !== "undefined" && "special" in pointer; +export interface SpecialPointer { + location: "special", + special: string; //sorry } diff --git a/packages/truffle-decoder/lib/decoder.ts b/packages/truffle-decoder/lib/contract.ts similarity index 72% rename from packages/truffle-decoder/lib/decoder.ts rename to packages/truffle-decoder/lib/contract.ts index 1f1a5417f0f..0d5214715ca 100644 --- a/packages/truffle-decoder/lib/decoder.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -11,7 +11,6 @@ import { Definition as DefinitionUtils, EVM, AstDefinition, AstReferences } from import { BlockType, Transaction } from "web3/eth/types"; import { EventLog, Log } from "web3/types"; import { Provider } from "web3/providers"; -import abiDecoder from "abi-decoder"; import isEqual from "lodash.isequal"; //util.isDeepStrictEqual doesn't exist in Node 8 import * as Decoder from "truffle-decoder-core"; import * as DecoderTypes from "./types"; @@ -31,14 +30,15 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { private contractNodes: AstReferences = {}; private contexts: DecodeUtils.Contexts.DecoderContexts = {}; private context: DecodeUtils.Contexts.DecoderContext; + private constructorContext: DecodeUtils.Contexts.DecoderContext; private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; - private storageAllocations: Decoder.StorageAllocations; - - private eventDefinitions: AstReferences; - private eventDefinitionIdsByName: { - [name: string]: number + private allocations: { + storage: Decoder.StorageAllocations; + abi: Decoder.AbiAllocations; + calldata: Decoder.CalldataAllocations; + event: Decoder.EventAllocations; }; private stateVariableReferences: Decoder.StorageMemberAllocations; @@ -66,38 +66,47 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.contracts[this.contractNode.id] = this.contract; this.contractNodes[this.contractNode.id] = this.contractNode; - if(this.contract.deployedBinary) { //just to be safe - this.context = this.makeContext(this.contract, this.contractNode); - this.contexts[this.contractNode.id] = this.context; + if(this.contract.deployedBytecode) { //just to be safe + const context = makeContext(this.contract, this.contractNode); + const hash = DecodeUtils.Conversion.toHexString( + DecodeUtils.EVM.keccak256({type: "string", + value: context.binary + }) + ); + this.contextHash = hash; + this.contexts[hash] = this.context; + } + if(this.contract.bytecode) { //now the constructor version + const constructorContext = makeContext(this.contract, this.contractNode, true); + const hash = DecodeUtils.Conversion.toHexString( + DecodeUtils.EVM.keccak256({type: "string", + value: constructorContext.binary + }) + ); + this.constructorContextHash = hash; + this.contexts[hash] = this.constructorContext; } - abiDecoder.addABI(this.contract.abi); for(let relevantContract of this.relevantContracts) { - abiDecoder.addABI(relevantContract.abi); let node: AstDefinition = Utils.getContractNode(relevantContract); if(node !== undefined) { this.contracts[node.id] = relevantContract; this.contractNodes[node.id] = node; - if(relevantContract.deployedBinary) { - this.contexts[node.id] = this.makeContext(relevantContract, node); + if(relevantContract.deployedBytecode) { + const context = makeContext(relevantContract, node); + const hash = DecodeUtils.Conversion.toHexString( + DecodeUtils.EVM.keccak256({type: "string", + value: context.binary + }) + ); + this.contexts[hash] = context; } } } this.contexts = DecodeUtils.Contexts.normalizeContexts(this.contexts); - } - - private makeContext(contract: ContractObject, node: AstDefinition) { - //we want the non-constructor context here - return { - contractName: contract.contractName, - binary: contract.deployedBytecode, - contractId: node.id, - contractKind: node.contractKind, - isConstructor: false, - abi: DecodeUtils.Contexts.abiToFunctionAbiWithSignatures(contract.abi), - payable: DecodeUtils.Contexts.isABIPayable(contract.abi) - }; + this.context = this.contexts[this.contextHash]; + this.constructorContext = this.contexts[this.constructorContextHash]; } public async init(): Promise { @@ -110,17 +119,21 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.referenceDeclarations = Utils.getReferenceDeclarations(Object.values(this.contractNodes)); this.userDefinedTypes = Types.definitionsToStoredTypes(this.referenceDeclarations); - this.eventDefinitions = Utils.getEventDefinitions(Object.values(this.contractNodes)); - this.eventDefinitionIdsByName = {}; - for (let id in this.eventDefinitions) { - this.eventDefinitionIdsByName[this.eventDefinitions[id].name] = parseInt(id); - //this parseInt shouldn't be necessary, but TypeScript refuses to believe - //that id must be a number even though the definition of AstReferences - //says so - } - debug("done with event definitions"); - - this.storageAllocations = Decoder.getStorageAllocations(this.referenceDeclarations, {[this.contractNode.id]: this.contractNode}); + this.allocations.storage = Decoder.getStorageAllocations(this.referenceDeclarations, {[this.contractNode.id]: this.contractNode}); + this.allocations.abi = Decoder.getAbiAllocations(this.referenceDeclarations); + this.allocations.event[this.contractNode.id] = Decoder.getEventAllocations( + this.contract.abi, + this.referenceDeclarations, + this.contractNode.linearizedBaseContracts, + this.allocations.abi + ); + this.allocations.calldata[this.contractNode.id] = Decoder.getCalldataAllocations( + this.contract.abi, + this.referenceDeclarations, + this.contractNode.linearizedBaseContracts, + this.allocations.abi, + this.constructorContext; + ); debug("done with allocation"); this.stateVariableReferences = this.storageAllocations[this.contractNode.id].members; debug("stateVariableReferences %O", this.stateVariableReferences); @@ -131,18 +144,16 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { private async decodeVariable(variable: Decoder.StorageMemberAllocation, block: number): Promise { const info: Decoder.EvmInfo = { state: { - stack: [], storage: {}, - memory: new Uint8Array(0) }, mappingKeys: this.mappingKeys, userDefinedTypes: this.userDefinedTypes, - storageAllocations: this.storageAllocations, + allocations: this.allocations, contexts: this.contexts, currentContext: this.context }; - const decoder = Decoder.forEvmState(variable.definition, variable.pointer, info); + const decoder = Decoder.decodeVariable(variable.definition, variable.pointer, info); let result = decoder.next(); while(!result.done) { @@ -301,70 +312,100 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { //all descendants, you'll need to alter watchMappingKey to use an if rather //than a while - public decodeTransaction(transaction: Transaction): any { - const decodedData = abiDecoder.decodeMethod(transaction.input); - - return decodedData; - } - - public decodeLog(log: Log): any { - const decodedLogs = this.decodeLogs([log]); - - return decodedLogs[0]; - } - - public decodeLogs(logs: Log[]): any[] { - const decodedLogs = abiDecoder.decodeLogs(logs); + //NOTE: will only work with transactions to-or-creating this address! + public async decodeTransaction(transaction: Transaction): DecodedTransaction { + const block = transaction.blockNumber; + const data = DecodeUtils.Conversion.toBytes(transaction.input); + const info: Decoder.EvmInfo = { + state: { + storage: {}, + calldata: data, + }, + mappingKeys: this.mappingKeys, + userDefinedTypes: this.userDefinedTypes, + allocations: this.allocations, + contexts: this.contexts + }; + const decoder = Decoder.decodeCalldata(info, this.contractNode.id); - return decodedLogs; + let result = decoder.next(); + while(!result.done) { + let request = (result.value); + let response: Uint8Array; + //only code requests should occur here + if(Decoder.isCodeRequest(request)) { + response = await this.getCode(request.address, block); + } + result = decoder.next(response); + } + //at this point, result.value holds the final value + const decoding = result.value; + + return { + ...transaction, + decoding + }; } - private decodeEvent(event: EventLog): DecoderTypes.ContractEvent { - let contractEvent: DecoderTypes.ContractEvent = { - logIndex: event.logIndex, - name: event.event, - blockHash: event.blockHash, - blockNumber: event.blockNumber, - transactionHash: event.transactionHash, - transactionIndex: event.transactionIndex, - variables: {} + //NOTE: will only work with logs for this address! + public async decodeLog(log: Log): DecodedEvent { + const block = log.blockNumber; + const data = DecodeUtils.Conversion.toBytes(log.data); + const topics = log.topics.map(DecodeUtils.Conversion.toBytes); + const info: Decoder.EvmInfo = { + state: { + storage: {}, + eventdata: data, + eventtopics: topics + }, + mappingKeys: this.mappingKeys, + userDefinedTypes: this.userDefinedTypes, + allocations: this.allocations, + contexts: this.contexts }; + const decoder = Decoder.decodeEvent(info, this.contractNode.id); - const eventDefinition = this.eventDefinitions[this.eventDefinitionIdsByName[contractEvent.name]]; - - if (typeof eventDefinition.parameters !== "undefined" && typeof eventDefinition.parameters.parameters !== "undefined") { - const argumentDefinitions = eventDefinition.parameters.parameters; - - for (let i = 0; i < argumentDefinitions.length; i++) { - const definition = argumentDefinitions[i]; - - if (definition.nodeType === "VariableDeclaration") { - contractEvent.variables[definition.name] = { - name: definition.name, - type: DefinitionUtils.typeClass(definition), - value: event.returnValues[definition.name] // TODO: this should be a decoded value, it currently is a string always - }; - } + let result = decoder.next(); + while(!result.done) { + let request = (result.value); + let response: Uint8Array; + //only code requests should occur here + if(Decoder.isCodeRequest(request)) { + response = await this.getCode(request.address, block); } + result = decoder.next(response); } + //at this point, result.value holds the final value + const decoding = result.value; + + return { + ...log, + decoding + }; + } - return contractEvent; + //NOTE: will only work with logs for this address! + public async decodeLogs(logs: Log[]): DecodedEvent[] { + return await Promise.all(logs.map(this.decodeLog)); } - public async events(name: string | null = null, block: BlockType = "latest"): Promise { - const web3Contract = new this.web3.eth.Contract(this.contract.abi, this.contractAddress); - const events = await web3Contract.getPastEvents(name, { - fromBlock: block, - toBlock: block + public async events(name: string | null = null, fromBlock: BlockType = "latest", toBlock: BlockType = "latest"): Promise { + const logs = await web3.eth.getPastLogs({ + address: this.address, + fromBlock, + toBlock, }); - let contractEvents: DecoderTypes.ContractEvent[] = []; + let events = this.decodeLogs(logs); - for (let i = 0; i < events.length; i++) { - contractEvents.push(this.decodeEvent(events[i])); + if(name !== null) { + events = events.filter(event => + event.decoding.kind === "event" + && event.decoding.name === name + ); } - return contractEvents; + return events; } public onEvent(name: string, callback: Function): void { diff --git a/packages/truffle-decoder/lib/index.ts b/packages/truffle-decoder/lib/index.ts index 2693f72984f..c3b32ed6171 100644 --- a/packages/truffle-decoder/lib/index.ts +++ b/packages/truffle-decoder/lib/index.ts @@ -1,7 +1,12 @@ -import TruffleContractDecoder from "./decoder"; +import TruffleContractDecoder from "./contract"; +import TruffleWireDecoder from "./wire"; import { Provider } from "web3/providers"; import { ContractObject } from "truffle-contract-schema/spec"; export function forContract(contract: ContractObject, relevantContracts: ContractObject[], provider: Provider, address?: string): TruffleContractDecoder { return new TruffleContractDecoder(contract, relevantContracts, provider, address); } + +export function forProject(contracts: ContractObject[], provider: Provider): TruffleContractDecoder { + return new TruffleWireDecoder(contracts, provider, address); +} diff --git a/packages/truffle-decoder/lib/types.ts b/packages/truffle-decoder/lib/types.ts index 1551582de63..4f68adbaaa4 100644 --- a/packages/truffle-decoder/lib/types.ts +++ b/packages/truffle-decoder/lib/types.ts @@ -2,13 +2,6 @@ import BN from "bn.js"; import { ContractObject } from "truffle-contract-schema/spec"; import { Values } from "truffle-decode-utils"; -interface EventVariable { - name: string; - type: string; - value: string; //NOTE: this should change to be a decoded variable object - //(although really that would replace EventVariable entirely) -}; - export interface ContractState { name: string; balance: BN; @@ -19,17 +12,13 @@ export interface ContractState { }; }; -export interface ContractEvent { - logIndex: number; - name: string; - blockHash: string; - blockNumber: number; - transactionHash: string; - transactionIndex: number; - variables: { - [name: string]: EventVariable - } -}; +export interface DecodedTransaction extends Transaction { + decoding: CalldataDecoding; +} + +export interface DecodedEvent extends Log { + decoding: EventDecoding; +} export interface ContractMapping { [nodeId: number]: ContractObject; diff --git a/packages/truffle-decoder/lib/utils.ts b/packages/truffle-decoder/lib/utils.ts index ef75e56d432..33a97f91139 100644 --- a/packages/truffle-decoder/lib/utils.ts +++ b/packages/truffle-decoder/lib/utils.ts @@ -45,3 +45,15 @@ export function getEventDefinitions(contracts: AstDefinition[]): AstReferences { return getDeclarationsForTypes(contracts, types); } + +export function makeContext(contract: ContractObject, node: AstDefinition, isConstructor = false): DecoderContext { + return { + contractName: contract.contractName, + binary: isConstructor ? contract.bytecode : contract.deployedBytecode, + contractId: node.id, + contractKind: node.contractKind, + isConstructor, + abi: DecodeUtils.Contexts.abiToFunctionAbiWithSignatures(contract.abi), + payable: DecodeUtils.Contexts.isABIPayable(contract.abi) + }; +} diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts new file mode 100644 index 00000000000..feb723c68d0 --- /dev/null +++ b/packages/truffle-decoder/lib/wire.ts @@ -0,0 +1,264 @@ +import debugModule from "debug"; +const debug = debugModule("decoder:decoder"); + +import * as DecodeUtils from "truffle-decode-utils"; +import { Types, Values } from "truffle-decode-utils"; +import AsyncEventEmitter from "async-eventemitter"; +import Web3 from "web3"; +import { ContractObject } from "truffle-contract-schema/spec"; +import BN from "bn.js"; +import { Definition as DefinitionUtils, EVM, AstDefinition, AstReferences } from "truffle-decode-utils"; +import { BlockType, Transaction } from "web3/eth/types"; +import { EventLog, Log } from "web3/types"; +import { Provider } from "web3/providers"; +import * as Decoder from "truffle-decoder-core"; +import * as DecoderTypes from "./types"; +import * as Utils from "./utils"; + +export default class TruffleWireDecoder extends AsyncEventEmitter { + private web3: Web3; + + private network: string; + + private contracts: DecoderTypes.ContractMapping = {}; + private contractNodes: AstReferences = {}; + private contexts: DecodeUtils.Contexts.DecoderContexts = {}; + + private referenceDeclarations: AstReferences; + private userDefinedTypes: Types.TypesById; + private allocations: { + storage: Decoder.StorageAllocations; + abi: Decoder.AbiAllocations; + calldata: Decoder.CalldataAllocations; + event: Decoder.EventAllocations; + }; + + private codeCache: DecoderTypes.CodeCache = {}; + + constructor(contracts: ContractObject[], provider: Provider) { + super(); + + this.web3 = new Web3(provider); + + for(let contract of contracts) { + let node: AstDefinition = Utils.getContractNode(contract); + if(node !== undefined) { + this.contracts[node.id] = contract; + this.contractNodes[node.id] = node; + if(contract.deployedBytecode) { + const context = makeContext(contract, node); + const hash = DecodeUtils.Conversion.toHexString( + DecodeUtils.EVM.keccak256({type: "string", + value: context.binary + }) + ); + this.contexts[hash] = context; + } + if(contract.byteCode) { + const constructorContext = makeContext(contract, node, true); + const hash = DecodeUtils.Conversion.toHexString( + DecodeUtils.EVM.keccak256({type: "string", + value: constructorContext.binary + }) + ); + this.contexts[hash] = constructorContext; + } + } + } + + this.contexts = DecodeUtils.Contexts.normalizeContexts(this.contexts); + } + + public async init(): Promise { + this.contractNetwork = (await this.web3.eth.net.getId()).toString(); + + debug("init called"); + this.referenceDeclarations = Utils.getReferenceDeclarations(Object.values(this.contractNodes)); + this.userDefinedTypes = Types.definitionsToStoredTypes(this.referenceDeclarations); + + this.allocations.storage = Decoder.getStorageAllocations(this.referenceDeclarations, {[this.contractNode.id]: this.contractNode}); + this.allocations.abi = Decoder.getAbiAllocations(this.referenceDeclarations); + for(let contractNode of Object.values(this.contractNodes)) { + let id = contractNode.id; + let contract = this.contracts[id]; + this.allocations.event[id] = Decoder.getEventAllocations( + contract.abi, + this.referenceDeclarations, + contractNode.linearizedBaseContracts, + this.allocations.abi + ); + let constructorContext = Object.values(contexts).find( + ({ contractId, isConstructor }) => + contractId === id && isConstructor + ); + this.allocations.calldata[id] = Decoder.getCalldataAllocations( + contract.abi, + this.referenceDeclarations, + contractNode.linearizedBaseContracts, + allocations.abi, + constructorContext; + ); + } + debug("done with allocation"); + } + + private async getCode(address: string, block: number): Promise { + //first, set up any preliminary layers as needed + if(this.codeCache[block] === undefined) { + this.codeCache[block] = {}; + } + //now, if we have it cached, just return it + if(this.codeCache[block][address] !== undefined) { + return this.codeCache[block][address]; + } + //otherwise, get it, cache it, and return it + let code = DecodeUtils.Conversion.toBytes( + await this.web3.eth.getCode( + address, + block + ) + ); + this.codeCache[block][address] = code; + return code; + } + + public decodeTransaction(transaction: Transaction): DecodedTransaction { + const contractId = await this.getContractIdByAddress(transaction.to, transaction.blockNumber, transaction.input); + if(contractId === null) { + return { + ...transaction, + decoding: { + kind: "fallback" + } + }; + } + + const data = DecodeUtils.Conversion.toBytes(transaction.input); + const info: Decoder.EvmInfo = { + state: { + storage: {}, + calldata: data, + }, + mappingKeys: this.mappingKeys, + userDefinedTypes: this.userDefinedTypes, + allocations: this.allocations, + contexts: this.contexts + }; + const decoder = Decoder.decodeCalldata(info, contractId); + + let result = decoder.next(); + while(!result.done) { + let request = (result.value); + let response: Uint8Array; + //only code requests should occur here + if(Decoder.isCodeRequest(request)) { + response = await this.getCode(request.address, block); + } + result = decoder.next(response); + } + //at this point, result.value holds the final value + const decoding = result.value; + + return { + ...transaction, + decoding + }; + } + + public decodeLog(log: Log): DecodedEvent { + const contractId = await this.getContractIdByAddress(log.address, log.blockNumber); + if(contractId === null) { + return { + ...transaction, + decoding: { + kind: "anonymous" + } + }; + } + + const data = DecodeUtils.Conversion.toBytes(log.data); + const topics = log.topics.map(DecodeUtils.Conversion.toBytes); + const info: Decoder.EvmInfo = { + state: { + storage: {}, + eventdata: data, + eventtopics: topics + }, + mappingKeys: this.mappingKeys, + userDefinedTypes: this.userDefinedTypes, + allocations: this.allocations, + contexts: this.contexts + }; + const decoder = Decoder.decodeEvent(info, this.contractNode.id); + + let result = decoder.next(); + while(!result.done) { + let request = (result.value); + let response: Uint8Array; + //only code requests should occur here + if(Decoder.isCodeRequest(request)) { + response = await this.getCode(request.address, block); + } + result = decoder.next(response); + } + //at this point, result.value holds the final value + const decoding = result.value; + + return { + ...log, + decoding + }; + } + + public decodeLogs(logs: Log[]): DecodedEvent[] { + return logs.map(this.decodeLog); + } + + public async events(name: string | null = null, fromBlock: BlockType = "latest", toBlock: BlockType = "latest"): Promise { + const logs = await web3.eth.getPastLogs({ + fromBlock, + toBlock, + }); + + let events = this.decodeLogs(logs); + + if(name !== null) { + events = events.filter(event => + event.decoding.kind === "event" + && event.decoding.name === name + ); + } + + return events; + } + + public onEvent(name: string, callback: Function): void { + //this.web3.eth.subscribe(name); + } + + public removeEventListener(name: string): void { + } + + //normally, this function gets the code of the given address at the given block, + //and checks this against the known contexts to determine the contract ID + //however, if this fails and constructorBinary is passed in, it will then also + //attempt to determine it from that + private async getContractIdByAddress(address: string, block: number, constructorBinary?: string): Promise { + let code = DecodeUtils.Conversion.toHexString( + await this.getCode(address, block) + ); + let context: DecoderContext; + if(code.length === 0 && constructorBinary) { + let + context = DecodeUtils.Contexts.findDecoderContext(this.contexts, constructorBinary); + } + else { + context = DecodeUtils.Contexts.findDecoderContext(this.contexts, code); + } + if(context === null) { + return null; + } + return context.contractId; + } + +} From 995fadebfed2b079cb6502995fccf007b6c3ae1d Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 4 Jun 2019 23:52:19 -0400 Subject: [PATCH 02/89] Account for further merge changes --- packages/truffle-decode-utils/src/values.ts | 8 ++++---- packages/truffle-decoder-core/lib/decode/event.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/truffle-decode-utils/src/values.ts b/packages/truffle-decode-utils/src/values.ts index 0ec067d6e24..be914ad618f 100644 --- a/packages/truffle-decode-utils/src/values.ts +++ b/packages/truffle-decode-utils/src/values.ts @@ -1235,13 +1235,13 @@ export namespace Values { } } - export class IndexedReferenceTypeError extends GenericErrorDirect { + export class IndexedReferenceTypeError extends GenericError { type: Types.ReferenceType; - raw: BN; + raw: string; //should be hex string message() { - return `Cannot decode indexed parameter of reference type ${this.type.typeClass} (raw value ${this.raw.toString()})`; + return `Cannot decode indexed parameter of reference type ${this.type.typeClass} (raw value ${this.raw})`; } - constructor(referenceType: Types.ReferenceType, raw: BN) { + constructor(referenceType: Types.ReferenceType, raw: string) { super(); this.type = referenceType; this.raw = raw; diff --git a/packages/truffle-decoder-core/lib/decode/event.ts b/packages/truffle-decoder-core/lib/decode/event.ts index b64361db0d2..f13ccf796fe 100644 --- a/packages/truffle-decoder-core/lib/decode/event.ts +++ b/packages/truffle-decoder-core/lib/decode/event.ts @@ -15,10 +15,10 @@ export default function* decode(dataType: Types.Type, pointer: EventTopicPointer bytes = yield* read(pointer, state); } catch(error) { //error: Values.DecodingError - return new Values.GenericError(error.error); + return new Values.ValueErrorGeneric(error.error); } - let raw: BN = ConversionUtils.toBN(bytes); - return new Values.GenericError( + let raw: string = ConversionUtils.toHexString(bytes); + return new Values.ValueErrorGeneric( new Values.IndexedReferenceTypeError( dataType, raw From dd51ee1e0add779424024e402169a7f8cf1eb839 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 5 Jun 2019 00:01:01 -0400 Subject: [PATCH 03/89] Add "unknown" type to decoded transactions & events --- .../lib/interface/decoding.ts | 23 ++----------------- .../truffle-decoder-core/lib/types/wire.ts | 18 +++++++++++++++ packages/truffle-decoder/lib/wire.ts | 18 +++++++-------- 3 files changed, 29 insertions(+), 30 deletions(-) create mode 100644 packages/truffle-decoder-core/lib/types/wire.ts diff --git a/packages/truffle-decoder-core/lib/interface/decoding.ts b/packages/truffle-decoder-core/lib/interface/decoding.ts index df0bbbe76a2..51f3cac0590 100644 --- a/packages/truffle-decoder-core/lib/interface/decoding.ts +++ b/packages/truffle-decoder-core/lib/interface/decoding.ts @@ -15,14 +15,6 @@ export function* decodeVariable(definition: AstDefinition, pointer: Pointer.Data return yield* decode(dataType, pointer, info); //no need to pass an offset } -export interface CalldataDecoding { - kind: "function" | "constructor" | "fallback" - name: string; - arguments?: { - [name: string]: Values.Value; - } -} - export function* decodeCalldata(info: EvmInfo, contractId: number): IterableIterator { const allocations = info.allocations.calldata[contractId]; let allocation: CalldataAllocation; @@ -43,7 +35,7 @@ export function* decodeCalldata(info: EvmInfo, contractId: number): IterableIter allocation = allocations[selector]; } if(allocation === undefined) { - return { kind: "fallback" }; + return { kind: "unknown" }; } let decodedArguments = Object.assign({}, ...Object.values(allocation).map(argumentAllocation => @@ -72,17 +64,6 @@ export function* decodeCalldata(info: EvmInfo, contractId: number): IterableIter } } -export interface EventDecoding { - kind: "event" | "anonymous"; - name?: string; - arguments?: EventDecodingArgument[]; -} - -export interface EventDecodingArgument { - name?: string; - value: Values.Value; -} - export function* decodeEvent(info: EvmInfo, contractId: number): IterableIterator { const allocations = info.allocations.event[contractId]; //TODO: error-handling here @@ -95,7 +76,7 @@ export function* decodeEvent(info: EvmInfo, contractId: number): IterableIterato allocation = allocations[selector]; if(allocation === undefined) { //we can't decode - return { kind: "anonymous" }; + return { kind: "unknown" }; } let decodedArguments: EventDecodingArgument[]; for(argumentAllocation of Object.values(allocation)) { diff --git a/packages/truffle-decoder-core/lib/types/wire.ts b/packages/truffle-decoder-core/lib/types/wire.ts new file mode 100644 index 00000000000..f7a7e3780be --- /dev/null +++ b/packages/truffle-decoder-core/lib/types/wire.ts @@ -0,0 +1,18 @@ +export interface CalldataDecoding { + kind: "function" | "constructor" | "fallback" | "unknown"; + name?: string; //included only if function + arguments?: { //included only if function or constructor + [name: string]: Values.Value; + } +} + +export interface EventDecoding { + kind: "event" | "anonymous" | "unknown"; + name?: string; //included only if event + arguments?: EventDecodingArgument[]; //included only if event +} + +export interface EventDecodingArgument { + name?: string; + value: Values.Value; +} diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index feb723c68d0..9f3a1883712 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -244,17 +244,17 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { //however, if this fails and constructorBinary is passed in, it will then also //attempt to determine it from that private async getContractIdByAddress(address: string, block: number, constructorBinary?: string): Promise { - let code = DecodeUtils.Conversion.toHexString( - await this.getCode(address, block) - ); - let context: DecoderContext; - if(code.length === 0 && constructorBinary) { - let - context = DecodeUtils.Contexts.findDecoderContext(this.contexts, constructorBinary); + let code: string; + if(address !== null) { + code = DecodeUtils.Conversion.toHexString( + await this.getCode(address, block) + ); } - else { - context = DecodeUtils.Contexts.findDecoderContext(this.contexts, code); + else if(constructorBinary) { + code = constructorBinary; } + //otherwise... we have a problem + let context = DecodeUtils.Contexts.findDecoderContext(this.contexts, code); if(context === null) { return null; } From e54d8a91d3ff56a1b255fab74b3d33f6e34f702e Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 5 Jun 2019 01:44:50 -0400 Subject: [PATCH 04/89] Account for possibility of anonymous calldata parameters --- .../truffle-decoder-core/lib/allocate/abi.ts | 90 ++++++++++--------- .../lib/interface/decoding.ts | 72 +++++++++------ .../lib/types/allocation.ts | 21 ++--- .../truffle-decoder-core/lib/types/wire.ts | 10 +-- packages/truffle-decoder/lib/wire.ts | 16 ---- 5 files changed, 99 insertions(+), 110 deletions(-) diff --git a/packages/truffle-decoder-core/lib/allocate/abi.ts b/packages/truffle-decoder-core/lib/allocate/abi.ts index 09e3abeef81..594f09cd878 100644 --- a/packages/truffle-decoder-core/lib/allocate/abi.ts +++ b/packages/truffle-decoder-core/lib/allocate/abi.ts @@ -258,21 +258,24 @@ function allocateCalldata( //now: perform the allocation! const abiAllocation = allocateMembers(node, node.parameters.parameters, referenceDeclarations, abiAllocations, offset)[node.id]; //finally: transform it appropriately + let argumentsAllocation = []; + for(const member of abiAllocation.members) { + const position = rawParameters.findIndex( + parameter => parameter.id === member.definition.id + ); + argumentsAllocation[position] = { + definition: member.definition, + pointer: { + location: "calldata", + start: member.pointer.start, + length: member.pointer.length + } + }; + } return { definition: abiAllocation.definition, offset, - arguments: Object.assign({}, ...Object.entries(abiAllocation.members).map( - ([id, { definition, { start, length }}]) => ({ - [id]: { - definition, - pointer: { - location: "calldata", - start, - length - } - } - }) - )) + arguments: argumentsAllocation }; } @@ -309,40 +312,39 @@ function allocateEvent( //now: perform the allocation for the non-indexed parameters! const abiAllocation = allocateMembers(node, nonIndexed, referenceDeclarations, abiAllocations)[node.id]; //now: transform it appropriately - let eventAllocation = { + let argumentsAllocation = []; + for(const member of abiAllocation.members) { + const position = rawParameters.findIndex( + parameter => parameter.id === member.definition.id + ); + argumentsAllocation[position] = { + definition: member.definition, + pointer: { + location: "eventdata", + start: member.pointer.start, + length: member.pointer.length + } + }; + } + //finally: add in the indexed parameters... + for(let positionInIndexed = 0; positionInIndexed < indexed.length; positionInIndexed++) { + const node = indexed[positionInIndexed]; + const position = rawParameters.findIndex( + parameter => parameter.id === node.id + ); + argumentsAllocation[position] = { + definition: node, + pointer: { + location: "eventtopic", + topic: positionInIndexed + 1 //signature takes up topic 0, so we skip that, hence +1 + } + }; + } + //...and return + return { definition: abiAllocation.definition, - arguments: Object.assign({}, ...Object.entries(abiAllocation.members).map( - ([id, { definition, { start, length }}]) => ({ - [id]: { - definition, - position: rawParameters.findIndex( - parameter => parameter.id === definition.id - ), - pointer: { - location: "eventdata", - start, - length - } - } - }) - )) + arguments: argumentsAllocation }; - //finally: add in the indexed parameters and return - Object.assign(eventAllocation.arguments, - ...indexed.map( (node, position) => //yes, that's the two-argument map style! - ({[node.id]: { - definition: node, - position: rawParameters.findIndex( - parameter => parameter.id === definition.id - ), - pointer: { - location: "eventtopic", - topic: position + 1 //signature takes up topic 0, so we skip that - } - }}) - ) - ); - return eventAllocation; } //NOTE: this is for a single contract! diff --git a/packages/truffle-decoder-core/lib/interface/decoding.ts b/packages/truffle-decoder-core/lib/interface/decoding.ts index 51f3cac0590..8d3440bbbda 100644 --- a/packages/truffle-decoder-core/lib/interface/decoding.ts +++ b/packages/truffle-decoder-core/lib/interface/decoding.ts @@ -15,7 +15,13 @@ export function* decodeVariable(definition: AstDefinition, pointer: Pointer.Data return yield* decode(dataType, pointer, info); //no need to pass an offset } -export function* decodeCalldata(info: EvmInfo, contractId: number): IterableIterator { +export function* decodeCalldata(info: EvmInfo, contractId: number | null): IterableIterator { + if(contractId === null) { + //if we don't know the contract ID, we can't decode + return { + kind: "unknown"; + } + } const allocations = info.allocations.calldata[contractId]; let allocation: CalldataAllocation; let isConstructor: boolean = info.currentContext.isConstructor; @@ -35,19 +41,21 @@ export function* decodeCalldata(info: EvmInfo, contractId: number): IterableIter allocation = allocations[selector]; } if(allocation === undefined) { - return { kind: "unknown" }; + return { kind: "fallback" }; } - let decodedArguments = Object.assign({}, - ...Object.values(allocation).map(argumentAllocation => - ({ [argumentAllocation.definition.name]: - decode( - Types.definitionToType(argumentAllocation.definition), - argumentAllocation.pointer, - info, - allocation.offset //note the use of the offset for decoding pointers! - ) - }) - ) + let decodedArguments = allocation.arguments.map( + argumentAllocation => { + const value = decode( + Types.definitionToType(argumentAllocation.definition), + argumentAllocation.pointer, + info, + allocation.offset //note the use of the offset for decoding pointers! + ); + const name = argumentAllocation.definition.name; + return name === undefined + ? { value } + : { name, value }; + } ); if(isConstructor) { return { @@ -64,7 +72,13 @@ export function* decodeCalldata(info: EvmInfo, contractId: number): IterableIter } } -export function* decodeEvent(info: EvmInfo, contractId: number): IterableIterator { +export function* decodeEvent(info: EvmInfo, contractId: number | null): IterableIterator { + if(contractId === null) { + //if we don't know the contract ID, we can't decode + return { + kind: "unknown"; + } + } const allocations = info.allocations.event[contractId]; //TODO: error-handling here let rawSelector = read(info.state, @@ -76,22 +90,22 @@ export function* decodeEvent(info: EvmInfo, contractId: number): IterableIterato allocation = allocations[selector]; if(allocation === undefined) { //we can't decode - return { kind: "unknown" }; - } - let decodedArguments: EventDecodingArgument[]; - for(argumentAllocation of Object.values(allocation)) { - const value = decode( - Types.definitionToType(argumentAllocation.definition), - argumentAllocation.pointer, - info, - 0 //offset is always 0 but let's be explicit - ); - const name = argumentAllocation.definition.name; - const position = argumentAllocation.position; - decodedArguments[position] = name === undefined - ? { value } - : { name, value }; + return { kind: "anonymous" }; } + let decodedArguments = allocation.arguments.map( + argumentAllocation => { + const value = decode( + Types.definitionToType(argumentAllocation.definition), + argumentAllocation.pointer, + info, + 0 //offset is always 0 but let's be explicit + ); + const name = argumentAllocation.definition.name; + return name === undefined + ? { value } + : { name, value }; + } + ); return { kind: "event", name: allocation.definition.name, diff --git a/packages/truffle-decoder-core/lib/types/allocation.ts b/packages/truffle-decoder-core/lib/types/allocation.ts index fb163ab9dbb..93c5b3ad347 100644 --- a/packages/truffle-decoder-core/lib/types/allocation.ts +++ b/packages/truffle-decoder-core/lib/types/allocation.ts @@ -84,9 +84,11 @@ export interface MemoryMemberAllocation { //indicating the overall start position. //Also, we index by contract ID and then selector rather than by function ID //(and have a special one for the constructor) +//also, arguments are in an array by position, rather than being given by +//node ID export interface CalldataAllocations { - [id: number]: CalldataContractAllocation + [contractId: number]: CalldataContractAllocation } export interface CalldataContractAllocations { @@ -95,13 +97,9 @@ export interface CalldataContractAllocations { } export interface CalldataAllocation { - definition?: AstDefinition; //may be omitted for implciit constructor + definition?: AstDefinition; //may be omitted for implicit constructor offset: number; //measured in bytes - arguments: CalldataArgumentAllocations; -} - -export interface CalldataArgumentAllocations { - [contractId: number]: CalldataArgumentAllocation + arguments: CalldataArgumentAllocation[]; } export interface CalldataArgumentAllocation { @@ -113,8 +111,6 @@ export interface CalldataArgumentAllocation { //there's no need for an offset, and the ultimate pointer can //be either an event data pointer or an event topic pointer //(also, there's no constructor) -//also, we want to know event argument positions, so that's -//included, too export interface EventAllocations { [contractId: number]: EventContractAllocations @@ -126,15 +122,10 @@ export interface EventContractAllocations { export interface EventAllocation { definition: AstDefinition; - arguments: EventMemberAllocations; -} - -export interface EventArgumentAllocations { - [id: number]: EventArgumentAllocation + arguments: EventArgumentAllocation[]; } export interface EventArgumentAllocation { - position: number; definition: AstDefinition; pointer: Pointer.EventDataPointer | Pointer.EventTopicPointer; } diff --git a/packages/truffle-decoder-core/lib/types/wire.ts b/packages/truffle-decoder-core/lib/types/wire.ts index f7a7e3780be..ad0597c9abf 100644 --- a/packages/truffle-decoder-core/lib/types/wire.ts +++ b/packages/truffle-decoder-core/lib/types/wire.ts @@ -1,18 +1,16 @@ export interface CalldataDecoding { kind: "function" | "constructor" | "fallback" | "unknown"; name?: string; //included only if function - arguments?: { //included only if function or constructor - [name: string]: Values.Value; - } + arguments?: AbiArgument[]; //included only if function or constructor } export interface EventDecoding { kind: "event" | "anonymous" | "unknown"; name?: string; //included only if event - arguments?: EventDecodingArgument[]; //included only if event + arguments?: AbiArgument[]; //included only if event } -export interface EventDecodingArgument { - name?: string; +export interface AbiArgument { + name?: string; //included if parameter is named value: Values.Value; } diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 9f3a1883712..e5d8aa6cf32 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -124,14 +124,6 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { public decodeTransaction(transaction: Transaction): DecodedTransaction { const contractId = await this.getContractIdByAddress(transaction.to, transaction.blockNumber, transaction.input); - if(contractId === null) { - return { - ...transaction, - decoding: { - kind: "fallback" - } - }; - } const data = DecodeUtils.Conversion.toBytes(transaction.input); const info: Decoder.EvmInfo = { @@ -167,14 +159,6 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { public decodeLog(log: Log): DecodedEvent { const contractId = await this.getContractIdByAddress(log.address, log.blockNumber); - if(contractId === null) { - return { - ...transaction, - decoding: { - kind: "anonymous" - } - }; - } const data = DecodeUtils.Conversion.toBytes(log.data); const topics = log.topics.map(DecodeUtils.Conversion.toBytes); From 9d014566e6cb1e4fbd636863ad2e0b0c09b16bc2 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 6 Jun 2019 00:36:55 -0400 Subject: [PATCH 05/89] Update errors for new type system --- packages/truffle-decode-utils/src/values.ts | 1 + packages/truffle-decoder-core/lib/decode/event.ts | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/truffle-decode-utils/src/values.ts b/packages/truffle-decode-utils/src/values.ts index eeaed06c209..c0d98719c7b 100644 --- a/packages/truffle-decode-utils/src/values.ts +++ b/packages/truffle-decode-utils/src/values.ts @@ -1231,6 +1231,7 @@ export namespace Values { } } + //attempted to decode an indexed parameter of reference type error export class IndexedReferenceTypeError extends GenericError { type: Types.ReferenceType; raw: string; //should be hex string diff --git a/packages/truffle-decoder-core/lib/decode/event.ts b/packages/truffle-decoder-core/lib/decode/event.ts index f13ccf796fe..27e4a6bc0ed 100644 --- a/packages/truffle-decoder-core/lib/decode/event.ts +++ b/packages/truffle-decoder-core/lib/decode/event.ts @@ -15,15 +15,16 @@ export default function* decode(dataType: Types.Type, pointer: EventTopicPointer bytes = yield* read(pointer, state); } catch(error) { //error: Values.DecodingError - return new Values.ValueErrorGeneric(error.error); + return Values.makeGenericValueError(dataType, error.error); } let raw: string = ConversionUtils.toHexString(bytes); - return new Values.ValueErrorGeneric( + return Values.makeGenericValueError( + dataType, new Values.IndexedReferenceTypeError( dataType, raw ) - ) + ); } //otherwise, dispatch to decodeValue return yield* decodeValue(dataType, pointer, info); From d50b0b12c8761c5e5923d9c069dfbc041737262b Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 6 Jun 2019 21:46:37 -0400 Subject: [PATCH 06/89] Add class field to decoding; have contract decoder check address --- packages/truffle-decode-utils/src/contexts.ts | 12 +++++++++- .../truffle-decoder-core/lib/decode/value.ts | 11 ++++----- .../lib/interface/decoding.ts | 23 ++++++++++++++----- packages/truffle-decoder/lib/contract.ts | 21 +++++++++++++++-- packages/truffle-decoder/lib/types.ts | 12 ++++++++++ packages/truffle-decoder/lib/wire.ts | 22 +++++++++--------- 6 files changed, 74 insertions(+), 27 deletions(-) diff --git a/packages/truffle-decode-utils/src/contexts.ts b/packages/truffle-decode-utils/src/contexts.ts index 635fae09717..9b8d205ba84 100644 --- a/packages/truffle-decode-utils/src/contexts.ts +++ b/packages/truffle-decode-utils/src/contexts.ts @@ -8,6 +8,7 @@ const abiCoder = new AbiCoder(); import escapeRegExp from "lodash.escaperegexp"; import { EVM } from "./evm"; +import { Types } from "./types"; export namespace Contexts { @@ -292,7 +293,7 @@ export namespace Contexts { return false; } for(let i = 0; i < abiParameters.length; i++) { - if(!matchesAbiType(abiParameters[i], nodeParameters[i], referenceDeclarations) { + if(!matchesAbiType(abiParameters[i], nodeParameters[i], referenceDeclarations)) { return false; } } @@ -317,5 +318,14 @@ export namespace Contexts { return abiEntry.type === "constructor" && abiEntry.inputs.length === 0; } + export function contextToType(context: DecoderContext | DebuggerContext): Types.ContractType { + return { + typeClass: "contract", + id: context.contractId, + typeName: context.contractName, + contractKind: context.contractKind, + payable: context.payable + }; + } } diff --git a/packages/truffle-decoder-core/lib/decode/value.ts b/packages/truffle-decoder-core/lib/decode/value.ts index e9e9b4759b2..7a6f2d074be 100644 --- a/packages/truffle-decoder-core/lib/decode/value.ts +++ b/packages/truffle-decoder-core/lib/decode/value.ts @@ -198,13 +198,10 @@ export function* decodeContract(addressBytes: Uint8Array, info: EvmInfo): Iterab let code = DecodeUtils.Conversion.toHexString(codeBytes); let context = DecodeUtils.Contexts.findDecoderContext(info.contexts, code); if(context !== null && context.contractName !== undefined) { - return new Values.ContractValueDirectKnown(address, { - typeClass: "contract", - id: context.contractId, - typeName: context.contractName, - contractKind: context.contractKind, - payable: context.payable - }); + return new Values.ContractValueDirectKnown( + address, + DecodeUtils.Contexts.contextToType(context) + ); } else { return new Values.ContractValueDirectUnknown(address); diff --git a/packages/truffle-decoder-core/lib/interface/decoding.ts b/packages/truffle-decoder-core/lib/interface/decoding.ts index 8d3440bbbda..b5c436bd649 100644 --- a/packages/truffle-decoder-core/lib/interface/decoding.ts +++ b/packages/truffle-decoder-core/lib/interface/decoding.ts @@ -15,13 +15,14 @@ export function* decodeVariable(definition: AstDefinition, pointer: Pointer.Data return yield* decode(dataType, pointer, info); //no need to pass an offset } -export function* decodeCalldata(info: EvmInfo, contractId: number | null): IterableIterator { - if(contractId === null) { +export function* decodeCalldata(info: EvmInfo, contractType: DecodeUtils.Types.ContractType | null): IterableIterator { + if(contractType === null) { //if we don't know the contract ID, we can't decode return { kind: "unknown"; } } + const contractId = contractType.id; const allocations = info.allocations.calldata[contractId]; let allocation: CalldataAllocation; let isConstructor: boolean = info.currentContext.isConstructor; @@ -41,7 +42,10 @@ export function* decodeCalldata(info: EvmInfo, contractId: number | null): Itera allocation = allocations[selector]; } if(allocation === undefined) { - return { kind: "fallback" }; + return { + kind: "fallback", + class: contractType + }; } let decodedArguments = allocation.arguments.map( argumentAllocation => { @@ -60,25 +64,28 @@ export function* decodeCalldata(info: EvmInfo, contractId: number | null): Itera if(isConstructor) { return { kind: "constructor", + class: contractType, arguments: decodedArguments }; } else { return { kind: "function", + class: contractType name: allocation.definition.name, arguments: decodedArguments }; } } -export function* decodeEvent(info: EvmInfo, contractId: number | null): IterableIterator { - if(contractId === null) { +export function* decodeEvent(info: EvmInfo, contractType: DecodeUtils.Types.ContractType | null): IterableIterator { + if(contractType === null) { //if we don't know the contract ID, we can't decode return { kind: "unknown"; } } + const contractId = contractType.id; const allocations = info.allocations.event[contractId]; //TODO: error-handling here let rawSelector = read(info.state, @@ -90,7 +97,10 @@ export function* decodeEvent(info: EvmInfo, contractId: number | null): Iterable allocation = allocations[selector]; if(allocation === undefined) { //we can't decode - return { kind: "anonymous" }; + return { + kind: "anonymous", + class: contractType + }; } let decodedArguments = allocation.arguments.map( argumentAllocation => { @@ -108,6 +118,7 @@ export function* decodeEvent(info: EvmInfo, contractId: number | null): Iterable ); return { kind: "event", + class: contractType, name: allocation.definition.name, arguments: decodedArguments }; diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index ae020613022..39641dd3eeb 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -31,6 +31,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { private contexts: DecodeUtils.Contexts.DecoderContexts = {}; private context: DecodeUtils.Contexts.DecoderContext; private constructorContext: DecodeUtils.Contexts.DecoderContext; + private contractType: DecodeUtils.Types.ContractType; private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; @@ -107,6 +108,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.contexts = DecodeUtils.Contexts.normalizeContexts(this.contexts); this.context = this.contexts[this.contextHash]; this.constructorContext = this.contexts[this.constructorContextHash]; + this.contractType = DecodeUtils.Contexts.contextToType(this.context); } public async init(): Promise { @@ -314,6 +316,18 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { //NOTE: will only work with transactions to-or-creating this address! public async decodeTransaction(transaction: Transaction): DecodedTransaction { + if(transaction.to !== this.contractAddress) { + if(transaction.to !== null) { + throw new EventOrTransactionIsNotForThisContract(transaction.to, this.contractAddress); + } + else { + //OK, it's not *to* this address, but maybe it *created* it? + const receipt = await web3.eth.getTransactionReceipt(transaction.hash); + if(receipt.contractAddress !== this.contractAddress) { + throw new EventOrTransactionIsNotForThisContract(receipt.contractAddress, this.contractAddress); + } + } + } const block = transaction.blockNumber; const data = DecodeUtils.Conversion.toBytes(transaction.input); const info: Decoder.EvmInfo = { @@ -326,7 +340,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contexts }; - const decoder = Decoder.decodeCalldata(info, this.contractNode.id); + const decoder = Decoder.decodeCalldata(info, this.contractType); let result = decoder.next(); while(!result.done) { @@ -349,6 +363,9 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { //NOTE: will only work with logs for this address! public async decodeLog(log: Log): DecodedEvent { + if(log.address !== this.contractAddress) { + throw new EventOrTransactionIsNotForThisContract(log.address, this.contractAddress); + } const block = log.blockNumber; const data = DecodeUtils.Conversion.toBytes(log.data); const topics = log.topics.map(DecodeUtils.Conversion.toBytes); @@ -363,7 +380,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contexts }; - const decoder = Decoder.decodeEvent(info, this.contractNode.id); + const decoder = Decoder.decodeEvent(info, this.contractType); let result = decoder.next(); while(!result.done) { diff --git a/packages/truffle-decoder/lib/types.ts b/packages/truffle-decoder/lib/types.ts index 4f68adbaaa4..5a12c2f39cb 100644 --- a/packages/truffle-decoder/lib/types.ts +++ b/packages/truffle-decoder/lib/types.ts @@ -45,3 +45,15 @@ export class ContractBeingDecodedHasNoNodeError extends Error { this.name = "ContractBeingDecodedHasNoNodeError"; } } + +export class EventOrTransactionIsNotForThisContract extends Error { + thisAddress: string; + decoderAddress: string; + constructor(thisAddress: string, decoderAddress: string) { + const message = "This event or transaction's address does not match that of the contract decoder"; + super(message); + this.name = "EventOrTransactionIsNotForThisContract"; + this.thisAddress = thisAddress; + this.decoderAddress = decoderAddress; + } +} diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index e5d8aa6cf32..c80f12dec04 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -122,8 +122,8 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { return code; } - public decodeTransaction(transaction: Transaction): DecodedTransaction { - const contractId = await this.getContractIdByAddress(transaction.to, transaction.blockNumber, transaction.input); + public async decodeTransaction(transaction: Transaction): DecodedTransaction { + const contractType = await this.getContractTypeByAddress(transaction.to, transaction.blockNumber, transaction.input); const data = DecodeUtils.Conversion.toBytes(transaction.input); const info: Decoder.EvmInfo = { @@ -136,7 +136,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contexts }; - const decoder = Decoder.decodeCalldata(info, contractId); + const decoder = Decoder.decodeCalldata(info, contractType); let result = decoder.next(); while(!result.done) { @@ -157,8 +157,8 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - public decodeLog(log: Log): DecodedEvent { - const contractId = await this.getContractIdByAddress(log.address, log.blockNumber); + public async decodeLog(log: Log): DecodedEvent { + const contractType = await this.getContractTypeByAddress(log.address, log.blockNumber); const data = DecodeUtils.Conversion.toBytes(log.data); const topics = log.topics.map(DecodeUtils.Conversion.toBytes); @@ -173,7 +173,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contexts }; - const decoder = Decoder.decodeEvent(info, this.contractNode.id); + const decoder = Decoder.decodeEvent(info, contractType); let result = decoder.next(); while(!result.done) { @@ -194,8 +194,8 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - public decodeLogs(logs: Log[]): DecodedEvent[] { - return logs.map(this.decodeLog); + public async decodeLogs(logs: Log[]): DecodedEvent[] { + return await Promise.all(logs.map(this.decodeLog)); } public async events(name: string | null = null, fromBlock: BlockType = "latest", toBlock: BlockType = "latest"): Promise { @@ -224,10 +224,10 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { } //normally, this function gets the code of the given address at the given block, - //and checks this against the known contexts to determine the contract ID + //and checks this against the known contexts to determine the contract type //however, if this fails and constructorBinary is passed in, it will then also //attempt to determine it from that - private async getContractIdByAddress(address: string, block: number, constructorBinary?: string): Promise { + private async getContractTypeByAddress(address: string, block: number, constructorBinary?: string): Promise { let code: string; if(address !== null) { code = DecodeUtils.Conversion.toHexString( @@ -242,7 +242,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { if(context === null) { return null; } - return context.contractId; + return DecodeUtils.Contexts.contextToType(context); } } From a63757035952ae5b3b85ff0a9d24006319df7898 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Fri, 7 Jun 2019 04:22:02 -0400 Subject: [PATCH 07/89] Update types --- packages/truffle-decoder-core/lib/decode/event.ts | 6 +++--- packages/truffle-decoder-core/lib/interface/decoding.ts | 6 +++--- packages/truffle-decoder-core/lib/types/wire.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/truffle-decoder-core/lib/decode/event.ts b/packages/truffle-decoder-core/lib/decode/event.ts index 27e4a6bc0ed..b11c59e4067 100644 --- a/packages/truffle-decoder-core/lib/decode/event.ts +++ b/packages/truffle-decoder-core/lib/decode/event.ts @@ -7,7 +7,7 @@ import { EventTopicPointer } from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; -export default function* decode(dataType: Types.Type, pointer: EventTopicPointer, info: EvmInfo): IterableIterator { +export default function* decode(dataType: Types.Type, pointer: EventTopicPointer, info: EvmInfo): IterableIterator { if(Types.isReferenceType(dataType)) { //we cannot decode reference types "stored" in topics; we have to just return an error let bytes: Uint8Array; @@ -15,10 +15,10 @@ export default function* decode(dataType: Types.Type, pointer: EventTopicPointer bytes = yield* read(pointer, state); } catch(error) { //error: Values.DecodingError - return Values.makeGenericValueError(dataType, error.error); + return Values.makeGenericErrorResult(dataType, error.error); } let raw: string = ConversionUtils.toHexString(bytes); - return Values.makeGenericValueError( + return Values.makeGenericErrorResult( dataType, new Values.IndexedReferenceTypeError( dataType, diff --git a/packages/truffle-decoder-core/lib/interface/decoding.ts b/packages/truffle-decoder-core/lib/interface/decoding.ts index b5c436bd649..ecc2156e379 100644 --- a/packages/truffle-decoder-core/lib/interface/decoding.ts +++ b/packages/truffle-decoder-core/lib/interface/decoding.ts @@ -9,13 +9,13 @@ import { DecoderRequest, GeneratorJunk } from "../types/request"; import { CalldataAllocation, EventAllocation } from "../types/allocation"; import decode from "../decode"; -export function* decodeVariable(definition: AstDefinition, pointer: Pointer.DataPointer, info: EvmInfo): IterableIterator { +export function* decodeVariable(definition: AstDefinition, pointer: Pointer.DataPointer, info: EvmInfo): IterableIterator { let dataType = Types.definitionToType(definition); debug("definition %O", definition); return yield* decode(dataType, pointer, info); //no need to pass an offset } -export function* decodeCalldata(info: EvmInfo, contractType: DecodeUtils.Types.ContractType | null): IterableIterator { +export function* decodeCalldata(info: EvmInfo, contractType: DecodeUtils.Types.ContractType | null): IterableIterator { if(contractType === null) { //if we don't know the contract ID, we can't decode return { @@ -78,7 +78,7 @@ export function* decodeCalldata(info: EvmInfo, contractType: DecodeUtils.Types.C } } -export function* decodeEvent(info: EvmInfo, contractType: DecodeUtils.Types.ContractType | null): IterableIterator { +export function* decodeEvent(info: EvmInfo, contractType: DecodeUtils.Types.ContractType | null): IterableIterator { if(contractType === null) { //if we don't know the contract ID, we can't decode return { diff --git a/packages/truffle-decoder-core/lib/types/wire.ts b/packages/truffle-decoder-core/lib/types/wire.ts index ad0597c9abf..57df4005d68 100644 --- a/packages/truffle-decoder-core/lib/types/wire.ts +++ b/packages/truffle-decoder-core/lib/types/wire.ts @@ -12,5 +12,5 @@ export interface EventDecoding { export interface AbiArgument { name?: string; //included if parameter is named - value: Values.Value; + value: Values.Result; } From d2845d36785f555f8544c11f467e705c0ec36ec8 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 12 Jun 2019 16:38:35 -0400 Subject: [PATCH 08/89] Make use of functionKind shim (+ correction to name check) --- packages/truffle-decode-utils/src/contexts.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/truffle-decode-utils/src/contexts.ts b/packages/truffle-decode-utils/src/contexts.ts index 9b8d205ba84..68c5a0b6973 100644 --- a/packages/truffle-decode-utils/src/contexts.ts +++ b/packages/truffle-decode-utils/src/contexts.ts @@ -262,29 +262,33 @@ export namespace Contexts { } export function matchesAbi(abiEntry: abiItem, node: AstDefinition, referenceDeclarations: AstReferences): boolean { - //first: does the basic type match? + //first: does the basic name and type match? switch(node.nodeType) { case "FunctionDefinition": if(node.visibility !== "external" && node.visibility !== "public") { return false; } - if(abiEntry.type !== node.kind) { + if(abiEntry.type !== DefinitionUtils.functionKind(node)) { return false; } + if(DefinitionUtils.functionKind(node) === "function") { + if(node.name !== abiEntry.name) { + return false; + } + } break; case "EventDefinition": if(abiEntry.type !== "event") { return false; } + if(node.name !== abiEntry.name) { + return false; + } break; default: return false; } - //if we've made it this far, the next question is, does the name match? - if(node.name !== abiEntry.name) { - return false; - } - //finally, we've got to start checking input types (we don't check output types) + //if so, we've got to start checking input types (we don't check output types) return matchesAbiParameters(abiEntry.inputs, node.parameters.parameters, referenceDeclarations); } From d039eef404d42e19fb589a0e8ec94acdf8928553 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 12 Jun 2019 23:49:42 -0400 Subject: [PATCH 09/89] Fix default constructor allocation; regularize calldata allocation format --- packages/truffle-decode-utils/src/contexts.ts | 4 - .../truffle-decoder-core/lib/allocate/abi.ts | 134 ++++++++++-------- .../lib/interface/decoding.ts | 2 +- .../lib/types/allocation.ts | 6 +- 4 files changed, 80 insertions(+), 66 deletions(-) diff --git a/packages/truffle-decode-utils/src/contexts.ts b/packages/truffle-decode-utils/src/contexts.ts index 68c5a0b6973..02d6834fe9d 100644 --- a/packages/truffle-decode-utils/src/contexts.ts +++ b/packages/truffle-decode-utils/src/contexts.ts @@ -318,10 +318,6 @@ export namespace Contexts { } } - function isNoArgumentConstructor(abiEntry: AbiItem) { - return abiEntry.type === "constructor" && abiEntry.inputs.length === 0; - } - export function contextToType(context: DecoderContext | DebuggerContext): Types.ContractType { return { typeClass: "contract", diff --git a/packages/truffle-decoder-core/lib/allocate/abi.ts b/packages/truffle-decoder-core/lib/allocate/abi.ts index 8fabb7ca7d3..71f5c4e170b 100644 --- a/packages/truffle-decoder-core/lib/allocate/abi.ts +++ b/packages/truffle-decoder-core/lib/allocate/abi.ts @@ -213,47 +213,42 @@ function allocateCalldata( constructorContext?: DecodeUtils.Contexts.DecoderContext ): Allocations.CalldataAllocation { //first: determine the corresponding function node + //(simultaneously: determine the offset) let node: AstDefinition; - //base contracts are listed from most derived to most base, so we - //have to reverse before processing, but reverse() is in place, so we - //clone with slice first - let linearizedBaseContractsFromBase: number[] = contract.linearizedBaseContracts.slice().reverse(); - for(const contractId of linearizedBaseContractsFromBase) { - node = referenceDeclarations[contractId].nodes.find( - functionNode => DecodeUtils.Contexts.matchesAbi( - abiEntry, functionNode, referenceDeclarations - ) - ); - if(node !== undefined) { - break; - } - } - if(node === undefined) { - //before we declare this an error-case... maybe it's just an implicit constructor? - //and we can return an empty allocation? - if(isNoArgumentConstructor(abiEntry)) { - let rawLength = constructorContext.binary.length; - offset = (rawLength - 2)/2; //number of bytes in 0x-prefixed bytestring - return { - offset, - arguments: {} - }; - } - else { - //TODO: error-handling - } - } - //now: determine the offset let offset: number; switch(abiEntry.type) { - case "function": - offset = DecodeUtils.EVM.SELECTOR_SIZE; - break; case "constructor": let rawLength = constructorContext.binary.length; offset = (rawLength - 2)/2; //number of bytes in 0x-prefixed bytestring + //for a constructor, we only want to search the particular contract, which + //is the last (most derived) contract in the linearized base contracts + let contractId = linearizedBaseContracts[linearizedBaseContracts.length - 1]; + let contractNode = referenceDeclarations[contractId]; + node = contractNode.nodes.find( + functionNode => DecodeUtils.Contexts.matchesAbi( + abiEntry, functionNode, referenceDeclarations + ) + ); + if(node === undefined) { + //TODO: error case + } + } + break; + case "function": + offset = DecodeUtils.EVM.SELECTOR_SIZE; + //search through base contracts, from most derived (right) to most base (left) + node = linearizedBaseContracts.reduceRight( + (foundNode, contractId) => foundNode || referenceDeclarations[contractId].nodes.find( + functionNode => DecodeUtils.Contexts.matchesAbi( + abiEntry, functionNode, referenceDeclarations + ) + ), + undefined + ); + if(node === undefined) { + //TODO: error case + } break; - //we'll ignore event and fallback, which are not applicable here } //now: perform the allocation! const abiAllocation = allocateMembers(node, node.parameters.parameters, referenceDeclarations, abiAllocations, offset)[node.id]; @@ -289,22 +284,16 @@ function allocateEvent( linearizedBaseContracts: number[], abiAllocations: AbiAllocations ): Allocations.EventAllocation { - //first: determine the corresponding function node - let node: AstDefinition; - //base contracts are listed from most derived to most base, so we - //have to reverse before processing, but reverse() is in place, so we - //clone with slice first - const linearizedBaseContractsFromBase: number[] = contract.linearizedBaseContracts.slice().reverse(); - for(const contractId of linearizedBaseContractsFromBase) { - node = referenceDeclarations[contractId].nodes.find( - functionNode => DecodeUtils.Contexts.matchesAbi( - abiEntry, functionNode, referenceDeclarations + //first: determine the corresponding event node + //search through base contracts, from most derived (right) to most base (left) + const node: AstDefinition = linearizedBaseContracts.reduceRight( + (foundNode, contractId) => foundNode || referenceDeclarations[contractId].nodes.find( + eventNode => DecodeUtils.Contexts.matchesAbi( + abiEntry, eventNode, referenceDeclarations ) - ); - if(node !== undefined) { - break; - } - } + ), + undefined + ); //now: split the list of parameters into indexed and non-indexed //but first attach positions so we can reconstruct the list later const rawParameters = node.parameters.parameters; @@ -356,16 +345,43 @@ export function getCalldataAllocations( abiAllocations: Allocations.AbiAllocations, constructorContext: DecoderContext ): Allocations.CalldataContractAllocations { - return Object.assign({}, ...abi - .filter(abiEntry => abiEntry.type === "function" || abiEntry.type === "constructor") - .map(abiEntry => - abiEntry.type === "constructor" - ? { constructorAllocation: allocateCalldata(abiEntry, referenceDeclarations, linearizedBaseContracts, abiAllocations, constructorContext) } - : { [abiCoder.encodeFunctionSignature(abiEntry)] : - allocateCalldata(abiEntry, referenceDeclarations, linearizedBaseContracts, abiAllocations) - }; - ) - ); + let allocations: Allocations.CalldataContractAllocations; + for(let abiEntry in abi) { + if(abiEntry.type === "constructor") { + allocations.constructorAllocation = allocateCalldata( + abiEntry, + referenceDeclarations, + linearizedBaseContracts, + abiAllocations, + constructorContext + ); + } + else if(abiEntry.type === "function") { + allocations.functionAllocations[abiCoder.encodeFunctionSignature(abiEntry)] = + allocateCalldata( + abiEntry, + referenceDeclarations, + linearizedBaseContracts, + abiAllocations, + constructorContext + ); + } + //skip over fallback and event + } + //now: did we allocate a constructor? if not, allocate a default one + if(allocations.constructorAllocation === undefined) { + allocations.constructorAllocation = defaultConstructorAllocation(constructorContext); + } + return allocations; +} + +function defaultConstructorAllocation(constructorContext: DecoderContext) { + let rawLength = constructorContext.binary.length; + let offset = (rawLength - 2)/2; //number of bytes in 0x-prefixed bytestring + return { + offset, + arguments: {} + }; } //NOTE: this is for a single contract! diff --git a/packages/truffle-decoder-core/lib/interface/decoding.ts b/packages/truffle-decoder-core/lib/interface/decoding.ts index ecc2156e379..e70044802b7 100644 --- a/packages/truffle-decoder-core/lib/interface/decoding.ts +++ b/packages/truffle-decoder-core/lib/interface/decoding.ts @@ -39,7 +39,7 @@ export function* decodeCalldata(info: EvmInfo, contractType: DecodeUtils.Types.C } ); let selector = DecodeUtils.EVM.toHexString(rawSelector); - allocation = allocations[selector]; + allocation = allocations.functionAllocations[selector]; } if(allocation === undefined) { return { diff --git a/packages/truffle-decoder-core/lib/types/allocation.ts b/packages/truffle-decoder-core/lib/types/allocation.ts index 93c5b3ad347..e2ddc2d8f82 100644 --- a/packages/truffle-decoder-core/lib/types/allocation.ts +++ b/packages/truffle-decoder-core/lib/types/allocation.ts @@ -92,8 +92,10 @@ export interface CalldataAllocations { } export interface CalldataContractAllocations { - constructorAllocation: CalldataAllocation, - [selector: string]: CalldataAllocation + constructorAllocation: CalldataAllocation; + functionAllocations: { + [selector: string]: CalldataAllocation; + }; } export interface CalldataAllocation { From e586eaa2198c378f85c1a9fcc8ccd5388436987a Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 13 Jun 2019 00:52:49 -0400 Subject: [PATCH 10/89] Handle the possibility of library events --- .../lib/interface/decoding.ts | 41 ++++++++++++------- .../truffle-decoder-core/lib/types/evm.ts | 7 ++++ .../truffle-decoder-core/lib/types/wire.ts | 3 +- packages/truffle-decoder/lib/wire.ts | 38 +++++++++++------ 4 files changed, 61 insertions(+), 28 deletions(-) diff --git a/packages/truffle-decoder-core/lib/interface/decoding.ts b/packages/truffle-decoder-core/lib/interface/decoding.ts index e70044802b7..50d9cd07179 100644 --- a/packages/truffle-decoder-core/lib/interface/decoding.ts +++ b/packages/truffle-decoder-core/lib/interface/decoding.ts @@ -15,14 +15,15 @@ export function* decodeVariable(definition: AstDefinition, pointer: Pointer.Data return yield* decode(dataType, pointer, info); //no need to pass an offset } -export function* decodeCalldata(info: EvmInfo, contractType: DecodeUtils.Types.ContractType | null): IterableIterator { - if(contractType === null) { +export function* decodeCalldata(info: EvmInfo, context: DecodeUtils.Contexts.DecoderContext | null): IterableIterator { + if(context === null) { //if we don't know the contract ID, we can't decode return { kind: "unknown"; } } - const contractId = contractType.id; + const contractId = context.contractId; + const contractType = DecodeUtils.Contexts.contextToType(context); const allocations = info.allocations.calldata[contractId]; let allocation: CalldataAllocation; let isConstructor: boolean = info.currentContext.isConstructor; @@ -78,29 +79,39 @@ export function* decodeCalldata(info: EvmInfo, contractType: DecodeUtils.Types.C } } -export function* decodeEvent(info: EvmInfo, contractType: DecodeUtils.Types.ContractType | null): IterableIterator { - if(contractType === null) { +export function* decodeEvent(info: EvmInfo, context: DecodeUtils.Contexts.DecoderContext | null): IterableIterator { + if(context === null) { //if we don't know the contract ID, we can't decode return { kind: "unknown"; } } - const contractId = contractType.id; - const allocations = info.allocations.event[contractId]; + let contractId = context.contractId; + let contractType = DecodeUtils.Contexts.contextToType(context); + let allocations = info.allocations.event[contractId]; + const libraryEventsTable = info.libraryEventsTable; //TODO: error-handling here - let rawSelector = read(info.state, + const rawSelector = read(info.state, { location: "eventdata", topic: 0 } ); - let selector = DecodeUtils.EVM.toHexString(rawSelector); - allocation = allocations[selector]; + const selector = DecodeUtils.EVM.toHexString(rawSelector); + let allocation = allocations[selector]; if(allocation === undefined) { - //we can't decode - return { - kind: "anonymous", - class: contractType - }; + //check the library events table + let libraryContext = libraryEventsTable[selector]; + if(libraryContext === undefined) { + //if that still doesn't find it, we can't decode + return { + kind: "unknown", + }; + } + //otherwise, we've found it, right? + contractId = libraryContext.contractId; + contractType = DecodeUtils.Contexts.contextToType(libraryContext); + allocations = info.allocations.event[contractId]; + allocation = allocations[selector]; } let decodedArguments = allocation.arguments.map( argumentAllocation => { diff --git a/packages/truffle-decoder-core/lib/types/evm.ts b/packages/truffle-decoder-core/lib/types/evm.ts index 6ca45de363d..6c4b0649505 100644 --- a/packages/truffle-decoder-core/lib/types/evm.ts +++ b/packages/truffle-decoder-core/lib/types/evm.ts @@ -32,6 +32,13 @@ export interface EvmInfo { contexts?: Contexts.DecoderContexts; currentContext?: Contexts.DecoderContext; internalFunctionsTable?: InternalFunctions; + libraryEventsTable?: LibraryEvents; +} + +//yes, this is basically DecoderContexts, but I want +//to keep it separate as it means something different +export interface LibraryEvents { + [selector: string]: Contexts.DecoderContext; } export interface InternalFunctions { diff --git a/packages/truffle-decoder-core/lib/types/wire.ts b/packages/truffle-decoder-core/lib/types/wire.ts index 57df4005d68..5ac68becc99 100644 --- a/packages/truffle-decoder-core/lib/types/wire.ts +++ b/packages/truffle-decoder-core/lib/types/wire.ts @@ -1,11 +1,12 @@ export interface CalldataDecoding { kind: "function" | "constructor" | "fallback" | "unknown"; + class?: DecodeUtils.Types.ContractType; //included only if not unknown name?: string; //included only if function arguments?: AbiArgument[]; //included only if function or constructor } export interface EventDecoding { - kind: "event" | "anonymous" | "unknown"; + kind: "event" | "unknown"; name?: string; //included only if event arguments?: AbiArgument[]; //included only if event } diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index c80f12dec04..17409e9f0d9 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -33,6 +33,9 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { event: Decoder.EventAllocations; }; + //maps libraries' event selectors to the corresponding library + private libraryEventsTable: Decoder.LibraryEvents; + private codeCache: DecoderTypes.CodeCache = {}; constructor(contracts: ContractObject[], provider: Provider) { @@ -100,6 +103,21 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { ); } debug("done with allocation"); + + //set up library event mapping + this.libraryEventsTable = Object.assign({}, ...Object.entries(this.allocations.event).map( + ([id, allocation]) => { + let context = this.contexts.find( + context => context.contractId === id && !context.isConstructor + ); + if(context.contractKind !== "library") { + return {}; + } + return Object.assign({}, ...Object.keys(allocation).map( + selector => ({[selector]: context}) + )); + } + ); } private async getCode(address: string, block: number): Promise { @@ -123,7 +141,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { } public async decodeTransaction(transaction: Transaction): DecodedTransaction { - const contractType = await this.getContractTypeByAddress(transaction.to, transaction.blockNumber, transaction.input); + const context = await this.getContextByAddress(transaction.to, transaction.blockNumber, transaction.input); const data = DecodeUtils.Conversion.toBytes(transaction.input); const info: Decoder.EvmInfo = { @@ -136,7 +154,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contexts }; - const decoder = Decoder.decodeCalldata(info, contractType); + const decoder = Decoder.decodeCalldata(info, context); let result = decoder.next(); while(!result.done) { @@ -158,7 +176,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { } public async decodeLog(log: Log): DecodedEvent { - const contractType = await this.getContractTypeByAddress(log.address, log.blockNumber); + const context = await this.getContextByAddress(log.address, log.blockNumber); const data = DecodeUtils.Conversion.toBytes(log.data); const topics = log.topics.map(DecodeUtils.Conversion.toBytes); @@ -171,9 +189,10 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { mappingKeys: this.mappingKeys, userDefinedTypes: this.userDefinedTypes, allocations: this.allocations, - contexts: this.contexts + contexts: this.contexts, + libraryEventsTable: this.libraryEventsTable }; - const decoder = Decoder.decodeEvent(info, contractType); + const decoder = Decoder.decodeEvent(info, context); let result = decoder.next(); while(!result.done) { @@ -227,7 +246,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { //and checks this against the known contexts to determine the contract type //however, if this fails and constructorBinary is passed in, it will then also //attempt to determine it from that - private async getContractTypeByAddress(address: string, block: number, constructorBinary?: string): Promise { + private async getContextByAddress(address: string, block: number, constructorBinary?: string): Promise { let code: string; if(address !== null) { code = DecodeUtils.Conversion.toHexString( @@ -238,11 +257,6 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { code = constructorBinary; } //otherwise... we have a problem - let context = DecodeUtils.Contexts.findDecoderContext(this.contexts, code); - if(context === null) { - return null; - } - return DecodeUtils.Contexts.contextToType(context); + return DecodeUtils.Contexts.findDecoderContext(this.contexts, code); } - } From ba1b5af7c7e6e604564cd64f71b5ef5047305a7f Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 13 Jun 2019 02:30:32 -0400 Subject: [PATCH 11/89] Store event allocations by selector only; update contract.ts --- .../truffle-decoder-core/lib/allocate/abi.ts | 28 ++++++++----- .../lib/interface/decoding.ts | 36 +++++----------- .../lib/types/allocation.ts | 15 +++---- .../truffle-decoder-core/lib/types/evm.ts | 7 ---- .../truffle-decoder-core/lib/types/wire.ts | 1 + packages/truffle-decoder/lib/contract.ts | 32 ++++++++------ packages/truffle-decoder/lib/wire.ts | 42 ++++++------------- 7 files changed, 68 insertions(+), 93 deletions(-) diff --git a/packages/truffle-decoder-core/lib/allocate/abi.ts b/packages/truffle-decoder-core/lib/allocate/abi.ts index 71f5c4e170b..a848e6523ca 100644 --- a/packages/truffle-decoder-core/lib/allocate/abi.ts +++ b/packages/truffle-decoder-core/lib/allocate/abi.ts @@ -207,11 +207,12 @@ export function isTypeDynamic(dataType: DecodeUtils.Types.Type, allocations: All //TODO: check accesses to abi & node members function allocateCalldata( abiEntry: AbiItem, - linearizedBaseContracts: number[], + contractId: number, referenceDeclarations: AstReferences, abiAllocations: AbiAllocations, constructorContext?: DecodeUtils.Contexts.DecoderContext ): Allocations.CalldataAllocation { + const linearizedBaseContracts = referenceDeclarations[contractId].linearizedBaseContracts; //first: determine the corresponding function node //(simultaneously: determine the offset) let node: AstDefinition; @@ -221,8 +222,6 @@ function allocateCalldata( let rawLength = constructorContext.binary.length; offset = (rawLength - 2)/2; //number of bytes in 0x-prefixed bytestring //for a constructor, we only want to search the particular contract, which - //is the last (most derived) contract in the linearized base contracts - let contractId = linearizedBaseContracts[linearizedBaseContracts.length - 1]; let contractNode = referenceDeclarations[contractId]; node = contractNode.nodes.find( functionNode => DecodeUtils.Contexts.matchesAbi( @@ -238,7 +237,7 @@ function allocateCalldata( offset = DecodeUtils.EVM.SELECTOR_SIZE; //search through base contracts, from most derived (right) to most base (left) node = linearizedBaseContracts.reduceRight( - (foundNode, contractId) => foundNode || referenceDeclarations[contractId].nodes.find( + (foundNode, baseContractId) => foundNode || referenceDeclarations[baseContractId].nodes.find( functionNode => DecodeUtils.Contexts.matchesAbi( abiEntry, functionNode, referenceDeclarations ) @@ -281,13 +280,14 @@ function allocateCalldata( function allocateEvent( abiEntry: AbiItem, referenceDeclarations: AstReferences, - linearizedBaseContracts: number[], + contractId: number, abiAllocations: AbiAllocations ): Allocations.EventAllocation { + const linearizedBaseContracts = referenceDeclarations[contractId].linearizedBaseContracts; //first: determine the corresponding event node //search through base contracts, from most derived (right) to most base (left) const node: AstDefinition = linearizedBaseContracts.reduceRight( - (foundNode, contractId) => foundNode || referenceDeclarations[contractId].nodes.find( + (foundNode, baseContractId) => foundNode || referenceDeclarations[baseContractId].nodes.find( eventNode => DecodeUtils.Contexts.matchesAbi( abiEntry, eventNode, referenceDeclarations ) @@ -332,6 +332,7 @@ function allocateEvent( //...and return return { definition: abiAllocation.definition, + contractId, arguments: argumentsAllocation }; } @@ -340,8 +341,8 @@ function allocateEvent( //run multiple times to handle multiple contracts export function getCalldataAllocations( abi: Abi, + contractId: number, referenceDeclarations: AstReferences, - linearizedBaseContracts: number[], abiAllocations: Allocations.AbiAllocations, constructorContext: DecoderContext ): Allocations.CalldataContractAllocations { @@ -350,8 +351,8 @@ export function getCalldataAllocations( if(abiEntry.type === "constructor") { allocations.constructorAllocation = allocateCalldata( abiEntry, + contractId, referenceDeclarations, - linearizedBaseContracts, abiAllocations, constructorContext ); @@ -360,8 +361,8 @@ export function getCalldataAllocations( allocations.functionAllocations[abiCoder.encodeFunctionSignature(abiEntry)] = allocateCalldata( abiEntry, + contractId, referenceDeclarations, - linearizedBaseContracts, abiAllocations, constructorContext ); @@ -386,12 +387,17 @@ function defaultConstructorAllocation(constructorContext: DecoderContext) { //NOTE: this is for a single contract! //run multiple times to handle multiple contracts -export function getEventAllocations(abi: Abi, referenceDeclarations: AstReferences, linearizedBaseContracts: number[], abiAllocations: Allocations.AbiAllocations): Allocations.EventContractAllocations { +export function getEventAllocations( + abi: Abi, + contractId: number, + referenceDeclarations: AstReferences, + abiAllocations: Allocations.AbiAllocations +): Allocations.EventAllocations { return Object.assign({}, ...abi .filter(abiEntry => abiEntry.type === "event") .map(abiEntry => ({ [abiCoder.encodeEventSignature(abiEntry)] : - allocateEvent(abiEntry, referenceDeclarations, linearizedBaseContracts, abiAllocations) + allocateEvent(abiEntry, contractId, referenceDeclarations, abiAllocations) }) ) ); diff --git a/packages/truffle-decoder-core/lib/interface/decoding.ts b/packages/truffle-decoder-core/lib/interface/decoding.ts index 50d9cd07179..24da14f7f7c 100644 --- a/packages/truffle-decoder-core/lib/interface/decoding.ts +++ b/packages/truffle-decoder-core/lib/interface/decoding.ts @@ -79,18 +79,8 @@ export function* decodeCalldata(info: EvmInfo, context: DecodeUtils.Contexts.Dec } } -export function* decodeEvent(info: EvmInfo, context: DecodeUtils.Contexts.DecoderContext | null): IterableIterator { - if(context === null) { - //if we don't know the contract ID, we can't decode - return { - kind: "unknown"; - } - } - let contractId = context.contractId; - let contractType = DecodeUtils.Contexts.contextToType(context); - let allocations = info.allocations.event[contractId]; - const libraryEventsTable = info.libraryEventsTable; - //TODO: error-handling here +export function* decodeEvent(info: EvmInfo): IterableIterator { + let allocations = info.allocations.event; const rawSelector = read(info.state, { location: "eventdata", topic: 0 @@ -99,20 +89,16 @@ export function* decodeEvent(info: EvmInfo, context: DecodeUtils.Contexts.Decode const selector = DecodeUtils.EVM.toHexString(rawSelector); let allocation = allocations[selector]; if(allocation === undefined) { - //check the library events table - let libraryContext = libraryEventsTable[selector]; - if(libraryContext === undefined) { - //if that still doesn't find it, we can't decode - return { - kind: "unknown", - }; - } - //otherwise, we've found it, right? - contractId = libraryContext.contractId; - contractType = DecodeUtils.Contexts.contextToType(libraryContext); - allocations = info.allocations.event[contractId]; - allocation = allocations[selector]; + //we can't decode + return { + kind: "unknown", + }; } + let context = info.contexts.find( + context => context.contractId === allocation.contractId + && !context.isConstructor + ); + let contractType = DecodeUtils.Contexts.contextToType(context); let decodedArguments = allocation.arguments.map( argumentAllocation => { const value = decode( diff --git a/packages/truffle-decoder-core/lib/types/allocation.ts b/packages/truffle-decoder-core/lib/types/allocation.ts index e2ddc2d8f82..a7745c019b3 100644 --- a/packages/truffle-decoder-core/lib/types/allocation.ts +++ b/packages/truffle-decoder-core/lib/types/allocation.ts @@ -109,21 +109,18 @@ export interface CalldataArgumentAllocation { pointer: Pointer.CalldataPointer; } -//finally we have events. these work like calldata, except that -//there's no need for an offset, and the ultimate pointer can -//be either an event data pointer or an event topic pointer -//(also, there's no constructor) +//finally we have events. these work like calldata, except that there's no +//need for an offset, the ultimate pointer can be either an event data pointer +//or an event topic pointer, and, they're given *only* by selector -- not by +//contract ID! Instead the contract ID is included in the allocation export interface EventAllocations { - [contractId: number]: EventContractAllocations -} - -export interface EventContractAllocations { - [selector: string]: EventAllocation + [selector: string]: EventAllocations } export interface EventAllocation { definition: AstDefinition; + contractId: number; arguments: EventArgumentAllocation[]; } diff --git a/packages/truffle-decoder-core/lib/types/evm.ts b/packages/truffle-decoder-core/lib/types/evm.ts index 6c4b0649505..6ca45de363d 100644 --- a/packages/truffle-decoder-core/lib/types/evm.ts +++ b/packages/truffle-decoder-core/lib/types/evm.ts @@ -32,13 +32,6 @@ export interface EvmInfo { contexts?: Contexts.DecoderContexts; currentContext?: Contexts.DecoderContext; internalFunctionsTable?: InternalFunctions; - libraryEventsTable?: LibraryEvents; -} - -//yes, this is basically DecoderContexts, but I want -//to keep it separate as it means something different -export interface LibraryEvents { - [selector: string]: Contexts.DecoderContext; } export interface InternalFunctions { diff --git a/packages/truffle-decoder-core/lib/types/wire.ts b/packages/truffle-decoder-core/lib/types/wire.ts index 5ac68becc99..a56aafedb45 100644 --- a/packages/truffle-decoder-core/lib/types/wire.ts +++ b/packages/truffle-decoder-core/lib/types/wire.ts @@ -7,6 +7,7 @@ export interface CalldataDecoding { export interface EventDecoding { kind: "event" | "unknown"; + class?: DecodeUtils.Types.ContractType; //included only if event name?: string; //included only if event arguments?: AbiArgument[]; //included only if event } diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 4e952df740d..8c253254081 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -31,7 +31,6 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { private contexts: DecodeUtils.Contexts.DecoderContexts = {}; private context: DecodeUtils.Contexts.DecoderContext; private constructorContext: DecodeUtils.Contexts.DecoderContext; - private contractType: DecodeUtils.Types.ContractType; private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; @@ -108,7 +107,6 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.contexts = DecodeUtils.Contexts.normalizeContexts(this.contexts); this.context = this.contexts[this.contextHash]; this.constructorContext = this.contexts[this.constructorContextHash]; - this.contractType = DecodeUtils.Contexts.contextToType(this.context); } public async init(): Promise { @@ -123,19 +121,29 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.allocations.storage = Decoder.getStorageAllocations(this.referenceDeclarations, {[this.contractNode.id]: this.contractNode}); this.allocations.abi = Decoder.getAbiAllocations(this.referenceDeclarations); - this.allocations.event[this.contractNode.id] = Decoder.getEventAllocations( - this.contract.abi, - this.referenceDeclarations, - this.contractNode.linearizedBaseContracts, - this.allocations.abi - ); this.allocations.calldata[this.contractNode.id] = Decoder.getCalldataAllocations( this.contract.abi, + this.contractNode.id, this.referenceDeclarations, - this.contractNode.linearizedBaseContracts, this.allocations.abi, - this.constructorContext; + this.constructorContext ); + this.allocations.event = {}; + for(let id in this.contractNodes) { + if(this.contractNodes[id].contractKind !== "library" + && id !== this.contractNode.id) { + continue; //only allocate for this contract and libraries + } + let contract = this.contracts[id]; + Object.assign(this.allocations.event, + Decoder.getEventAllocations( + contract.abi + id, + this.referenceDeclarations, + this.allocations.abi + ) + ); + } debug("done with allocation"); this.stateVariableReferences = this.storageAllocations[this.contractNode.id].members; debug("stateVariableReferences %O", this.stateVariableReferences); @@ -340,7 +348,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contexts }; - const decoder = Decoder.decodeCalldata(info, this.contractType); + const decoder = Decoder.decodeCalldata(info, this.context); let result = decoder.next(); while(!result.done) { @@ -380,7 +388,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contexts }; - const decoder = Decoder.decodeEvent(info, this.contractType); + const decoder = Decoder.decodeEvent(info); let result = decoder.next(); while(!result.done) { diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 17409e9f0d9..963a16463e1 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -33,9 +33,6 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { event: Decoder.EventAllocations; }; - //maps libraries' event selectors to the corresponding library - private libraryEventsTable: Decoder.LibraryEvents; - private codeCache: DecoderTypes.CodeCache = {}; constructor(contracts: ContractObject[], provider: Provider) { @@ -81,14 +78,17 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { this.allocations.storage = Decoder.getStorageAllocations(this.referenceDeclarations, {[this.contractNode.id]: this.contractNode}); this.allocations.abi = Decoder.getAbiAllocations(this.referenceDeclarations); + this.allocations.event = {}; for(let contractNode of Object.values(this.contractNodes)) { let id = contractNode.id; let contract = this.contracts[id]; - this.allocations.event[id] = Decoder.getEventAllocations( - contract.abi, - this.referenceDeclarations, - contractNode.linearizedBaseContracts, - this.allocations.abi + Object.assign(this.allocations.event, + Decoder.getEventAllocations( + contract.abi, + id, + this.referenceDeclarations, + this.allocations.abi + ) ); let constructorContext = Object.values(contexts).find( ({ contractId, isConstructor }) => @@ -96,28 +96,13 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { ); this.allocations.calldata[id] = Decoder.getCalldataAllocations( contract.abi, + id, this.referenceDeclarations, - contractNode.linearizedBaseContracts, - allocations.abi, - constructorContext; + this.allocations.abi, + constructorContext ); } debug("done with allocation"); - - //set up library event mapping - this.libraryEventsTable = Object.assign({}, ...Object.entries(this.allocations.event).map( - ([id, allocation]) => { - let context = this.contexts.find( - context => context.contractId === id && !context.isConstructor - ); - if(context.contractKind !== "library") { - return {}; - } - return Object.assign({}, ...Object.keys(allocation).map( - selector => ({[selector]: context}) - )); - } - ); } private async getCode(address: string, block: number): Promise { @@ -176,8 +161,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { } public async decodeLog(log: Log): DecodedEvent { - const context = await this.getContextByAddress(log.address, log.blockNumber); - + const block = log.blockNumber; const data = DecodeUtils.Conversion.toBytes(log.data); const topics = log.topics.map(DecodeUtils.Conversion.toBytes); const info: Decoder.EvmInfo = { @@ -192,7 +176,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { contexts: this.contexts, libraryEventsTable: this.libraryEventsTable }; - const decoder = Decoder.decodeEvent(info, context); + const decoder = Decoder.decodeEvent(info); let result = decoder.next(); while(!result.done) { From 06e5ad6ddfc1a9cde4ba45d2fc3020f9e044f393 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Sat, 15 Jun 2019 01:12:29 -0400 Subject: [PATCH 12/89] Pass along compiler info in wire decoder --- packages/truffle-decode-utils/src/contexts.ts | 4 +++ .../lib/interface/decoding.ts | 3 +- .../truffle-decoder-core/lib/types/evm.ts | 2 +- packages/truffle-decoder/lib/contract.ts | 12 +++++-- packages/truffle-decoder/lib/utils.ts | 24 ------------- packages/truffle-decoder/lib/wire.ts | 36 ++++++++++++++++--- 6 files changed, 47 insertions(+), 34 deletions(-) diff --git a/packages/truffle-decode-utils/src/contexts.ts b/packages/truffle-decode-utils/src/contexts.ts index 92fd9f70977..2a968a304c6 100644 --- a/packages/truffle-decode-utils/src/contexts.ts +++ b/packages/truffle-decode-utils/src/contexts.ts @@ -20,6 +20,10 @@ export namespace Contexts { [context: string]: DecoderContext; } + export interface DecoderContextsById { + [id: number]: DecoderContext; + } + export interface DebuggerContexts { [context: string]: DebuggerContext; } diff --git a/packages/truffle-decoder-core/lib/interface/decoding.ts b/packages/truffle-decoder-core/lib/interface/decoding.ts index 2d0eb625bad..c335745d9bb 100644 --- a/packages/truffle-decoder-core/lib/interface/decoding.ts +++ b/packages/truffle-decoder-core/lib/interface/decoding.ts @@ -101,13 +101,14 @@ export function* decodeEvent(info: EvmInfo): IterableIterator context.contractId === allocation.contractId && !context.isConstructor ); + let newInfo = { ...info, currentContext: context }; let contractType = DecodeUtils.Contexts.contextToType(context); let decodedArguments = allocation.arguments.map( argumentAllocation => { const value = decode( Types.definitionToType(argumentAllocation.definition, compiler), argumentAllocation.pointer, - info, + newInfo, 0 //offset is always 0 but let's be explicit ); const name = argumentAllocation.definition.name; diff --git a/packages/truffle-decoder-core/lib/types/evm.ts b/packages/truffle-decoder-core/lib/types/evm.ts index 6ca45de363d..36a782be771 100644 --- a/packages/truffle-decoder-core/lib/types/evm.ts +++ b/packages/truffle-decoder-core/lib/types/evm.ts @@ -29,7 +29,7 @@ export interface EvmInfo { calldata?: Allocations.CalldataAllocations; event?: Allocations.EventAllocations; } - contexts?: Contexts.DecoderContexts; + contexts?: Contexts.DecoderContextsById; currentContext?: Contexts.DecoderContext; internalFunctionsTable?: InternalFunctions; } diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index b71573d36b7..5337120516a 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -29,6 +29,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { private contracts: DecoderTypes.ContractMapping = {}; private contractNodes: AstReferences = {}; private contexts: DecodeUtils.Contexts.DecoderContexts = {}; + private contextsById: DecodeUtils.Contexts.DecoderContextsById = {}; //deployed contexts only private context: DecodeUtils.Contexts.DecoderContext; private constructorContext: DecodeUtils.Contexts.DecoderContext; @@ -107,6 +108,11 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.contexts = DecodeUtils.Contexts.normalizeContexts(this.contexts); this.context = this.contexts[this.contextHash]; this.constructorContext = this.contexts[this.constructorContextHash]; + this.contextsById = Object.assign({}, ...Object.values(this.contexts).filter( + ({isConstructor}) => !isConstructor + ).map(context => + ({[context.contractId]: context}) + )); } public async init(): Promise { @@ -178,7 +184,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { mappingKeys: this.mappingKeys, userDefinedTypes: this.userDefinedTypes, allocations: this.allocations, - contexts: this.contexts, + contexts: this.contextsById, currentContext: this.context }; @@ -365,7 +371,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { mappingKeys: this.mappingKeys, userDefinedTypes: this.userDefinedTypes, allocations: this.allocations, - contexts: this.contexts + contexts: this.contextsById }; const decoder = Decoder.decodeCalldata(info, this.context); @@ -405,7 +411,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { mappingKeys: this.mappingKeys, userDefinedTypes: this.userDefinedTypes, allocations: this.allocations, - contexts: this.contexts + contexts: this.contextsById }; const decoder = Decoder.decodeEvent(info); diff --git a/packages/truffle-decoder/lib/utils.ts b/packages/truffle-decoder/lib/utils.ts index 8a3eaf69057..71d9457db4f 100644 --- a/packages/truffle-decoder/lib/utils.ts +++ b/packages/truffle-decoder/lib/utils.ts @@ -10,30 +10,6 @@ export function getContractNode(contract: ContractObject): AstDefinition { ); } -function getDeclarationsForTypes(contracts: AstDefinition[], types: string[]): AstReferences { - let result: AstReferences = {}; - - for (let contract of contracts) { - if (contract) { - for (const node of contract.nodes) { - if (types.includes(node.nodeType)) { - result[node.id] = node; - } - } - } - } - - return result; -} - -export function getEventDefinitions(contracts: AstDefinition[]): AstReferences { - const types = [ - "EventDefinition" - ]; - - return getDeclarationsForTypes(contracts, types); -} - export function makeContext(contract: ContractObject, node: AstDefinition, isConstructor = false): DecoderContext { return { contractName: contract.contractName, diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 83b807185c3..bd16d3501ba 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -23,6 +23,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { private contracts: DecoderTypes.ContractMapping = {}; private contractNodes: AstReferences = {}; private contexts: DecodeUtils.Contexts.DecoderContexts = {}; + private contextsById: DecodeUtils.Contexts.DecoderContextsById = {}; //deployed contexts only private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; @@ -67,14 +68,18 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { } this.contexts = DecodeUtils.Contexts.normalizeContexts(this.contexts); + this.contextsById = Object.assign({}, ...Object.values(this.contexts).filter( + ({isConstructor}) => !isConstructor + ).map(context => + ({[context.contractId]: context}) + )); } public async init(): Promise { this.contractNetwork = (await this.web3.eth.net.getId()).toString(); debug("init called"); - this.referenceDeclarations = Utils.getReferenceDeclarations(Object.values(this.contractNodes)); - this.userDefinedTypes = Types.definitionsToStoredTypes(this.referenceDeclarations); //TODO + [this.referenceDeclarations, this.userDefinedTypes] = this.getUserDefinedTypes(); this.allocations.storage = Decoder.getStorageAllocations(this.referenceDeclarations, {[this.contractNode.id]: this.contractNode}); this.allocations.abi = Decoder.getAbiAllocations(this.referenceDeclarations); @@ -105,6 +110,26 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { debug("done with allocation"); } + private getUserDefinedTypes(): [AstReferences, Types.TypesById] { + let references: AstReferences = {}; + let types: Types.TypesById = {}; + for(const id in this.contracts) { + const compiler = this.contracts[id].compiler; + //first, add the contract itself + const contractNode = this.contractNodes[id]; + references[id] = contractNode; + types[id] = Types.definitionToStoredType(contractNode, compiler); + //now, add its struct and enum definitions + for(const node of contractNode.nodes) { + if(node.nodeType === "StructDefinition" || node.nodeType === "EnumDefinition") { + references[node.id] = node; + types[node.id] = Types.definitionToStoredType(node, compiler); + } + } + } + return [references, types]; + } + private async getCode(address: string, block: number): Promise { //first, set up any preliminary layers as needed if(this.codeCache[block] === undefined) { @@ -137,9 +162,10 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { mappingKeys: this.mappingKeys, userDefinedTypes: this.userDefinedTypes, allocations: this.allocations, - contexts: this.contexts - //TODO set up current context + contexts: this.contextsById, + currentContext: context }; + //TODO: remove redundancy here const decoder = Decoder.decodeCalldata(info, context); let result = decoder.next(); @@ -174,7 +200,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { mappingKeys: this.mappingKeys, userDefinedTypes: this.userDefinedTypes, allocations: this.allocations, - contexts: this.contexts, + contexts: this.contextsById, libraryEventsTable: this.libraryEventsTable }; const decoder = Decoder.decodeEvent(info); From 65afeddd842cbf1f448f4da40f38360a946fe0d3 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Mon, 17 Jun 2019 21:25:05 -0400 Subject: [PATCH 13/89] Remove redundancy in calldata interface --- packages/truffle-decoder-core/lib/interface/decoding.ts | 5 +++-- packages/truffle-decoder/lib/contract.ts | 5 +++-- packages/truffle-decoder/lib/wire.ts | 3 +-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/truffle-decoder-core/lib/interface/decoding.ts b/packages/truffle-decoder-core/lib/interface/decoding.ts index c335745d9bb..635ae88b16a 100644 --- a/packages/truffle-decoder-core/lib/interface/decoding.ts +++ b/packages/truffle-decoder-core/lib/interface/decoding.ts @@ -16,14 +16,15 @@ export function* decodeVariable(definition: AstDefinition, pointer: Pointer.Data return yield* decode(dataType, pointer, info); //no need to pass an offset } -export function* decodeCalldata(info: EvmInfo, context: DecodeUtils.Contexts.DecoderContext | null): IterableIterator { - const compiler = info.currentContext.compiler; +export function* decodeCalldata(info: EvmInfo): IterableIterator { + const context = info.currentContext; if(context === null) { //if we don't know the contract ID, we can't decode return { kind: "unknown"; } } + const compiler = info.currentContext.compiler; const contractId = context.contractId; const contractType = DecodeUtils.Contexts.contextToType(context); const allocations = info.allocations.calldata[contractId]; diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 6c07e879e0c..4b943c212d6 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -367,9 +367,10 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { mappingKeys: this.mappingKeys, userDefinedTypes: this.userDefinedTypes, allocations: this.allocations, - contexts: this.contextsById + contexts: this.contextsById, + currentContext: transaction.to === null ? this.constructorContext : this.context }; - const decoder = Decoder.decodeCalldata(info, this.context); + const decoder = Decoder.decodeCalldata(info); let result = decoder.next(); while(!result.done) { diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index bd16d3501ba..c60f6ab5016 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -165,8 +165,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { contexts: this.contextsById, currentContext: context }; - //TODO: remove redundancy here - const decoder = Decoder.decodeCalldata(info, context); + const decoder = Decoder.decodeCalldata(info); let result = decoder.next(); while(!result.done) { From c68627892e4a225cf42da2c0596d10db60d123a9 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 18 Jun 2019 01:54:36 -0400 Subject: [PATCH 14/89] Fix lots of compile errors! --- packages/truffle-decode-utils/src/ast.ts | 1 + packages/truffle-decode-utils/src/contexts.ts | 20 ++++--- .../truffle-decode-utils/src/definition.ts | 8 ++- .../truffle-decode-utils/src/types/values.ts | 22 +++---- .../truffle-decoder-core/lib/allocate/abi.ts | 57 +++++++++---------- .../lib/allocate/memory.ts | 7 +-- .../lib/allocate/storage.ts | 5 +- .../truffle-decoder-core/lib/decode/abi.ts | 6 +- .../truffle-decoder-core/lib/decode/event.ts | 3 +- .../truffle-decoder-core/lib/decode/index.ts | 2 +- .../truffle-decoder-core/lib/decode/stack.ts | 8 +-- .../lib/decode/storage.ts | 4 +- .../lib/interface/decoding.ts | 32 ++++++----- .../lib/interface/index.ts | 7 ++- .../truffle-decoder-core/lib/read/index.ts | 4 +- .../lib/types/allocation.ts | 4 +- .../truffle-decoder-core/lib/types/wire.ts | 4 +- packages/truffle-decoder-core/package.json | 5 +- packages/truffle-decoder/lib/contract.ts | 56 +++++++++--------- packages/truffle-decoder/lib/index.ts | 4 +- packages/truffle-decoder/lib/types.ts | 6 +- packages/truffle-decoder/lib/utils.ts | 8 +-- packages/truffle-decoder/lib/wire.ts | 38 ++++++------- packages/truffle-decoder/package.json | 3 +- yarn.lock | 12 ++++ 25 files changed, 180 insertions(+), 146 deletions(-) diff --git a/packages/truffle-decode-utils/src/ast.ts b/packages/truffle-decode-utils/src/ast.ts index 0fa1416b67d..d56b7d3c408 100644 --- a/packages/truffle-decode-utils/src/ast.ts +++ b/packages/truffle-decode-utils/src/ast.ts @@ -32,6 +32,7 @@ export interface AstDefinition { }; keyType?: AstDefinition; valueType?: AstDefinition; + indexed?: boolean; [k: string]: any; } diff --git a/packages/truffle-decode-utils/src/contexts.ts b/packages/truffle-decode-utils/src/contexts.ts index 2a968a304c6..22f3f62569a 100644 --- a/packages/truffle-decode-utils/src/contexts.ts +++ b/packages/truffle-decode-utils/src/contexts.ts @@ -3,12 +3,14 @@ const debug = debugModule("decode-utils:contexts"); import { Abi } from "truffle-contract-schema/spec"; import { AbiCoder } from "web3-eth-abi"; -import { AbiItem } from "web3-utils"; +import { AbiItem, AbiInput } from "web3-utils"; const abiCoder = new AbiCoder(); import escapeRegExp from "lodash.escaperegexp"; import { EVM } from "./evm"; -import { Types } from "./types"; +import { Types } from "./types/types"; +import { AstDefinition, AstReferences } from "./ast"; +import { Definition as DefinitionUtils } from "./definition"; export namespace Contexts { @@ -127,6 +129,10 @@ export namespace Contexts { ) } + export function abiToWeb3Abi(abi: Abi): AbiItem[] { + return abi; //yup. just a type coercion :P + } + //does this ABI have a payable fallback function? export function isABIPayable(abi: Abi | undefined): boolean | undefined { if(abi === undefined) { @@ -138,7 +144,7 @@ export namespace Contexts { } //I split these next two apart because the type system was giving me rouble - export function findDecoderContext(contexts: DecoderContexts, binary: string): DecoderContext | null { + export function findDecoderContext(contexts: DecoderContexts | DecoderContextsById, binary: string): DecoderContext | null { debug("binary %s", binary); let context = Object.values(contexts).find(context => matchContext(context, binary) @@ -270,7 +276,7 @@ export namespace Contexts { return newContexts; } - export function matchesAbi(abiEntry: abiItem, node: AstDefinition, referenceDeclarations: AstReferences): boolean { + export function matchesAbi(abiEntry: AbiItem, node: AstDefinition, referenceDeclarations: AstReferences): boolean { //first: does the basic name and type match? switch(node.nodeType) { case "FunctionDefinition": @@ -301,7 +307,7 @@ export namespace Contexts { return matchesAbiParameters(abiEntry.inputs, node.parameters.parameters, referenceDeclarations); } - function matchesAbiParameters(abiParameters: AbiParameter[], nodeParameters: AstDefinition[], referenceDeclarations: AstReferences): boolean { + function matchesAbiParameters(abiParameters: AbiInput[], nodeParameters: AstDefinition[], referenceDeclarations: AstReferences): boolean { if(abiParameters.length !== nodeParameters.length) { return false; } @@ -314,11 +320,11 @@ export namespace Contexts { } //TODO: add error-handling - function matchesAbiType(abiParameter: AbiParameter, nodeParameter: AstDefinition, referenceDeclarations: AstReferences): boolean { + function matchesAbiType(abiParameter: AbiInput, nodeParameter: AstDefinition, referenceDeclarations: AstReferences): boolean { if(DefinitionUtils.toAbiType(nodeParameter, referenceDeclarations) !== abiParameter.type) { return false; } - if(abiParameter.type.beginsWith("tuple")) { + if(abiParameter.type.startsWith("tuple")) { let referenceDeclaration = referenceDeclarations[DefinitionUtils.typeId(nodeParameter)]; return matchesAbiParameters(abiParameter.components, referenceDeclaration.members, referenceDeclarations); } diff --git a/packages/truffle-decode-utils/src/definition.ts b/packages/truffle-decode-utils/src/definition.ts index 724d34e7517..c8c03f08817 100644 --- a/packages/truffle-decode-utils/src/definition.ts +++ b/packages/truffle-decode-utils/src/definition.ts @@ -2,7 +2,7 @@ import debugModule from "debug"; const debug = debugModule("decode-utils:definition"); import { EVM as EVMUtils } from "./evm"; -import { AstDefinition, Scopes } from "./ast"; +import { AstDefinition, AstReferences, Scopes } from "./ast"; import { Contexts } from "./contexts"; import BN from "bn.js"; import cloneDeep from "lodash.clonedeep"; @@ -199,7 +199,7 @@ export namespace Definition { } //adds "_ptr" on to the end of type identifiers that might need it; note that - //this operats on identifiers, not defintions + //this operats on identifiers, not definitions export function restorePtr(identifier: string): string { return identifier.replace(/(?<=_(storage|memory|calldata))$/, "_ptr"); } @@ -349,8 +349,10 @@ export namespace Definition { } //note: this is only meant for types that can go in the ABI + //it returns how that type is notated in the ABI -- just the string, + //to be clear, not components of tuples //TODO add error handling - export function toAbiType(defintion: AstDefinition, referenceDeclarations: AstReferences): string { + export function toAbiType(definition: AstDefinition, referenceDeclarations: AstReferences): string { let basicType = typeClassLongForm(definition); //get that whole first segment! switch(basicType) { case "contract": diff --git a/packages/truffle-decode-utils/src/types/values.ts b/packages/truffle-decode-utils/src/types/values.ts index ce3c479d450..9fb236df96a 100644 --- a/packages/truffle-decode-utils/src/types/values.ts +++ b/packages/truffle-decode-utils/src/types/values.ts @@ -1277,6 +1277,17 @@ export namespace Values { } } + export class ReadErrorTopic extends GenericError { + topic: number; + message() { + return `Can't read event topic ${this.topic}`; + } + constructor(topic: number) { + super(); + this.topic = topic; + } + } + //finally, a convenience function for constructing generic errors export function makeGenericErrorResult(dataType: Types.Type, error: GenericError): ErrorResult { switch(dataType.typeClass) { @@ -1317,15 +1328,4 @@ export namespace Values { } } } - - export class ReadErrorTopic extends GenericErrorDirect { - topic: number; - message() { - return `Can't read event topic ${this.topic}`; - } - constructor(topic: number) { - super(); - this.topic = topic; - } - } } diff --git a/packages/truffle-decoder-core/lib/allocate/abi.ts b/packages/truffle-decoder-core/lib/allocate/abi.ts index 28ca674b050..a5d2d1d544a 100644 --- a/packages/truffle-decoder-core/lib/allocate/abi.ts +++ b/packages/truffle-decoder-core/lib/allocate/abi.ts @@ -7,6 +7,10 @@ import { AstDefinition, AstReferences } from "truffle-decode-utils"; import * as DecodeUtils from "truffle-decode-utils"; import partition from "lodash.partition"; +import { AbiCoder } from "web3-eth-abi"; +import { AbiItem, AbiInput } from "web3-utils"; +const abiCoder = new AbiCoder(); + export function getAbiAllocations(referenceDeclarations: AstReferences): Allocations.AbiAllocations { let allocations: Allocations.AbiAllocations = {}; for(const node of Object.values(referenceDeclarations)) { @@ -44,7 +48,7 @@ function allocateMembers(parentNode: AstDefinition, definitions: AstDefinition[] //vomit on illegal types in calldata -- note the short-circuit! if(length === undefined) { - allocations[definition.id] = null; + allocations[parentNode.id] = null; return allocations; } @@ -63,8 +67,8 @@ function allocateMembers(parentNode: AstDefinition, definitions: AstDefinition[] dynamic = dynamic || dynamicMember; } - allocations[definition.id] = { - definition, + allocations[parentNode.id] = { + definition: parentNode, members: memberAllocations, length: dynamic ? DecodeUtils.EVM.WORD_SIZE : start, dynamic @@ -209,7 +213,7 @@ function allocateCalldata( abiEntry: AbiItem, contractId: number, referenceDeclarations: AstReferences, - abiAllocations: AbiAllocations, + abiAllocations: Allocations.AbiAllocations, constructorContext?: DecodeUtils.Contexts.DecoderContext ): Allocations.CalldataAllocation { const linearizedBaseContracts = referenceDeclarations[contractId].linearizedBaseContracts; @@ -228,10 +232,7 @@ function allocateCalldata( abiEntry, functionNode, referenceDeclarations ) ); - if(node === undefined) { - //TODO: error case - } - } + //TODO: handle case if node undefined break; case "function": offset = DecodeUtils.EVM.SELECTOR_SIZE; @@ -244,9 +245,7 @@ function allocateCalldata( ), undefined ); - if(node === undefined) { - //TODO: error case - } + //TODO: handle case if node undefined break; } //now: perform the allocation! @@ -254,13 +253,13 @@ function allocateCalldata( //finally: transform it appropriately let argumentsAllocation = []; for(const member of abiAllocation.members) { - const position = rawParameters.findIndex( - parameter => parameter.id === member.definition.id + const position = node.parameters.parameters.findIndex( + (parameter: AstDefinition) => parameter.id === member.definition.id ); argumentsAllocation[position] = { definition: member.definition, pointer: { - location: "calldata", + location: "calldata" as "calldata", start: member.pointer.start, length: member.pointer.length } @@ -279,9 +278,9 @@ function allocateCalldata( //TODO: check accesses to abi & node members function allocateEvent( abiEntry: AbiItem, - referenceDeclarations: AstReferences, contractId: number, - abiAllocations: AbiAllocations + referenceDeclarations: AstReferences, + abiAllocations: Allocations.AbiAllocations ): Allocations.EventAllocation { const linearizedBaseContracts = referenceDeclarations[contractId].linearizedBaseContracts; //first: determine the corresponding event node @@ -297,19 +296,19 @@ function allocateEvent( //now: split the list of parameters into indexed and non-indexed //but first attach positions so we can reconstruct the list later const rawParameters = node.parameters.parameters; - const [indexed, nonIndexed] = rawParameters.partition(parameter => parameter.indexed); + const [indexed, nonIndexed] = rawParameters.partition((parameter: AstDefinition) => parameter.indexed); //now: perform the allocation for the non-indexed parameters! const abiAllocation = allocateMembers(node, nonIndexed, referenceDeclarations, abiAllocations)[node.id]; //now: transform it appropriately let argumentsAllocation = []; for(const member of abiAllocation.members) { const position = rawParameters.findIndex( - parameter => parameter.id === member.definition.id + (parameter: AstDefinition) => parameter.id === member.definition.id ); argumentsAllocation[position] = { definition: member.definition, pointer: { - location: "eventdata", + location: "eventdata" as "eventdata", start: member.pointer.start, length: member.pointer.length } @@ -319,12 +318,12 @@ function allocateEvent( for(let positionInIndexed = 0; positionInIndexed < indexed.length; positionInIndexed++) { const node = indexed[positionInIndexed]; const position = rawParameters.findIndex( - parameter => parameter.id === node.id + (parameter: AstDefinition) => parameter.id === node.id ); argumentsAllocation[position] = { definition: node, pointer: { - location: "eventtopic", + location: "eventtopic" as "eventtopic", topic: positionInIndexed + 1 //signature takes up topic 0, so we skip that, hence +1 } }; @@ -340,14 +339,14 @@ function allocateEvent( //NOTE: this is for a single contract! //run multiple times to handle multiple contracts export function getCalldataAllocations( - abi: Abi, + abi: AbiItem[], contractId: number, referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations, - constructorContext: DecoderContext -): Allocations.CalldataContractAllocations { - let allocations: Allocations.CalldataContractAllocations; - for(let abiEntry in abi) { + constructorContext: DecodeUtils.Contexts.DecoderContext +): Allocations.CalldataContractAllocation { + let allocations: Allocations.CalldataContractAllocation; + for(let abiEntry of abi) { if(abiEntry.type === "constructor") { allocations.constructorAllocation = allocateCalldata( abiEntry, @@ -376,19 +375,19 @@ export function getCalldataAllocations( return allocations; } -function defaultConstructorAllocation(constructorContext: DecoderContext) { +function defaultConstructorAllocation(constructorContext: DecodeUtils.Contexts.DecoderContext) { let rawLength = constructorContext.binary.length; let offset = (rawLength - 2)/2; //number of bytes in 0x-prefixed bytestring return { offset, - arguments: {} + arguments: [] as Allocations.CalldataArgumentAllocation[] }; } //NOTE: this is for a single contract! //run multiple times to handle multiple contracts export function getEventAllocations( - abi: Abi, + abi: AbiItem[], contractId: number, referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations diff --git a/packages/truffle-decoder-core/lib/allocate/memory.ts b/packages/truffle-decoder-core/lib/allocate/memory.ts index 77d1d9150f0..08fd8b4807e 100644 --- a/packages/truffle-decoder-core/lib/allocate/memory.ts +++ b/packages/truffle-decoder-core/lib/allocate/memory.ts @@ -32,10 +32,9 @@ function allocateStruct(definition: AstDefinition): MemoryAllocation { memberAllocations.push({ definition: member, pointer: { - memory: { - start: nonMappingIndex * DecodeUtils.EVM.WORD_SIZE, - length: DecodeUtils.EVM.WORD_SIZE - } + location: "memory", + start: nonMappingIndex * DecodeUtils.EVM.WORD_SIZE, + length: DecodeUtils.EVM.WORD_SIZE } }); nonMappingIndex++; diff --git a/packages/truffle-decoder-core/lib/allocate/storage.ts b/packages/truffle-decoder-core/lib/allocate/storage.ts index ee2a9a101bd..abcffbe4ce1 100644 --- a/packages/truffle-decoder-core/lib/allocate/storage.ts +++ b/packages/truffle-decoder-core/lib/allocate/storage.ts @@ -48,7 +48,7 @@ function allocateMembers(parentNode: AstDefinition, definitions: AstDefinition[] //first off: is this a constant? if so we use a different, simpler process if(node.constant) { - let pointer = { definition: node.value }; + let pointer = { location: "definition" as "definition", definition: node.value }; //HACK restrict ourselves to the types of constants we know how to handle if(DecodeUtils.Definition.isSimpleConstant(node.value)) { memberAllocations.push({definition: node, pointer}); @@ -110,7 +110,8 @@ function allocateMembers(parentNode: AstDefinition, definitions: AstDefinition[] memberAllocations.push({ definition: node, pointer: { - storage: range + location: "storage", + range } }); diff --git a/packages/truffle-decoder-core/lib/decode/abi.ts b/packages/truffle-decoder-core/lib/decode/abi.ts index 89b9ba75ed3..25ad734a1fb 100644 --- a/packages/truffle-decoder-core/lib/decode/abi.ts +++ b/packages/truffle-decoder-core/lib/decode/abi.ts @@ -37,9 +37,9 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin const { allocations: { abi: allocations }, state } = info; debug("pointer %o", pointer); //this variable holds the location we should look to *next* - const location = pointer.location === "eventdata" + const location: AbiLocation = pointer.location === "eventdata" ? "eventdata" - : "calldata"; //stack pointers point to calldata, not the stack + : "calldata"; //stack pointers (& stack literal pointers) point to calldata, not the stack let rawValue: Uint8Array; try { @@ -154,7 +154,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin return new Values.ArrayValue(dataType, decodedChildren); case "struct": - return yield* decodeAbiStructByPosition(dataType, startPosition, info); + return yield* decodeAbiStructByPosition(dataType, location, startPosition, info); } } diff --git a/packages/truffle-decoder-core/lib/decode/event.ts b/packages/truffle-decoder-core/lib/decode/event.ts index b11c59e4067..8ec2291a312 100644 --- a/packages/truffle-decoder-core/lib/decode/event.ts +++ b/packages/truffle-decoder-core/lib/decode/event.ts @@ -2,6 +2,7 @@ import debugModule from "debug"; const debug = debugModule("decoder-core:decode:event"); import decodeValue from "./value"; +import read from "../read"; import { Types, Values, Conversion as ConversionUtils } from "truffle-decode-utils"; import { EventTopicPointer } from "../types/pointer"; import { EvmInfo } from "../types/evm"; @@ -12,7 +13,7 @@ export default function* decode(dataType: Types.Type, pointer: EventTopicPointer //we cannot decode reference types "stored" in topics; we have to just return an error let bytes: Uint8Array; try { - bytes = yield* read(pointer, state); + bytes = yield* read(pointer, info.state); } catch(error) { //error: Values.DecodingError return Values.makeGenericErrorResult(dataType, error.error); diff --git a/packages/truffle-decoder-core/lib/decode/index.ts b/packages/truffle-decoder-core/lib/decode/index.ts index cbed3a5aeba..d5b7dc646fa 100644 --- a/packages/truffle-decoder-core/lib/decode/index.ts +++ b/packages/truffle-decoder-core/lib/decode/index.ts @@ -6,7 +6,7 @@ import decodeMemory from "./memory"; import decodeStorage from "./storage"; import decodeStack from "./stack"; import { decodeLiteral } from "./stack"; -import decodeCalldata from "./calldata"; +import decodeAbi from "./abi"; import decodeConstant from "./constant"; import decodeSpecial from "./special"; import decodeTopic from "./event"; diff --git a/packages/truffle-decoder-core/lib/decode/stack.ts b/packages/truffle-decoder-core/lib/decode/stack.ts index d570cf3fae9..de4a6a0a79c 100644 --- a/packages/truffle-decoder-core/lib/decode/stack.ts +++ b/packages/truffle-decoder-core/lib/decode/stack.ts @@ -8,7 +8,7 @@ import decodeValue from "./value"; import { decodeExternalFunction, checkPaddingLeft } from "./value"; import { decodeMemoryReferenceByAddress } from "./memory"; import { decodeStorageReferenceByAddress } from "./storage"; -import { decodeCalldataReferenceByAddress } from "./calldata"; +import { decodeAbiReferenceByAddress } from "./abi"; import { StackPointer, StackLiteralPointer } from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; @@ -51,7 +51,7 @@ export function* decodeLiteral(dataType: Types.Type, pointer: StackLiteralPointe if(dataType.typeClass === "bytes" || dataType.typeClass === "string") { let start = DecodeUtils.Conversion.toBN(pointer.literal.slice(0, DecodeUtils.EVM.WORD_SIZE)).toNumber(); let length = DecodeUtils.Conversion.toBN(pointer.literal.slice(DecodeUtils.EVM.WORD_SIZE)).toNumber(); - let newPointer = { location: "calldata", start, length }; + let newPointer = { location: "calldata" as "calldata", start, length }; return yield* decodeValue(dataType, newPointer, info); } @@ -64,12 +64,12 @@ export function* decodeLiteral(dataType: Types.Type, pointer: StackLiteralPointe //HACK -- in order to read the correct location, we need to add an offset //of -32 (since, again, we're throwing away the length info), so we pass //that in as the "base" value - return yield* decodeCalldataReferenceByAddress(dataType, {location: "stackliteral", literal: locationOnly}, info, -DecodeUtils.EVM.WORD_SIZE); + return yield* decodeAbiReferenceByAddress(dataType, {location: "stackliteral", literal: locationOnly}, info, -DecodeUtils.EVM.WORD_SIZE); } else { //multivalue case -- this case is straightforward //pass in 0 as the base since this is an absolute pointer - return yield* decodeCalldataReferenceByAddress(dataType, pointer, info, 0); + return yield* decodeAbiReferenceByAddress(dataType, pointer, info, 0); } } } diff --git a/packages/truffle-decoder-core/lib/decode/storage.ts b/packages/truffle-decoder-core/lib/decode/storage.ts index e115aa36cd6..54cffb97204 100644 --- a/packages/truffle-decoder-core/lib/decode/storage.ts +++ b/packages/truffle-decoder-core/lib/decode/storage.ts @@ -49,7 +49,7 @@ export function* decodeStorageReferenceByAddress(dataType: Types.ReferenceType, //coercion const size = (<{words: number}>rawSize).words; //now, construct the storage pointer - const newPointer = { location: "storage", range: { + const newPointer = { location: "storage" as "storage", range: { from: { slot: { offset: startOffset @@ -72,6 +72,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: var length; const { state } = info; + const allocations = info.allocations.storage; switch (dataType.typeClass) { case "array": { @@ -350,6 +351,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: } else { valuePointer = { + location: "storage", range: { from: { slot: { diff --git a/packages/truffle-decoder-core/lib/interface/decoding.ts b/packages/truffle-decoder-core/lib/interface/decoding.ts index 635ae88b16a..a823371dc35 100644 --- a/packages/truffle-decoder-core/lib/interface/decoding.ts +++ b/packages/truffle-decoder-core/lib/interface/decoding.ts @@ -6,7 +6,9 @@ import * as DecodeUtils from "truffle-decode-utils"; import * as Pointer from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; -import { CalldataAllocation, EventAllocation } from "../types/allocation"; +import { CalldataAllocation, EventAllocation, EventArgumentAllocation } from "../types/allocation"; +import { CalldataDecoding, EventDecoding } from "../types/wire"; +import read from "../read"; import decode from "../decode"; export function* decodeVariable(definition: AstDefinition, pointer: Pointer.DataPointer, info: EvmInfo): IterableIterator { @@ -21,7 +23,7 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator read( { location: "calldata", start: 0, length: DecodeUtils.EVM.SELECTOR_SIZE - } - ); - let selector = DecodeUtils.EVM.toHexString(rawSelector); + }, + info.state + ).next().value; //no requests should occur, we can just get the first value + let selector = DecodeUtils.Conversion.toHexString(rawSelector); allocation = allocations.functionAllocations[selector]; } if(allocation === undefined) { @@ -75,7 +78,7 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator { const compiler = info.currentContext.compiler; const allocations = info.allocations.event; - const rawSelector = read(info.state, - { location: "eventdata", + const rawSelector = read( + { location: "eventtopic", topic: 0 - } - ); - const selector = DecodeUtils.EVM.toHexString(rawSelector); + }, + info.state + ).next().value; //no requests should occur, we can just get the first value + const selector = DecodeUtils.Conversion.toHexString(rawSelector); const allocation = allocations[selector]; if(allocation === undefined) { //we can't decode @@ -98,14 +102,14 @@ export function* decodeEvent(info: EvmInfo): IterableIterator context.contractId === allocation.contractId && !context.isConstructor ); let newInfo = { ...info, currentContext: context }; let contractType = DecodeUtils.Contexts.contextToType(context); let decodedArguments = allocation.arguments.map( - argumentAllocation => { + (argumentAllocation: EventArgumentAllocation) => { const value = decode( Types.definitionToType(argumentAllocation.definition, compiler), argumentAllocation.pointer, diff --git a/packages/truffle-decoder-core/lib/interface/index.ts b/packages/truffle-decoder-core/lib/interface/index.ts index 54319d96ef4..e0b15c85c42 100644 --- a/packages/truffle-decoder-core/lib/interface/index.ts +++ b/packages/truffle-decoder-core/lib/interface/index.ts @@ -1,12 +1,13 @@ export { getStorageAllocations, storageSize } from "../allocate/storage"; -export { getCalldataAllocations } from "../allocate/calldata"; +export { getAbiAllocations, getCalldataAllocations, getEventAllocations } from "../allocate/abi"; export { getMemoryAllocations } from "../allocate/memory"; export { readStack } from "../read/stack"; export { slotAddress } from "../read/storage"; -export { StoragePointer, isStoragePointer } from "../types/pointer"; -export { StorageAllocations, StorageMemberAllocation } from "../types/allocation"; +export { StoragePointer } from "../types/pointer"; +export { StorageAllocations, StorageMemberAllocation, AbiAllocations, CalldataAllocations, EventAllocations } from "../types/allocation"; export { Slot, isWordsLength } from "../types/storage"; export { DecoderRequest, isStorageRequest, isCodeRequest } from "../types/request"; export { EvmInfo } from "../types/evm"; +export { CalldataDecoding, EventDecoding } from "../types/wire"; export { decodeVariable, decodeEvent, decodeCalldata } from "./decoding"; diff --git a/packages/truffle-decoder-core/lib/read/index.ts b/packages/truffle-decoder-core/lib/read/index.ts index adf257c48ba..8d6e86604ca 100644 --- a/packages/truffle-decoder-core/lib/read/index.ts +++ b/packages/truffle-decoder-core/lib/read/index.ts @@ -37,7 +37,7 @@ export default function* read(pointer: Pointer.DataPointer, state: EvmState): It return state.specials[pointer.special]; case "eventtopic": - return readTopic(state.eventTopics, pointer.topic); + return readTopic(state.eventtopics, pointer.topic); //...and in the case of "abi", which shouldn't happen, we'll just fall off //the end and cause a problem :P @@ -49,7 +49,7 @@ function readTopic(topics: Uint8Array[], index: number) { let topic = topics[index]; if(topic === undefined) { throw new Values.DecodingError( - new Values.ReadErrorTopic(index); + new Values.ReadErrorTopic(index) ); } return topic; diff --git a/packages/truffle-decoder-core/lib/types/allocation.ts b/packages/truffle-decoder-core/lib/types/allocation.ts index 6f6844b219a..becc8d9dfa8 100644 --- a/packages/truffle-decoder-core/lib/types/allocation.ts +++ b/packages/truffle-decoder-core/lib/types/allocation.ts @@ -77,7 +77,7 @@ export interface CalldataAllocations { [contractId: number]: CalldataContractAllocation } -export interface CalldataContractAllocations { +export interface CalldataContractAllocation { constructorAllocation: CalldataAllocation; functionAllocations: { [selector: string]: CalldataAllocation; @@ -101,7 +101,7 @@ export interface CalldataArgumentAllocation { //contract ID! Instead the contract ID is included in the allocation export interface EventAllocations { - [selector: string]: EventAllocations + [selector: string]: EventAllocation } export interface EventAllocation { diff --git a/packages/truffle-decoder-core/lib/types/wire.ts b/packages/truffle-decoder-core/lib/types/wire.ts index a56aafedb45..8b1ea601892 100644 --- a/packages/truffle-decoder-core/lib/types/wire.ts +++ b/packages/truffle-decoder-core/lib/types/wire.ts @@ -1,3 +1,5 @@ +import * as DecodeUtils from "truffle-decode-utils"; + export interface CalldataDecoding { kind: "function" | "constructor" | "fallback" | "unknown"; class?: DecodeUtils.Types.ContractType; //included only if not unknown @@ -14,5 +16,5 @@ export interface EventDecoding { export interface AbiArgument { name?: string; //included if parameter is named - value: Values.Result; + value: DecodeUtils.Values.Result; } diff --git a/packages/truffle-decoder-core/package.json b/packages/truffle-decoder-core/package.json index 2f3f34fb954..7ffa0927a1d 100644 --- a/packages/truffle-decoder-core/package.json +++ b/packages/truffle-decoder-core/package.json @@ -29,12 +29,15 @@ "devDependencies": { "@types/bn.js": "^4.11.2", "@types/debug": "^0.0.31", + "@types/lodash.partition": "^4.6.6", "typescript": "^3.1.3" }, "dependencies": { "bn.js": "^4.11.8", "debug": "^4.1.0", - "truffle-decode-utils": "^1.0.12" + "lodash.partition": "^4.6.0", + "truffle-decode-utils": "^1.0.12", + "web3-eth-abi": "1.0.0-beta.52" }, "peerDependencies": { "truffle": "^5.0.14" diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 4b943c212d6..0fb1882b473 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -32,6 +32,8 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { private contextsById: DecodeUtils.Contexts.DecoderContextsById = {}; //deployed contexts only private context: DecodeUtils.Contexts.DecoderContext; private constructorContext: DecodeUtils.Contexts.DecoderContext; + private contextHash: string; + private constructorContextHash: string; private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; @@ -68,7 +70,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.contracts[this.contractNode.id] = this.contract; this.contractNodes[this.contractNode.id] = this.contractNode; if(this.contract.deployedBytecode) { //just to be safe - const context = makeContext(this.contract, this.contractNode); + const context = Utils.makeContext(this.contract, this.contractNode); const hash = DecodeUtils.Conversion.toHexString( DecodeUtils.EVM.keccak256({type: "string", value: context.binary @@ -78,7 +80,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.contexts[hash] = this.context; } if(this.contract.bytecode) { //now the constructor version - const constructorContext = makeContext(this.contract, this.contractNode, true); + const constructorContext = Utils.makeContext(this.contract, this.contractNode, true); const hash = DecodeUtils.Conversion.toHexString( DecodeUtils.EVM.keccak256({type: "string", value: constructorContext.binary @@ -94,7 +96,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.contracts[node.id] = relevantContract; this.contractNodes[node.id] = node; if(relevantContract.deployedBytecode) { - const context = makeContext(relevantContract, node); + const context = Utils.makeContext(relevantContract, node); const hash = DecodeUtils.Conversion.toHexString( DecodeUtils.EVM.keccak256({type: "string", value: context.binary @@ -127,7 +129,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.allocations.storage = Decoder.getStorageAllocations(this.referenceDeclarations, {[this.contractNode.id]: this.contractNode}); this.allocations.abi = Decoder.getAbiAllocations(this.referenceDeclarations); this.allocations.calldata[this.contractNode.id] = Decoder.getCalldataAllocations( - this.contract.abi, + DecodeUtils.Contexts.abiToWeb3Abi(this.contract.abi), this.contractNode.id, this.referenceDeclarations, this.allocations.abi, @@ -136,21 +138,21 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.allocations.event = {}; for(let id in this.contractNodes) { if(this.contractNodes[id].contractKind !== "library" - && id !== this.contractNode.id) { + && parseInt(id) !== this.contractNode.id) { continue; //only allocate for this contract and libraries } let contract = this.contracts[id]; Object.assign(this.allocations.event, Decoder.getEventAllocations( - contract.abi - id, + DecodeUtils.Contexts.abiToWeb3Abi(contract.abi), + parseInt(id), this.referenceDeclarations, this.allocations.abi ) ); } debug("done with allocation"); - this.stateVariableReferences = this.storageAllocations[this.contractNode.id].members; + this.stateVariableReferences = this.allocations.storage[this.contractNode.id].members; debug("stateVariableReferences %O", this.stateVariableReferences); this.contractCode = await this.web3.eth.getCode(this.contractAddress); @@ -344,16 +346,16 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { //than a while //NOTE: will only work with transactions to-or-creating this address! - public async decodeTransaction(transaction: Transaction): DecodedTransaction { + public async decodeTransaction(transaction: Transaction): Promise { if(transaction.to !== this.contractAddress) { if(transaction.to !== null) { - throw new EventOrTransactionIsNotForThisContract(transaction.to, this.contractAddress); + throw new DecoderTypes.EventOrTransactionIsNotForThisContractError(transaction.to, this.contractAddress); } else { //OK, it's not *to* this address, but maybe it *created* it? - const receipt = await web3.eth.getTransactionReceipt(transaction.hash); + const receipt = await this.web3.eth.getTransactionReceipt(transaction.hash); if(receipt.contractAddress !== this.contractAddress) { - throw new EventOrTransactionIsNotForThisContract(receipt.contractAddress, this.contractAddress); + throw new DecoderTypes.EventOrTransactionIsNotForThisContractError(receipt.contractAddress, this.contractAddress); } } } @@ -364,7 +366,6 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { storage: {}, calldata: data, }, - mappingKeys: this.mappingKeys, userDefinedTypes: this.userDefinedTypes, allocations: this.allocations, contexts: this.contextsById, @@ -383,7 +384,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { result = decoder.next(response); } //at this point, result.value holds the final value - const decoding = result.value; + const decoding = result.value; return { ...transaction, @@ -392,9 +393,9 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } //NOTE: will only work with logs for this address! - public async decodeLog(log: Log): DecodedEvent { + public async decodeLog(log: Log): Promise { if(log.address !== this.contractAddress) { - throw new EventOrTransactionIsNotForThisContract(log.address, this.contractAddress); + throw new DecoderTypes.EventOrTransactionIsNotForThisContractError(log.address, this.contractAddress); } const block = log.blockNumber; const data = DecodeUtils.Conversion.toBytes(log.data); @@ -405,7 +406,6 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { eventdata: data, eventtopics: topics }, - mappingKeys: this.mappingKeys, userDefinedTypes: this.userDefinedTypes, allocations: this.allocations, contexts: this.contextsById @@ -423,7 +423,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { result = decoder.next(response); } //at this point, result.value holds the final value - const decoding = result.value; + const decoding = result.value; return { ...log, @@ -432,18 +432,18 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } //NOTE: will only work with logs for this address! - public async decodeLogs(logs: Log[]): DecodedEvent[] { + public async decodeLogs(logs: Log[]): Promise { return await Promise.all(logs.map(this.decodeLog)); } public async events(name: string | null = null, fromBlock: BlockType = "latest", toBlock: BlockType = "latest"): Promise { - const logs = await web3.eth.getPastLogs({ - address: this.address, + const logs = await this.web3.eth.getPastLogs({ + address: this.contractAddress, fromBlock, toBlock, }); - let events = this.decodeLogs(logs); + let events = await this.decodeLogs(logs); if(name !== null) { events = events.filter(event => @@ -483,10 +483,10 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { let definition = allocation.definition; let pointer = allocation.pointer; - if(!Decoder.isStoragePointer(pointer)) { //if it's a constant + if(pointer.location !== "storage") { //i.e., if it's a constant return [undefined, undefined]; } - return [pointer.storage.from.slot, definition]; + return [pointer.range.from.slot, definition]; } //main case @@ -509,7 +509,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { index = new BN(rawIndex); } definition = parentDefinition.baseType || parentDefinition.typeName.baseType; - let size = Decoder.storageSize(definition, this.referenceDeclarations, this.storageAllocations); + let size = Decoder.storageSize(definition, this.referenceDeclarations, this.allocations.storage); if(!Decoder.isWordsLength(size)) { return [undefined, undefined]; } @@ -534,11 +534,11 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { let allocation: Decoder.StorageMemberAllocation; if(typeof rawIndex === "number") { index = rawIndex; - allocation = this.storageAllocations[parentId].members[index]; + allocation = this.allocations.storage[parentId].members[index]; definition = allocation.definition; } else { - allocation = Object.values(this.storageAllocations[parentId].members) + allocation = Object.values(this.allocations.storage[parentId].members) .find(({definition}) => definition.name === rawIndex); //there should be exactly one definition = allocation.definition; index = definition.id; //not really necessary, but may as well @@ -546,7 +546,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { slot = { path: parentSlot, //need type coercion here -- we know structs don't contain constants but the compiler doesn't - offset: (allocation.pointer).storage.from.slot.offset.clone() + offset: (allocation.pointer).range.from.slot.offset.clone() } break; default: diff --git a/packages/truffle-decoder/lib/index.ts b/packages/truffle-decoder/lib/index.ts index c3b32ed6171..2e6c325e0d6 100644 --- a/packages/truffle-decoder/lib/index.ts +++ b/packages/truffle-decoder/lib/index.ts @@ -7,6 +7,6 @@ export function forContract(contract: ContractObject, relevantContracts: Contrac return new TruffleContractDecoder(contract, relevantContracts, provider, address); } -export function forProject(contracts: ContractObject[], provider: Provider): TruffleContractDecoder { - return new TruffleWireDecoder(contracts, provider, address); +export function forProject(contracts: ContractObject[], provider: Provider): TruffleWireDecoder { + return new TruffleWireDecoder(contracts, provider); } diff --git a/packages/truffle-decoder/lib/types.ts b/packages/truffle-decoder/lib/types.ts index c20c8ab4af1..d8df034096e 100644 --- a/packages/truffle-decoder/lib/types.ts +++ b/packages/truffle-decoder/lib/types.ts @@ -1,6 +1,8 @@ import BN from "bn.js"; import { ContractObject } from "truffle-contract-schema/spec"; import { Values } from "truffle-decode-utils"; +import { CalldataDecoding, EventDecoding } from "truffle-decoder-core"; +import { Transaction, Log } from "web3-core"; export interface ContractState { name: string; @@ -46,13 +48,13 @@ export class ContractBeingDecodedHasNoNodeError extends Error { } } -export class EventOrTransactionIsNotForThisContract extends Error { +export class EventOrTransactionIsNotForThisContractError extends Error { thisAddress: string; decoderAddress: string; constructor(thisAddress: string, decoderAddress: string) { const message = "This event or transaction's address does not match that of the contract decoder"; super(message); - this.name = "EventOrTransactionIsNotForThisContract"; + this.name = "EventOrTransactionIsNotForThisContractError"; this.thisAddress = thisAddress; this.decoderAddress = decoderAddress; } diff --git a/packages/truffle-decoder/lib/utils.ts b/packages/truffle-decoder/lib/utils.ts index 71d9457db4f..71cdb30f602 100644 --- a/packages/truffle-decoder/lib/utils.ts +++ b/packages/truffle-decoder/lib/utils.ts @@ -1,4 +1,4 @@ -import { AstDefinition, AstReferences } from "truffle-decode-utils"; +import { AstDefinition, AstReferences, Contexts as ContextsUtils } from "truffle-decode-utils"; import { ContractObject } from "truffle-contract-schema/spec"; export function getContractNode(contract: ContractObject): AstDefinition { @@ -10,15 +10,15 @@ export function getContractNode(contract: ContractObject): AstDefinition { ); } -export function makeContext(contract: ContractObject, node: AstDefinition, isConstructor = false): DecoderContext { +export function makeContext(contract: ContractObject, node: AstDefinition, isConstructor = false): ContextsUtils.DecoderContext { return { contractName: contract.contractName, binary: isConstructor ? contract.bytecode : contract.deployedBytecode, contractId: node.id, contractKind: node.contractKind, isConstructor, - abi: DecodeUtils.Contexts.abiToFunctionAbiWithSignatures(contract.abi), - payable: DecodeUtils.Contexts.isABIPayable(contract.abi), + abi: ContextsUtils.abiToFunctionAbiWithSignatures(contract.abi), + payable: ContextsUtils.isABIPayable(contract.abi), compiler: contract.compiler }; } diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index c60f6ab5016..67004c6591d 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -47,7 +47,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { this.contracts[node.id] = contract; this.contractNodes[node.id] = node; if(contract.deployedBytecode) { - const context = makeContext(contract, node); + const context = Utils.makeContext(contract, node); const hash = DecodeUtils.Conversion.toHexString( DecodeUtils.EVM.keccak256({type: "string", value: context.binary @@ -56,7 +56,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { this.contexts[hash] = context; } if(contract.byteCode) { - const constructorContext = makeContext(contract, node, true); + const constructorContext = Utils.makeContext(contract, node, true); const hash = DecodeUtils.Conversion.toHexString( DecodeUtils.EVM.keccak256({type: "string", value: constructorContext.binary @@ -76,12 +76,12 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { } public async init(): Promise { - this.contractNetwork = (await this.web3.eth.net.getId()).toString(); + //note: this doesn't need to be async, but is for consistency debug("init called"); [this.referenceDeclarations, this.userDefinedTypes] = this.getUserDefinedTypes(); - this.allocations.storage = Decoder.getStorageAllocations(this.referenceDeclarations, {[this.contractNode.id]: this.contractNode}); + this.allocations.storage = Decoder.getStorageAllocations(this.referenceDeclarations, this.contractNodes); this.allocations.abi = Decoder.getAbiAllocations(this.referenceDeclarations); this.allocations.event = {}; for(let contractNode of Object.values(this.contractNodes)) { @@ -89,18 +89,18 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { let contract = this.contracts[id]; Object.assign(this.allocations.event, Decoder.getEventAllocations( - contract.abi, + DecodeUtils.Contexts.abiToWeb3Abi(contract.abi), id, this.referenceDeclarations, this.allocations.abi ) ); - let constructorContext = Object.values(contexts).find( + let constructorContext = Object.values(this.contexts).find( ({ contractId, isConstructor }) => contractId === id && isConstructor ); this.allocations.calldata[id] = Decoder.getCalldataAllocations( - contract.abi, + DecodeUtils.Contexts.abiToWeb3Abi(contract.abi), id, this.referenceDeclarations, this.allocations.abi, @@ -150,8 +150,9 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { return code; } - public async decodeTransaction(transaction: Transaction): DecodedTransaction { - const context = await this.getContextByAddress(transaction.to, transaction.blockNumber, transaction.input); + public async decodeTransaction(transaction: Transaction): Promise { + const block = transaction.blockNumber; + const context = await this.getContextByAddress(transaction.to, block, transaction.input); const data = DecodeUtils.Conversion.toBytes(transaction.input); const info: Decoder.EvmInfo = { @@ -159,7 +160,6 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { storage: {}, calldata: data, }, - mappingKeys: this.mappingKeys, userDefinedTypes: this.userDefinedTypes, allocations: this.allocations, contexts: this.contextsById, @@ -178,7 +178,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { result = decoder.next(response); } //at this point, result.value holds the final value - const decoding = result.value; + const decoding = result.value; return { ...transaction, @@ -186,7 +186,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - public async decodeLog(log: Log): DecodedEvent { + public async decodeLog(log: Log): Promise { const block = log.blockNumber; const data = DecodeUtils.Conversion.toBytes(log.data); const topics = log.topics.map(DecodeUtils.Conversion.toBytes); @@ -196,11 +196,9 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { eventdata: data, eventtopics: topics }, - mappingKeys: this.mappingKeys, userDefinedTypes: this.userDefinedTypes, allocations: this.allocations, - contexts: this.contextsById, - libraryEventsTable: this.libraryEventsTable + contexts: this.contextsById }; const decoder = Decoder.decodeEvent(info); @@ -215,7 +213,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { result = decoder.next(response); } //at this point, result.value holds the final value - const decoding = result.value; + const decoding = result.value; return { ...log, @@ -223,17 +221,17 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - public async decodeLogs(logs: Log[]): DecodedEvent[] { + public async decodeLogs(logs: Log[]): Promise { return await Promise.all(logs.map(this.decodeLog)); } public async events(name: string | null = null, fromBlock: BlockType = "latest", toBlock: BlockType = "latest"): Promise { - const logs = await web3.eth.getPastLogs({ + const logs = await this.web3.eth.getPastLogs({ fromBlock, toBlock, }); - let events = this.decodeLogs(logs); + let events = await this.decodeLogs(logs); if(name !== null) { events = events.filter(event => @@ -256,7 +254,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { //and checks this against the known contexts to determine the contract type //however, if this fails and constructorBinary is passed in, it will then also //attempt to determine it from that - private async getContextByAddress(address: string, block: number, constructorBinary?: string): Promise { + private async getContextByAddress(address: string, block: number, constructorBinary?: string): Promise { let code: string; if(address !== null) { code = DecodeUtils.Conversion.toHexString( diff --git a/packages/truffle-decoder/package.json b/packages/truffle-decoder/package.json index 0f206f9f0f8..b33ef48d42b 100644 --- a/packages/truffle-decoder/package.json +++ b/packages/truffle-decoder/package.json @@ -44,6 +44,7 @@ "@types/debug": "^4.1.4", "@types/lodash.isequal": "^4.5.5", "@types/web3": "1.0.18", - "typescript": "^3.5.1" + "typescript": "^3.5.1", + "web3-core": "1.0.0-beta.37" } } diff --git a/yarn.lock b/yarn.lock index 30af14d5101..d8bac15bab3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1025,6 +1025,13 @@ dependencies: "@types/lodash" "*" +"@types/lodash.partition@^4.6.6": + version "4.6.6" + resolved "https://registry.yarnpkg.com/@types/lodash.partition/-/lodash.partition-4.6.6.tgz#fdc23c9809b64b1d2e2f07faef045c035f0cd2c7" + integrity sha512-s8ZNNFWhBgTKI4uNxVrTs3Aa7UQoi7Fesw55bfpBBMCLda+uSuwDyuax8ka9aBy8Ccsjp2SiS034DkSZa+CzVA== + dependencies: + "@types/lodash" "*" + "@types/lodash@*", "@types/lodash@^4.14.116": version "4.14.133" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.133.tgz#430721c96da22dd1694443e68e6cec7ba1c1003d" @@ -9326,6 +9333,11 @@ lodash.once@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= +lodash.partition@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.partition/-/lodash.partition-4.6.0.tgz#a38e46b73469e0420b0da1212e66d414be364ba4" + integrity sha1-o45GtzRp4EILDaEhLmbUFL42S6Q= + lodash.pick@^4.2.1: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" From 65a4ebddcdfb7993d5a17396c4baf112c2f81d67 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 18 Jun 2019 17:39:31 -0400 Subject: [PATCH 15/89] Add a bit that went missing in a merge somewhere --- packages/truffle-decoder-core/lib/allocate/abi.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/truffle-decoder-core/lib/allocate/abi.ts b/packages/truffle-decoder-core/lib/allocate/abi.ts index a5d2d1d544a..f3721c22870 100644 --- a/packages/truffle-decoder-core/lib/allocate/abi.ts +++ b/packages/truffle-decoder-core/lib/allocate/abi.ts @@ -128,6 +128,10 @@ function abiSizeAndAllocate(definition: AstDefinition, referenceDeclarations?: A else { //static array case const length: number = DecodeUtils.Definition.staticLength(definition); + if(length === 0) { + //arrays of length 0 are static regardless of base type + return [0, false, existingAllocations]; + } const baseDefinition: AstDefinition = definition.baseType || definition.typeName.baseType; const [baseSize, dynamic, allocations] = abiSizeAndAllocate(baseDefinition, referenceDeclarations, existingAllocations); return [length * baseSize, dynamic, allocations]; From c95ad3f538eb90cabec0f98c47520ccaf70f5c15 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 18 Jun 2019 19:11:42 -0400 Subject: [PATCH 16/89] Redo calldata/event allocation interface --- packages/truffle-decode-utils/package.json | 2 + packages/truffle-decode-utils/src/contexts.ts | 4 -- .../truffle-decoder-core/lib/allocate/abi.ts | 32 ++++++++++---- .../lib/interface/index.ts | 2 +- .../lib/types/allocation.ts | 10 ++++- packages/truffle-decoder-core/package.json | 2 + packages/truffle-decoder/lib/contract.ts | 42 +++++++++---------- packages/truffle-decoder/lib/wire.ts | 40 +++++++----------- yarn.lock | 9 ++++ 9 files changed, 82 insertions(+), 61 deletions(-) diff --git a/packages/truffle-decode-utils/package.json b/packages/truffle-decode-utils/package.json index 773d21f03f0..a788958d1ad 100644 --- a/packages/truffle-decode-utils/package.json +++ b/packages/truffle-decode-utils/package.json @@ -4,9 +4,11 @@ "description": "Utilities for decoding data from the EVM", "dependencies": { "bn.js": "^4.11.8", + "json-schema-to-typescript": "^6.1.3", "lodash.clonedeep": "^4.5.0", "lodash.escaperegexp": "^4.1.2", "semver": "^6.1.1", + "truffle-contract-schema": "^3.0.11", "web3": "1.0.0-beta.37", "web3-eth-abi": "1.0.0-beta.52" }, diff --git a/packages/truffle-decode-utils/src/contexts.ts b/packages/truffle-decode-utils/src/contexts.ts index 811e67d45fd..5ab8b430e01 100644 --- a/packages/truffle-decode-utils/src/contexts.ts +++ b/packages/truffle-decode-utils/src/contexts.ts @@ -129,10 +129,6 @@ export namespace Contexts { ) } - export function abiToWeb3Abi(abi: Abi): AbiItem[] { - return abi; //yup. just a type coercion :P - } - //does this ABI have a payable fallback function? export function isABIPayable(abi: Abi | undefined): boolean | undefined { if(abi === undefined) { diff --git a/packages/truffle-decoder-core/lib/allocate/abi.ts b/packages/truffle-decoder-core/lib/allocate/abi.ts index f3721c22870..021cef3ee6a 100644 --- a/packages/truffle-decoder-core/lib/allocate/abi.ts +++ b/packages/truffle-decoder-core/lib/allocate/abi.ts @@ -8,7 +8,7 @@ import * as DecodeUtils from "truffle-decode-utils"; import partition from "lodash.partition"; import { AbiCoder } from "web3-eth-abi"; -import { AbiItem, AbiInput } from "web3-utils"; +import { AbiItem } from "web3-utils"; const abiCoder = new AbiCoder(); export function getAbiAllocations(referenceDeclarations: AstReferences): Allocations.AbiAllocations { @@ -340,14 +340,12 @@ function allocateEvent( }; } -//NOTE: this is for a single contract! -//run multiple times to handle multiple contracts -export function getCalldataAllocations( +function getCalldataAllocationsForContract( abi: AbiItem[], contractId: number, + constructorContext: DecodeUtils.Contexts.DecoderContext, referenceDeclarations: AstReferences, - abiAllocations: Allocations.AbiAllocations, - constructorContext: DecodeUtils.Contexts.DecoderContext + abiAllocations: Allocations.AbiAllocations ): Allocations.CalldataContractAllocation { let allocations: Allocations.CalldataContractAllocation; for(let abiEntry of abi) { @@ -388,9 +386,18 @@ function defaultConstructorAllocation(constructorContext: DecodeUtils.Contexts.D }; } -//NOTE: this is for a single contract! -//run multiple times to handle multiple contracts -export function getEventAllocations( +//note: contract allocation info should include constructor context +export function getCalldataAllocations(contracts: Allocations.ContractAllocationInfo[], referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations): Allocations.CalldataAllocations { + return Object.assign({}, ...contracts.map( + ({abi, id, constructorContext}) => ({ + [id]: getCalldataAllocationsForContract( + abi, id, constructorContext, referenceDeclarations, abiAllocations + ) + }) + )); +} + +function getEventAllocationsForContract( abi: AbiItem[], contractId: number, referenceDeclarations: AstReferences, @@ -405,3 +412,10 @@ export function getEventAllocations( ) ); } + +//note: constructor context is ignored by this function; no need to pass it in +export function getEventAllocations(contracts: Allocations.ContractAllocationInfo[], referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations): Allocations.EventAllocations { + return Object.assign({}, ...contracts.map( + ({abi, id}) => getEventAllocationsForContract(abi, id, referenceDeclarations, abiAllocations) + )); +} diff --git a/packages/truffle-decoder-core/lib/interface/index.ts b/packages/truffle-decoder-core/lib/interface/index.ts index e0b15c85c42..6f65c622ea5 100644 --- a/packages/truffle-decoder-core/lib/interface/index.ts +++ b/packages/truffle-decoder-core/lib/interface/index.ts @@ -4,7 +4,7 @@ export { getMemoryAllocations } from "../allocate/memory"; export { readStack } from "../read/stack"; export { slotAddress } from "../read/storage"; export { StoragePointer } from "../types/pointer"; -export { StorageAllocations, StorageMemberAllocation, AbiAllocations, CalldataAllocations, EventAllocations } from "../types/allocation"; +export { ContractAllocationInfo, StorageAllocations, StorageMemberAllocation, AbiAllocations, CalldataAllocations, EventAllocations } from "../types/allocation"; export { Slot, isWordsLength } from "../types/storage"; export { DecoderRequest, isStorageRequest, isCodeRequest } from "../types/request"; export { EvmInfo } from "../types/evm"; diff --git a/packages/truffle-decoder-core/lib/types/allocation.ts b/packages/truffle-decoder-core/lib/types/allocation.ts index becc8d9dfa8..4620aaaa635 100644 --- a/packages/truffle-decoder-core/lib/types/allocation.ts +++ b/packages/truffle-decoder-core/lib/types/allocation.ts @@ -1,6 +1,14 @@ import { StorageLength } from "./storage"; import * as Pointer from "./pointer"; -import { AstDefinition } from "truffle-decode-utils"; +import { AstDefinition, Contexts } from "truffle-decode-utils"; +import { Abi } from "truffle-contract-schema/spec"; + +//for passing to calldata/event allocation functions +export interface ContractAllocationInfo { + abi: Abi; + id: number; + constructorContext?: Contexts.DecoderContext; +} //holds a collection of storage allocations for structs and contracts, indexed //by the ID of the struct or contract diff --git a/packages/truffle-decoder-core/package.json b/packages/truffle-decoder-core/package.json index 7ffa0927a1d..9c464b31b16 100644 --- a/packages/truffle-decoder-core/package.json +++ b/packages/truffle-decoder-core/package.json @@ -35,7 +35,9 @@ "dependencies": { "bn.js": "^4.11.8", "debug": "^4.1.0", + "json-schema-to-typescript": "^6.1.3", "lodash.partition": "^4.6.0", + "truffle-contract-schema": "^3.0.11", "truffle-decode-utils": "^1.0.12", "web3-eth-abi": "1.0.0-beta.52" }, diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 0fb1882b473..48b2d682cb5 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -126,31 +126,29 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { debug("init called"); [this.referenceDeclarations, this.userDefinedTypes] = this.getUserDefinedTypes(); - this.allocations.storage = Decoder.getStorageAllocations(this.referenceDeclarations, {[this.contractNode.id]: this.contractNode}); + this.allocations.storage = Decoder.getStorageAllocations( + this.referenceDeclarations, + {[this.contractNode.id]: this.contractNode} + ); this.allocations.abi = Decoder.getAbiAllocations(this.referenceDeclarations); - this.allocations.calldata[this.contractNode.id] = Decoder.getCalldataAllocations( - DecodeUtils.Contexts.abiToWeb3Abi(this.contract.abi), - this.contractNode.id, + this.allocations.calldata = Decoder.getCalldataAllocations( + [{ + abi: this.contract.abi, + id: this.contractNode.id, + constructorContext: this.constructorContext + }], this.referenceDeclarations, - this.allocations.abi, - this.constructorContext + this.allocations.abi ); - this.allocations.event = {}; - for(let id in this.contractNodes) { - if(this.contractNodes[id].contractKind !== "library" - && parseInt(id) !== this.contractNode.id) { - continue; //only allocate for this contract and libraries - } - let contract = this.contracts[id]; - Object.assign(this.allocations.event, - Decoder.getEventAllocations( - DecodeUtils.Contexts.abiToWeb3Abi(contract.abi), - parseInt(id), - this.referenceDeclarations, - this.allocations.abi - ) - ); - } + this.allocations.event = Decoder.getEventAllocations( + [{ + abi: this.contract.abi, + id: this.contractNode.id + }], + this.referenceDeclarations, + this.allocations.abi + ); + debug("done with allocation"); this.stateVariableReferences = this.allocations.storage[this.contractNode.id].members; debug("stateVariableReferences %O", this.stateVariableReferences); diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 67004c6591d..f64e637c396 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -24,6 +24,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { private contractNodes: AstReferences = {}; private contexts: DecodeUtils.Contexts.DecoderContexts = {}; private contextsById: DecodeUtils.Contexts.DecoderContextsById = {}; //deployed contexts only + private constructorContextsById: DecodeUtils.Contexts.DecoderContextsById = {}; private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; @@ -73,6 +74,11 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { ).map(context => ({[context.contractId]: context}) )); + this.constructorContextsById = Object.assign({}, ...Object.values(this.contexts).filter( + ({isConstructor}) => isConstructor + ).map(context => + ({[context.contractId]: context}) + )); } public async init(): Promise { @@ -81,32 +87,18 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { debug("init called"); [this.referenceDeclarations, this.userDefinedTypes] = this.getUserDefinedTypes(); + let allocationInfo: Decoder.ContractAllocationInfo[] = Object.entries(this.contracts).map( + ([id, { abi }]) => ({ + abi, + id: parseInt(id), + constructorContext: this.constructorContextsById[parseInt(id)] + }) + ); + this.allocations.storage = Decoder.getStorageAllocations(this.referenceDeclarations, this.contractNodes); this.allocations.abi = Decoder.getAbiAllocations(this.referenceDeclarations); - this.allocations.event = {}; - for(let contractNode of Object.values(this.contractNodes)) { - let id = contractNode.id; - let contract = this.contracts[id]; - Object.assign(this.allocations.event, - Decoder.getEventAllocations( - DecodeUtils.Contexts.abiToWeb3Abi(contract.abi), - id, - this.referenceDeclarations, - this.allocations.abi - ) - ); - let constructorContext = Object.values(this.contexts).find( - ({ contractId, isConstructor }) => - contractId === id && isConstructor - ); - this.allocations.calldata[id] = Decoder.getCalldataAllocations( - DecodeUtils.Contexts.abiToWeb3Abi(contract.abi), - id, - this.referenceDeclarations, - this.allocations.abi, - constructorContext - ); - } + this.allocations.calldata = Decoder.getCalldataAllocations(allocationInfo, this.referenceDeclarations, this.allocations.abi); + this.allocations.event = Decoder.getEventAllocations(allocationInfo, this.referenceDeclarations, this.allocations.abi); debug("done with allocation"); } diff --git a/yarn.lock b/yarn.lock index d8bac15bab3..511e1cdb875 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14515,6 +14515,15 @@ truffle-contract-schema@^2.0.1: crypto-js "^3.1.9-1" debug "^3.1.0" +truffle-contract-schema@^3.0.11: + version "3.0.11" + resolved "https://registry.yarnpkg.com/truffle-contract-schema/-/truffle-contract-schema-3.0.11.tgz#202f6982b51bcad032b7ff2a8d5837853fb69301" + integrity sha512-YcgSOlrufi6VtnXg8LU5Ma7JHzHpnZQxzB1PSWnb+JOTc1nL02XRoCWTgEO7PkJnFgf6yrwOpW0ajSwHk3zQ7Q== + dependencies: + ajv "^6.10.0" + crypto-js "^3.1.9-1" + debug "^4.1.0" + truffle-contract@4.0.0-next.0: version "4.0.0-next.0" resolved "https://registry.yarnpkg.com/truffle-contract/-/truffle-contract-4.0.0-next.0.tgz#717b5921331ec70ca4f58a3d29ae955db80163fe" From ac1a2a59b331f21c4e54b4a1855766d6e83b0a52 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 18 Jun 2019 23:33:35 -0400 Subject: [PATCH 17/89] Add an abi encoder! --- .../truffle-debugger/lib/data/sagas/index.js | 3 +- .../truffle-decode-utils/src/conversion.ts | 12 +- .../truffle-decoder-core/lib/allocate/abi.ts | 4 +- .../lib/allocate/storage.ts | 2 +- .../truffle-decoder-core/lib/decode/value.ts | 3 + .../truffle-decoder-core/lib/encode/abi.ts | 168 ++++++++++++++++++ packages/truffle-decoder-core/package.json | 2 + yarn.lock | 7 + 8 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 packages/truffle-decoder-core/lib/encode/abi.ts diff --git a/packages/truffle-debugger/lib/data/sagas/index.js b/packages/truffle-debugger/lib/data/sagas/index.js index c9298b39c97..7e79860b06e 100644 --- a/packages/truffle-debugger/lib/data/sagas/index.js +++ b/packages/truffle-debugger/lib/data/sagas/index.js @@ -59,8 +59,7 @@ export function* decode(definition, ref, forceNonPayable = false) { ); let blockNumber = yield select(data.views.blockNumber); - let ZERO_WORD = new Uint8Array(DecodeUtils.EVM.WORD_SIZE); - ZERO_WORD.fill(0); + let ZERO_WORD = new Uint8Array(DecodeUtils.EVM.WORD_SIZE); //automatically filled with zeroes let NO_CODE = new Uint8Array(); //empty array if (forceNonPayable) { diff --git a/packages/truffle-decode-utils/src/conversion.ts b/packages/truffle-decode-utils/src/conversion.ts index 6f595539f58..f45e0d49e3e 100644 --- a/packages/truffle-decode-utils/src/conversion.ts +++ b/packages/truffle-decode-utils/src/conversion.ts @@ -103,11 +103,11 @@ export namespace Conversion { return Web3.utils.toChecksumAddress(toHexString(bytes, Constants.ADDRESS_SIZE)); } - export function toBytes(data: BN | string, length: number = 0): Uint8Array { + export function toBytes(data: BN | string | number, length: number = 0): Uint8Array { //note that length is a minimum output length //strings will be 0-padded on left - //BN will be sign-padded on left - //NOTE: if a BN is passed in that is too big for the given length, + //numbers/BNs will be sign-padded on left + //NOTE: if a number/BN is passed in that is too big for the given length, //you will get an error! //(note that strings passed in should be hex strings; this is not for converting //generic strings to hex) @@ -138,14 +138,16 @@ export namespace Conversion { if (bytes.length < length) { let prior = bytes; bytes = new Uint8Array(length); - bytes.fill(0); bytes.set(prior, length - prior.length); } return bytes; } else { - // BN case + // BN/number case + if(typeof data === "number") { + data = new BN(data); + } //note that the argument for toTwos is given in bits return new Uint8Array(data.toTwos(length * 8).toArrayLike(Buffer, "be", length)); //big-endian diff --git a/packages/truffle-decoder-core/lib/allocate/abi.ts b/packages/truffle-decoder-core/lib/allocate/abi.ts index 021cef3ee6a..9f92c580389 100644 --- a/packages/truffle-decoder-core/lib/allocate/abi.ts +++ b/packages/truffle-decoder-core/lib/allocate/abi.ts @@ -163,7 +163,7 @@ function abiSizeAndAllocate(definition: AstDefinition, referenceDeclarations?: A //like abiSize, but for a Type object; also assumes you've already done allocation //(note: function for dynamic is separate, see below) //also, does not attempt to handle types that don't occur in calldata -export function abiSizeForType(dataType: DecodeUtils.Types.Type, allocations: Allocations.AbiAllocations): number { +export function abiSizeForType(dataType: DecodeUtils.Types.Type, allocations?: Allocations.AbiAllocations): number { switch(dataType.typeClass) { case "array": switch(dataType.kind) { @@ -188,7 +188,7 @@ export function abiSizeForType(dataType: DecodeUtils.Types.Type, allocations: Al } //again, this function does not attempt to handle types that don't occur in the abi -export function isTypeDynamic(dataType: DecodeUtils.Types.Type, allocations: Allocations.AbiAllocations): boolean { +export function isTypeDynamic(dataType: DecodeUtils.Types.Type, allocations?: Allocations.AbiAllocations): boolean { switch(dataType.typeClass) { case "string": return true; diff --git a/packages/truffle-decoder-core/lib/allocate/storage.ts b/packages/truffle-decoder-core/lib/allocate/storage.ts index abcffbe4ce1..9df65b487bd 100644 --- a/packages/truffle-decoder-core/lib/allocate/storage.ts +++ b/packages/truffle-decoder-core/lib/allocate/storage.ts @@ -307,7 +307,7 @@ function storageSizeAndAllocate(definition: AstDefinition, referenceDeclarations } //like storageSize, but for a Type object; also assumes you've already done allocation -export function storageSizeForType(dataType: DecodeUtils.Types.Type, userDefinedTypes: DecodeUtils.Types.TypesById, allocations: StorageAllocations): StorageLength { +export function storageSizeForType(dataType: DecodeUtils.Types.Type, userDefinedTypes?: DecodeUtils.Types.TypesById, allocations?: StorageAllocations): StorageLength { switch(dataType.typeClass) { case "bool": return {bytes: 1}; diff --git a/packages/truffle-decoder-core/lib/decode/value.ts b/packages/truffle-decoder-core/lib/decode/value.ts index b0771d0b35c..c3d56b0df31 100644 --- a/packages/truffle-decoder-core/lib/decode/value.ts +++ b/packages/truffle-decoder-core/lib/decode/value.ts @@ -111,6 +111,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "string": //there is no padding check for strings + //HACK WARNING: we don't convert UTF-8 to UTF-16! we need to find some way to do that + //that doesn't throw an error, since we *don't* want to return an error value for + //invalid UTF-8 (since this can be generated via normal Solidity) return new Values.StringValue(dataType, String.fromCharCode.apply(undefined, bytes)); case "function": diff --git a/packages/truffle-decoder-core/lib/encode/abi.ts b/packages/truffle-decoder-core/lib/encode/abi.ts new file mode 100644 index 00000000000..341d071b3bb --- /dev/null +++ b/packages/truffle-decoder-core/lib/encode/abi.ts @@ -0,0 +1,168 @@ +import { Types, Values, Conversion as ConversionUtils, EVM as EVMUtils } from "truffle-decode-utils"; +import { AbiAllocations } from "../types/allocation"; +import { isTypeDynamic, abiSizeForType } from "../allocate/abi"; +import sum from "lodash.sum"; + +export function encodeAbi(input: Values.Result, allocations?: AbiAllocations): Uint8Array | undefined { + if(input instanceof Values.ErrorResult) { + return undefined; + } + //types that can't go in ABI + if(input instanceof Values.MappingValue) { + return undefined; + } + if(input instanceof Values.FunctionInternalValue) { + return undefined; + } + if(input instanceof Values.MagicValue) { + return undefined; + } + //now, the types that actually work! + if(input instanceof Values.UintValue) { + return ConversionUtils.toBytes(input.value, EVMUtils.WORD_SIZE); + } + if(input instanceof Values.IntValue) { + return ConversionUtils.toBytes(input.value, EVMUtils.WORD_SIZE); + } + if(input instanceof Values.EnumValue) { + return ConversionUtils.toBytes(input.value.numeric, EVMUtils.WORD_SIZE); + } + if(input instanceof Values.BoolValue) { + let bytes = new Uint8Array(EVMUtils.WORD_SIZE); //is initialized to zeroes + if(input.value) { + bytes[EVMUtils.WORD_SIZE - 1] = 1; + } + return bytes; + } + if(input instanceof Values.BytesValue) { + switch(input.type.kind) { + case "static": { + let bytes = ConversionUtils.toBytes(input.value); + return rightPad(bytes, EVMUtils.WORD_SIZE); + } + case "dynamic": { + let bytes = ConversionUtils.toBytes(input.value); + return padAndPrependLength(bytes); + } + } + } + if(input instanceof Values.AddressValue) { + let bytes = ConversionUtils.toBytes(input.value); + return rightPad(bytes, EVMUtils.WORD_SIZE); + } + if(input instanceof Values.ContractValue) { + let bytes = ConversionUtils.toBytes(input.value.address); + return rightPad(bytes, EVMUtils.WORD_SIZE); + } + if(input instanceof Values.StringValue) { + let bytes = stringToBytes(input.value); + return padAndPrependLength(bytes); + } + if(input instanceof Values.FunctionExternalValue) { + let encoded = new Uint8Array(EVMUtils.WORD_SIZE); //starts filled w/0s + let addressBytes = ConversionUtils.toBytes(input.value.contract.address); //should already be correct length + let selectorBytes = ConversionUtils.toBytes(input.value.selector); //should already be correct length + encoded.set(addressBytes); + encoded.set(selectorBytes, EVMUtils.ADDRESS_SIZE); //set it after the address + return encoded; + } + //skip fixed/ufixed for now + if(input instanceof Values.ArrayValue) { + if(input.reference !== undefined) { + return undefined; //circular values can't be encoded + } + let staticEncoding = encodeTupleAbi(input.value, allocations); + switch(input.type.kind) { + case "static": + return staticEncoding; + case "dynamic": + let encoded = new Uint8Array(EVMUtils.WORD_SIZE + staticEncoding.length); //leave room for length + let lengthBytes = ConversionUtils.toBytes(input.value.length, EVMUtils.WORD_SIZE); + encoded.set(lengthBytes); //and now we set the length + return encoded; + } + } + if(input instanceof Values.StructValue) { + if(input.reference !== undefined) { + return undefined; //circular values can't be encoded + } + return encodeTupleAbi(input.value.map(([_, value]) => value), allocations); + } +} + +function stringToBytes(input: string): Uint8Array { + //HACK WARNING: does not properly handle the UTF-16 to UTF-8 conversion + //(i.e. we ignore this problem) + //HOWEVER, since we also ignore this in the decoder, this should work + //fine for the purposes we're using it for now >_> + let bytes = new Uint8Array(input.length); + for(let i = 0; i < input.length; i++) { + bytes[i] = input.charCodeAt(i); + } + return bytes; +} + +function padAndPrependLength(bytes: Uint8Array): Uint8Array { + let length = bytes.length; + let paddedLength = EVMUtils.WORD_SIZE * Math.ceil(length / EVMUtils.WORD_SIZE); + let encoded = new Uint8Array(EVMUtils.WORD_SIZE + paddedLength); + encoded.set(bytes, EVMUtils.WORD_SIZE); //start 32 in to leave room for the length beforehand + let lengthBytes = ConversionUtils.toBytes(length, EVMUtils.WORD_SIZE); + encoded.set(lengthBytes); //and now we set the length + return encoded; +} + +function rightPad(bytes: Uint8Array, length: number): Uint8Array { + let padded = new Uint8Array(length); + padded.set(bytes); + return padded; +} + +export function encodeTupleAbi(tuple: Values.Result[], allocations?: AbiAllocations): Uint8Array | undefined { + let elementEncodings = tuple.map(element => encodeAbi(element, allocations)); + if(elementEncodings.some(element => element === undefined)) { + return undefined; + } + let isElementDynamic: boolean[] = tuple.map(element => isTypeDynamic(element.type, allocations)); + //heads and tails here are as discussed in the ABI docs; + //for a static type the head is the encoding and the tail is empty, + //for a dynamic type the head is the pointer and the tail is the encoding + let heads: Uint8Array[] = []; + let tails: Uint8Array[] = []; + //but first, we need to figure out where the first tail will start, + //by adding up the sizes of all the heads (we can easily do this in + //advance via abiSizeForType, without needing to know the particular + //values of the heads) + let startOfNextTail = sum(tuple.map(element => abiSizeForType(element.type, allocations))); + for(let i = 0; i < tuple.length; i++) { + let head: Uint8Array; + let tail: Uint8Array; + if(!isElementDynamic[i]) { + //static case + head = elementEncodings[i]; + tail = new Uint8Array(); //empty array + } + else { + //dynamic case + head = ConversionUtils.toBytes(startOfNextTail, EVMUtils.WORD_SIZE); + tail = elementEncodings[i]; + } + heads.push(head); + tails.push(tail); + startOfNextTail += tail.length; + } + //finally, we need to concatenate everything together! + //since we're dealing with Uint8Arrays, we have to do this manually + let totalSize = startOfNextTail; + let encoded = new Uint8Array(totalSize); + let position = 0; + for(let head of heads) { + encoded.set(head, position); + position += head.length; + } + for(let tail of tails) { + encoded.set(tail, position); + position += tail.length; + } + return encoded; +} diff --git a/packages/truffle-decoder-core/package.json b/packages/truffle-decoder-core/package.json index 9c464b31b16..b7ee2749aac 100644 --- a/packages/truffle-decoder-core/package.json +++ b/packages/truffle-decoder-core/package.json @@ -30,6 +30,7 @@ "@types/bn.js": "^4.11.2", "@types/debug": "^0.0.31", "@types/lodash.partition": "^4.6.6", + "@types/lodash.sum": "^4.0.6", "typescript": "^3.1.3" }, "dependencies": { @@ -37,6 +38,7 @@ "debug": "^4.1.0", "json-schema-to-typescript": "^6.1.3", "lodash.partition": "^4.6.0", + "lodash.sum": "^4.0.2", "truffle-contract-schema": "^3.0.11", "truffle-decode-utils": "^1.0.12", "web3-eth-abi": "1.0.0-beta.52" diff --git a/yarn.lock b/yarn.lock index 511e1cdb875..3977f31090d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1032,6 +1032,13 @@ dependencies: "@types/lodash" "*" +"@types/lodash.sum@^4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/lodash.sum/-/lodash.sum-4.0.6.tgz#acae9c8b24390a974667565acf17288e98dcdb8e" + integrity sha512-mqfBmDFruS3JFSAI/kPCEdFdOZvL4MJnakntlgPBUIYEwSTqxTZsENyWOgs1EMHq7+lU+jgs3yEtrljaUvvALQ== + dependencies: + "@types/lodash" "*" + "@types/lodash@*", "@types/lodash@^4.14.116": version "4.14.133" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.133.tgz#430721c96da22dd1694443e68e6cec7ba1c1003d" From b6115b85dfa136de77c088ab3fb9967d3485178f Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 19 Jun 2019 00:48:14 -0400 Subject: [PATCH 18/89] Update yarn.lock --- yarn.lock | 9 --------- 1 file changed, 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index 18e3e7311f0..78490c77062 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14425,15 +14425,6 @@ truffle-contract-schema@^2.0.1: crypto-js "^3.1.9-1" debug "^3.1.0" -truffle-contract-schema@^3.0.11: - version "3.0.11" - resolved "https://registry.yarnpkg.com/truffle-contract-schema/-/truffle-contract-schema-3.0.11.tgz#202f6982b51bcad032b7ff2a8d5837853fb69301" - integrity sha512-YcgSOlrufi6VtnXg8LU5Ma7JHzHpnZQxzB1PSWnb+JOTc1nL02XRoCWTgEO7PkJnFgf6yrwOpW0ajSwHk3zQ7Q== - dependencies: - ajv "^6.10.0" - crypto-js "^3.1.9-1" - debug "^4.1.0" - truffle-contract@4.0.0-next.0: version "4.0.0-next.0" resolved "https://registry.yarnpkg.com/truffle-contract/-/truffle-contract-4.0.0-next.0.tgz#717b5921331ec70ca4f58a3d29ae955db80163fe" From cd630d756c1978e489e4964453904d1b20c88492 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 19 Jun 2019 01:13:01 -0400 Subject: [PATCH 19/89] Rename truffle-decoder-core to truffle-codec --- .../.gitignore | 0 .../.npmignore | 0 .../README.md | 0 .../lib/allocate/abi.ts | 0 .../lib/allocate/memory.ts | 0 .../lib/allocate/storage.ts | 0 .../lib/decode/abi.ts | 0 .../lib/decode/constant.ts | 0 .../lib/decode/event.ts | 0 .../lib/decode/index.ts | 0 .../lib/decode/memory.ts | 0 .../lib/decode/special.ts | 0 .../lib/decode/stack.ts | 0 .../lib/decode/storage.ts | 0 .../lib/decode/value.ts | 0 .../lib/encode/abi.ts | 0 .../lib/interface/decoding.ts | 0 .../lib/interface/index.ts | 2 + .../lib/read/bytes.ts | 0 .../lib/read/constant.ts | 0 .../lib/read/index.ts | 0 .../lib/read/stack.ts | 0 .../lib/read/storage.ts | 0 .../lib/types/allocation.ts | 0 .../lib/types/errors.ts | 0 .../lib/types/evm.ts | 0 .../lib/types/pointer.ts | 0 .../lib/types/request.ts | 0 .../lib/types/storage.ts | 0 .../lib/types/wire.ts | 0 .../package.json | 6 +- .../tsconfig.json | 0 .../truffle-debugger/lib/data/reducers.js | 2 +- .../truffle-debugger/lib/data/sagas/index.js | 2 +- packages/truffle-debugger/package.json | 2 +- packages/truffle-decoder/lib/contract.ts | 74 +++++++++---------- packages/truffle-decoder/lib/types.ts | 2 +- packages/truffle-decoder/lib/wire.ts | 40 +++++----- packages/truffle-decoder/package.json | 2 +- 39 files changed, 67 insertions(+), 65 deletions(-) rename packages/{truffle-decoder-core => truffle-codec}/.gitignore (100%) rename packages/{truffle-decoder-core => truffle-codec}/.npmignore (100%) rename packages/{truffle-decoder-core => truffle-codec}/README.md (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/allocate/abi.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/allocate/memory.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/allocate/storage.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/decode/abi.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/decode/constant.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/decode/event.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/decode/index.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/decode/memory.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/decode/special.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/decode/stack.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/decode/storage.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/decode/value.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/encode/abi.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/interface/decoding.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/interface/index.ts (86%) rename packages/{truffle-decoder-core => truffle-codec}/lib/read/bytes.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/read/constant.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/read/index.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/read/stack.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/read/storage.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/types/allocation.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/types/errors.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/types/evm.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/types/pointer.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/types/request.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/types/storage.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/lib/types/wire.ts (100%) rename packages/{truffle-decoder-core => truffle-codec}/package.json (89%) rename packages/{truffle-decoder-core => truffle-codec}/tsconfig.json (100%) diff --git a/packages/truffle-decoder-core/.gitignore b/packages/truffle-codec/.gitignore similarity index 100% rename from packages/truffle-decoder-core/.gitignore rename to packages/truffle-codec/.gitignore diff --git a/packages/truffle-decoder-core/.npmignore b/packages/truffle-codec/.npmignore similarity index 100% rename from packages/truffle-decoder-core/.npmignore rename to packages/truffle-codec/.npmignore diff --git a/packages/truffle-decoder-core/README.md b/packages/truffle-codec/README.md similarity index 100% rename from packages/truffle-decoder-core/README.md rename to packages/truffle-codec/README.md diff --git a/packages/truffle-decoder-core/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts similarity index 100% rename from packages/truffle-decoder-core/lib/allocate/abi.ts rename to packages/truffle-codec/lib/allocate/abi.ts diff --git a/packages/truffle-decoder-core/lib/allocate/memory.ts b/packages/truffle-codec/lib/allocate/memory.ts similarity index 100% rename from packages/truffle-decoder-core/lib/allocate/memory.ts rename to packages/truffle-codec/lib/allocate/memory.ts diff --git a/packages/truffle-decoder-core/lib/allocate/storage.ts b/packages/truffle-codec/lib/allocate/storage.ts similarity index 100% rename from packages/truffle-decoder-core/lib/allocate/storage.ts rename to packages/truffle-codec/lib/allocate/storage.ts diff --git a/packages/truffle-decoder-core/lib/decode/abi.ts b/packages/truffle-codec/lib/decode/abi.ts similarity index 100% rename from packages/truffle-decoder-core/lib/decode/abi.ts rename to packages/truffle-codec/lib/decode/abi.ts diff --git a/packages/truffle-decoder-core/lib/decode/constant.ts b/packages/truffle-codec/lib/decode/constant.ts similarity index 100% rename from packages/truffle-decoder-core/lib/decode/constant.ts rename to packages/truffle-codec/lib/decode/constant.ts diff --git a/packages/truffle-decoder-core/lib/decode/event.ts b/packages/truffle-codec/lib/decode/event.ts similarity index 100% rename from packages/truffle-decoder-core/lib/decode/event.ts rename to packages/truffle-codec/lib/decode/event.ts diff --git a/packages/truffle-decoder-core/lib/decode/index.ts b/packages/truffle-codec/lib/decode/index.ts similarity index 100% rename from packages/truffle-decoder-core/lib/decode/index.ts rename to packages/truffle-codec/lib/decode/index.ts diff --git a/packages/truffle-decoder-core/lib/decode/memory.ts b/packages/truffle-codec/lib/decode/memory.ts similarity index 100% rename from packages/truffle-decoder-core/lib/decode/memory.ts rename to packages/truffle-codec/lib/decode/memory.ts diff --git a/packages/truffle-decoder-core/lib/decode/special.ts b/packages/truffle-codec/lib/decode/special.ts similarity index 100% rename from packages/truffle-decoder-core/lib/decode/special.ts rename to packages/truffle-codec/lib/decode/special.ts diff --git a/packages/truffle-decoder-core/lib/decode/stack.ts b/packages/truffle-codec/lib/decode/stack.ts similarity index 100% rename from packages/truffle-decoder-core/lib/decode/stack.ts rename to packages/truffle-codec/lib/decode/stack.ts diff --git a/packages/truffle-decoder-core/lib/decode/storage.ts b/packages/truffle-codec/lib/decode/storage.ts similarity index 100% rename from packages/truffle-decoder-core/lib/decode/storage.ts rename to packages/truffle-codec/lib/decode/storage.ts diff --git a/packages/truffle-decoder-core/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts similarity index 100% rename from packages/truffle-decoder-core/lib/decode/value.ts rename to packages/truffle-codec/lib/decode/value.ts diff --git a/packages/truffle-decoder-core/lib/encode/abi.ts b/packages/truffle-codec/lib/encode/abi.ts similarity index 100% rename from packages/truffle-decoder-core/lib/encode/abi.ts rename to packages/truffle-codec/lib/encode/abi.ts diff --git a/packages/truffle-decoder-core/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts similarity index 100% rename from packages/truffle-decoder-core/lib/interface/decoding.ts rename to packages/truffle-codec/lib/interface/decoding.ts diff --git a/packages/truffle-decoder-core/lib/interface/index.ts b/packages/truffle-codec/lib/interface/index.ts similarity index 86% rename from packages/truffle-decoder-core/lib/interface/index.ts rename to packages/truffle-codec/lib/interface/index.ts index 6f65c622ea5..c2645b11de2 100644 --- a/packages/truffle-decoder-core/lib/interface/index.ts +++ b/packages/truffle-codec/lib/interface/index.ts @@ -11,3 +11,5 @@ export { EvmInfo } from "../types/evm"; export { CalldataDecoding, EventDecoding } from "../types/wire"; export { decodeVariable, decodeEvent, decodeCalldata } from "./decoding"; + +export { encodeAbi, encodeTupleAbi } from "../encode/abi"; //we have no need to export these at present, but someone might want them? diff --git a/packages/truffle-decoder-core/lib/read/bytes.ts b/packages/truffle-codec/lib/read/bytes.ts similarity index 100% rename from packages/truffle-decoder-core/lib/read/bytes.ts rename to packages/truffle-codec/lib/read/bytes.ts diff --git a/packages/truffle-decoder-core/lib/read/constant.ts b/packages/truffle-codec/lib/read/constant.ts similarity index 100% rename from packages/truffle-decoder-core/lib/read/constant.ts rename to packages/truffle-codec/lib/read/constant.ts diff --git a/packages/truffle-decoder-core/lib/read/index.ts b/packages/truffle-codec/lib/read/index.ts similarity index 100% rename from packages/truffle-decoder-core/lib/read/index.ts rename to packages/truffle-codec/lib/read/index.ts diff --git a/packages/truffle-decoder-core/lib/read/stack.ts b/packages/truffle-codec/lib/read/stack.ts similarity index 100% rename from packages/truffle-decoder-core/lib/read/stack.ts rename to packages/truffle-codec/lib/read/stack.ts diff --git a/packages/truffle-decoder-core/lib/read/storage.ts b/packages/truffle-codec/lib/read/storage.ts similarity index 100% rename from packages/truffle-decoder-core/lib/read/storage.ts rename to packages/truffle-codec/lib/read/storage.ts diff --git a/packages/truffle-decoder-core/lib/types/allocation.ts b/packages/truffle-codec/lib/types/allocation.ts similarity index 100% rename from packages/truffle-decoder-core/lib/types/allocation.ts rename to packages/truffle-codec/lib/types/allocation.ts diff --git a/packages/truffle-decoder-core/lib/types/errors.ts b/packages/truffle-codec/lib/types/errors.ts similarity index 100% rename from packages/truffle-decoder-core/lib/types/errors.ts rename to packages/truffle-codec/lib/types/errors.ts diff --git a/packages/truffle-decoder-core/lib/types/evm.ts b/packages/truffle-codec/lib/types/evm.ts similarity index 100% rename from packages/truffle-decoder-core/lib/types/evm.ts rename to packages/truffle-codec/lib/types/evm.ts diff --git a/packages/truffle-decoder-core/lib/types/pointer.ts b/packages/truffle-codec/lib/types/pointer.ts similarity index 100% rename from packages/truffle-decoder-core/lib/types/pointer.ts rename to packages/truffle-codec/lib/types/pointer.ts diff --git a/packages/truffle-decoder-core/lib/types/request.ts b/packages/truffle-codec/lib/types/request.ts similarity index 100% rename from packages/truffle-decoder-core/lib/types/request.ts rename to packages/truffle-codec/lib/types/request.ts diff --git a/packages/truffle-decoder-core/lib/types/storage.ts b/packages/truffle-codec/lib/types/storage.ts similarity index 100% rename from packages/truffle-decoder-core/lib/types/storage.ts rename to packages/truffle-codec/lib/types/storage.ts diff --git a/packages/truffle-decoder-core/lib/types/wire.ts b/packages/truffle-codec/lib/types/wire.ts similarity index 100% rename from packages/truffle-decoder-core/lib/types/wire.ts rename to packages/truffle-codec/lib/types/wire.ts diff --git a/packages/truffle-decoder-core/package.json b/packages/truffle-codec/package.json similarity index 89% rename from packages/truffle-decoder-core/package.json rename to packages/truffle-codec/package.json index b7ee2749aac..67690ab2566 100644 --- a/packages/truffle-decoder-core/package.json +++ b/packages/truffle-codec/package.json @@ -1,7 +1,7 @@ { - "name": "truffle-decoder-core", + "name": "truffle-codec", "version": "3.0.3", - "description": "A decoder for Solidity variables of all sorts", + "description": "A decoder and encoder for Solidity variables of all sorts", "main": "dist/interface/index.js", "types": "lib/interface/index.ts", "directories": { @@ -12,7 +12,7 @@ "build": "node_modules/.bin/tsc", "start": "node_modules/.bin/tsc --watch" }, - "repository": "https://github.com/trufflesuite/truffle/tree/master/packages/truffle-decoder-core", + "repository": "https://github.com/trufflesuite/truffle/tree/master/packages/truffle-codec", "keywords": [ "ethereum", "solidity", diff --git a/packages/truffle-decoder-core/tsconfig.json b/packages/truffle-codec/tsconfig.json similarity index 100% rename from packages/truffle-decoder-core/tsconfig.json rename to packages/truffle-codec/tsconfig.json diff --git a/packages/truffle-debugger/lib/data/reducers.js b/packages/truffle-debugger/lib/data/reducers.js index 6ddcb4aa5c3..a67aa4f3e6c 100644 --- a/packages/truffle-debugger/lib/data/reducers.js +++ b/packages/truffle-debugger/lib/data/reducers.js @@ -5,7 +5,7 @@ import { combineReducers } from "redux"; import * as actions from "./actions"; -import { slotAddress } from "truffle-decoder-core"; +import { slotAddress } from "truffle-codec"; import { makeAssignment } from "lib/helpers"; import { Conversion, Definition, EVM } from "truffle-decode-utils"; diff --git a/packages/truffle-debugger/lib/data/sagas/index.js b/packages/truffle-debugger/lib/data/sagas/index.js index 0a15cdd1f14..5277e270ce9 100644 --- a/packages/truffle-debugger/lib/data/sagas/index.js +++ b/packages/truffle-debugger/lib/data/sagas/index.js @@ -23,7 +23,7 @@ import { readStack, storageSize, forEvmState -} from "truffle-decoder-core"; +} from "truffle-codec"; import BN from "bn.js"; export function* scope(nodeId, pointer, parentId, sourceId) { diff --git a/packages/truffle-debugger/package.json b/packages/truffle-debugger/package.json index 04e72e899d2..1e0dd2dab04 100644 --- a/packages/truffle-debugger/package.json +++ b/packages/truffle-debugger/package.json @@ -32,7 +32,7 @@ "reselect-tree": "^1.3.1", "truffle-code-utils": "^1.2.4", "truffle-decode-utils": "^1.0.14", - "truffle-decoder-core": "^3.0.3", + "truffle-codec": "^3.0.3", "truffle-expect": "^0.0.9", "truffle-solidity-utils": "^1.2.3", "web3": "1.0.0-beta.37", diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 48b2d682cb5..fd9f2bbd789 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -12,7 +12,7 @@ import { BlockType, Transaction } from "web3/eth/types"; import { EventLog, Log } from "web3/types"; import { Provider } from "web3/providers"; import isEqual from "lodash.isequal"; //util.isDeepStrictEqual doesn't exist in Node 8 -import * as Decoder from "truffle-decoder-core"; +import * as Codec from "truffle-codec"; import * as DecoderTypes from "./types"; import * as Utils from "./utils"; @@ -38,15 +38,15 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; private allocations: { - storage: Decoder.StorageAllocations; - abi: Decoder.AbiAllocations; - calldata: Decoder.CalldataAllocations; - event: Decoder.EventAllocations; + storage: Codec.StorageAllocations; + abi: Codec.AbiAllocations; + calldata: Codec.CalldataAllocations; + event: Codec.EventAllocations; }; - private stateVariableReferences: Decoder.StorageMemberAllocation[]; + private stateVariableReferences: Codec.StorageMemberAllocation[]; - private mappingKeys: Decoder.Slot[] = []; + private mappingKeys: Codec.Slot[] = []; private storageCache: DecoderTypes.StorageCache = {}; private codeCache: DecoderTypes.CodeCache = {}; @@ -126,12 +126,12 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { debug("init called"); [this.referenceDeclarations, this.userDefinedTypes] = this.getUserDefinedTypes(); - this.allocations.storage = Decoder.getStorageAllocations( + this.allocations.storage = Codec.getStorageAllocations( this.referenceDeclarations, {[this.contractNode.id]: this.contractNode} ); - this.allocations.abi = Decoder.getAbiAllocations(this.referenceDeclarations); - this.allocations.calldata = Decoder.getCalldataAllocations( + this.allocations.abi = Codec.getAbiAllocations(this.referenceDeclarations); + this.allocations.calldata = Codec.getCalldataAllocations( [{ abi: this.contract.abi, id: this.contractNode.id, @@ -140,7 +140,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.referenceDeclarations, this.allocations.abi ); - this.allocations.event = Decoder.getEventAllocations( + this.allocations.event = Codec.getEventAllocations( [{ abi: this.contract.abi, id: this.contractNode.id @@ -176,8 +176,8 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { return [references, types]; } - private async decodeVariable(variable: Decoder.StorageMemberAllocation, block: number): Promise { - const info: Decoder.EvmInfo = { + private async decodeVariable(variable: Codec.StorageMemberAllocation, block: number): Promise { + const info: Codec.EvmInfo = { state: { storage: {}, }, @@ -188,16 +188,16 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { currentContext: this.context }; - const decoder = Decoder.decodeVariable(variable.definition, variable.pointer, info); + const decoder = Codec.decodeVariable(variable.definition, variable.pointer, info); let result = decoder.next(); while(!result.done) { - let request = (result.value); + let request = (result.value); let response: Uint8Array; - if(Decoder.isStorageRequest(request)) { + if(Codec.isStorageRequest(request)) { response = await this.getStorage(this.contractAddress, request.slot, block); } - else if(Decoder.isCodeRequest(request)) { + else if(Codec.isCodeRequest(request)) { response = await this.getCode(request.address, block); } //note: one of the above conditionals *must* be true by the type system. @@ -242,7 +242,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { ? block : (await this.web3.eth.getBlock(block)).number; - let variable: Decoder.StorageMemberAllocation; + let variable: Codec.StorageMemberAllocation; variable = this.stateVariableReferences.find( ({definition}) => definition.name === nameOrId || definition.id == nameOrId ); //there should be exactly one @@ -305,7 +305,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { //feel free to mix arrays, mappings, and structs here! //see the comment on constructSlot for more detail on what forms are accepted public watchMappingKey(variable: number | string, ...indices: any[]): void { - let slot: Decoder.Slot | undefined = this.constructSlot(variable, ...indices)[0]; + let slot: Codec.Slot | undefined = this.constructSlot(variable, ...indices)[0]; //add mapping key and all ancestors debug("slot: %O", slot); while(slot !== undefined && @@ -324,7 +324,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { //input is similar to watchMappingKey; will unwatch all descendants too public unwatchMappingKey(variable: number | string, ...indices: any[]): void { - let slot: Decoder.Slot | undefined = this.constructSlot(variable, ...indices)[0]; + let slot: Codec.Slot | undefined = this.constructSlot(variable, ...indices)[0]; if(slot === undefined) { return; //not strictly necessary, but may as well } @@ -359,7 +359,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } const block = transaction.blockNumber; const data = DecodeUtils.Conversion.toBytes(transaction.input); - const info: Decoder.EvmInfo = { + const info: Codec.EvmInfo = { state: { storage: {}, calldata: data, @@ -369,20 +369,20 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { contexts: this.contextsById, currentContext: transaction.to === null ? this.constructorContext : this.context }; - const decoder = Decoder.decodeCalldata(info); + const decoder = Codec.decodeCalldata(info); let result = decoder.next(); while(!result.done) { - let request = (result.value); + let request = (result.value); let response: Uint8Array; //only code requests should occur here - if(Decoder.isCodeRequest(request)) { + if(Codec.isCodeRequest(request)) { response = await this.getCode(request.address, block); } result = decoder.next(response); } //at this point, result.value holds the final value - const decoding = result.value; + const decoding = result.value; return { ...transaction, @@ -398,7 +398,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { const block = log.blockNumber; const data = DecodeUtils.Conversion.toBytes(log.data); const topics = log.topics.map(DecodeUtils.Conversion.toBytes); - const info: Decoder.EvmInfo = { + const info: Codec.EvmInfo = { state: { storage: {}, eventdata: data, @@ -408,20 +408,20 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contextsById }; - const decoder = Decoder.decodeEvent(info); + const decoder = Codec.decodeEvent(info); let result = decoder.next(); while(!result.done) { - let request = (result.value); + let request = (result.value); let response: Uint8Array; //only code requests should occur here - if(Decoder.isCodeRequest(request)) { + if(Codec.isCodeRequest(request)) { response = await this.getCode(request.address, block); } result = decoder.next(response); } //at this point, result.value holds the final value - const decoding = result.value; + const decoding = result.value; return { ...log, @@ -470,10 +470,10 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { //bytes mapping keys should be given as hex strings beginning with "0x" //address mapping keys are like bytes; checksum case is not required //boolean mapping keys may be given either as booleans, or as string "true" or "false" - private constructSlot(variable: number | string, ...indices: any[]): [Decoder.Slot | undefined , AstDefinition | undefined] { + private constructSlot(variable: number | string, ...indices: any[]): [Codec.Slot | undefined , AstDefinition | undefined] { //base case: we need to locate the variable and its definition if(indices.length === 0) { - let allocation: Decoder.StorageMemberAllocation; + let allocation: Codec.StorageMemberAllocation; allocation = this.stateVariableReferences.find( ({definition}) => definition.name === variable || definition.id == variable ); //there should be exactly one @@ -496,7 +496,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { let rawIndex = indices[indices.length - 1]; let index: any; let key: Values.ElementaryResult; - let slot: Decoder.Slot; + let slot: Codec.Slot; let definition: AstDefinition; switch(DefinitionUtils.typeClass(parentDefinition)) { case "array": @@ -507,8 +507,8 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { index = new BN(rawIndex); } definition = parentDefinition.baseType || parentDefinition.typeName.baseType; - let size = Decoder.storageSize(definition, this.referenceDeclarations, this.allocations.storage); - if(!Decoder.isWordsLength(size)) { + let size = Codec.storageSize(definition, this.referenceDeclarations, this.allocations.storage); + if(!Codec.isWordsLength(size)) { return [undefined, undefined]; } slot = { @@ -529,7 +529,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { break; case "struct": let parentId = DefinitionUtils.typeId(parentDefinition); - let allocation: Decoder.StorageMemberAllocation; + let allocation: Codec.StorageMemberAllocation; if(typeof rawIndex === "number") { index = rawIndex; allocation = this.allocations.storage[parentId].members[index]; @@ -544,7 +544,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { slot = { path: parentSlot, //need type coercion here -- we know structs don't contain constants but the compiler doesn't - offset: (allocation.pointer).range.from.slot.offset.clone() + offset: (allocation.pointer).range.from.slot.offset.clone() } break; default: diff --git a/packages/truffle-decoder/lib/types.ts b/packages/truffle-decoder/lib/types.ts index d8df034096e..d5152b31934 100644 --- a/packages/truffle-decoder/lib/types.ts +++ b/packages/truffle-decoder/lib/types.ts @@ -1,7 +1,7 @@ import BN from "bn.js"; import { ContractObject } from "truffle-contract-schema/spec"; import { Values } from "truffle-decode-utils"; -import { CalldataDecoding, EventDecoding } from "truffle-decoder-core"; +import { CalldataDecoding, EventDecoding } from "truffle-codec"; import { Transaction, Log } from "web3-core"; export interface ContractState { diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index f64e637c396..9a0e27f2b20 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -11,7 +11,7 @@ import { Definition as DefinitionUtils, EVM, AstDefinition, AstReferences } from import { BlockType, Transaction } from "web3/eth/types"; import { EventLog, Log } from "web3/types"; import { Provider } from "web3/providers"; -import * as Decoder from "truffle-decoder-core"; +import * as Codec from "truffle-codec"; import * as DecoderTypes from "./types"; import * as Utils from "./utils"; @@ -29,10 +29,10 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; private allocations: { - storage: Decoder.StorageAllocations; - abi: Decoder.AbiAllocations; - calldata: Decoder.CalldataAllocations; - event: Decoder.EventAllocations; + storage: Codec.StorageAllocations; + abi: Codec.AbiAllocations; + calldata: Codec.CalldataAllocations; + event: Codec.EventAllocations; }; private codeCache: DecoderTypes.CodeCache = {}; @@ -87,7 +87,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { debug("init called"); [this.referenceDeclarations, this.userDefinedTypes] = this.getUserDefinedTypes(); - let allocationInfo: Decoder.ContractAllocationInfo[] = Object.entries(this.contracts).map( + let allocationInfo: Codec.ContractAllocationInfo[] = Object.entries(this.contracts).map( ([id, { abi }]) => ({ abi, id: parseInt(id), @@ -95,10 +95,10 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }) ); - this.allocations.storage = Decoder.getStorageAllocations(this.referenceDeclarations, this.contractNodes); - this.allocations.abi = Decoder.getAbiAllocations(this.referenceDeclarations); - this.allocations.calldata = Decoder.getCalldataAllocations(allocationInfo, this.referenceDeclarations, this.allocations.abi); - this.allocations.event = Decoder.getEventAllocations(allocationInfo, this.referenceDeclarations, this.allocations.abi); + this.allocations.storage = Codec.getStorageAllocations(this.referenceDeclarations, this.contractNodes); + this.allocations.abi = Codec.getAbiAllocations(this.referenceDeclarations); + this.allocations.calldata = Codec.getCalldataAllocations(allocationInfo, this.referenceDeclarations, this.allocations.abi); + this.allocations.event = Codec.getEventAllocations(allocationInfo, this.referenceDeclarations, this.allocations.abi); debug("done with allocation"); } @@ -147,7 +147,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { const context = await this.getContextByAddress(transaction.to, block, transaction.input); const data = DecodeUtils.Conversion.toBytes(transaction.input); - const info: Decoder.EvmInfo = { + const info: Codec.EvmInfo = { state: { storage: {}, calldata: data, @@ -157,20 +157,20 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { contexts: this.contextsById, currentContext: context }; - const decoder = Decoder.decodeCalldata(info); + const decoder = Codec.decodeCalldata(info); let result = decoder.next(); while(!result.done) { - let request = (result.value); + let request = (result.value); let response: Uint8Array; //only code requests should occur here - if(Decoder.isCodeRequest(request)) { + if(Codec.isCodeRequest(request)) { response = await this.getCode(request.address, block); } result = decoder.next(response); } //at this point, result.value holds the final value - const decoding = result.value; + const decoding = result.value; return { ...transaction, @@ -182,7 +182,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { const block = log.blockNumber; const data = DecodeUtils.Conversion.toBytes(log.data); const topics = log.topics.map(DecodeUtils.Conversion.toBytes); - const info: Decoder.EvmInfo = { + const info: Codec.EvmInfo = { state: { storage: {}, eventdata: data, @@ -192,20 +192,20 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contextsById }; - const decoder = Decoder.decodeEvent(info); + const decoder = Codec.decodeEvent(info); let result = decoder.next(); while(!result.done) { - let request = (result.value); + let request = (result.value); let response: Uint8Array; //only code requests should occur here - if(Decoder.isCodeRequest(request)) { + if(Codec.isCodeRequest(request)) { response = await this.getCode(request.address, block); } result = decoder.next(response); } //at this point, result.value holds the final value - const decoding = result.value; + const decoding = result.value; return { ...log, diff --git a/packages/truffle-decoder/package.json b/packages/truffle-decoder/package.json index 93036c50e65..912cb4729b2 100644 --- a/packages/truffle-decoder/package.json +++ b/packages/truffle-decoder/package.json @@ -36,7 +36,7 @@ "lodash.isequal": "^4.5.0", "truffle-contract-schema": "^3.0.9", "truffle-decode-utils": "^1.0.12", - "truffle-decoder-core": "^3.0.3", + "truffle-codec": "^3.0.3", "truffle-decode-utils": "^1.0.14", "web3": "1.0.0-beta.37" }, From 4fb1d8488afbc66f56b45b0537eae3774d19bd41 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 19 Jun 2019 01:14:38 -0400 Subject: [PATCH 20/89] Fix outdated function names in data sagas --- packages/truffle-debugger/lib/data/sagas/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/truffle-debugger/lib/data/sagas/index.js b/packages/truffle-debugger/lib/data/sagas/index.js index 5277e270ce9..b1c28229217 100644 --- a/packages/truffle-debugger/lib/data/sagas/index.js +++ b/packages/truffle-debugger/lib/data/sagas/index.js @@ -19,10 +19,10 @@ import * as DecodeUtils from "truffle-decode-utils"; import { getStorageAllocations, getMemoryAllocations, - getCalldataAllocations, + getAbiAllocations, readStack, storageSize, - forEvmState + decodeVariable } from "truffle-codec"; import BN from "bn.js"; @@ -69,7 +69,7 @@ export function* decode(definition, ref, forceNonPayable = false) { currentContext = { ...currentContext, compiler: null }; } - let decoder = forEvmState(definition, ref, { + let decoder = decodeVariable(definition, ref, { userDefinedTypes, state, mappingKeys, @@ -619,7 +619,7 @@ export function* recordAllocations() { ); debug("storageAllocations %O", storageAllocations); const memoryAllocations = getMemoryAllocations(referenceDeclarations); - const calldataAllocations = getCalldataAllocations(referenceDeclarations); + const calldataAllocations = getAbiAllocations(referenceDeclarations); yield put( actions.allocate(storageAllocations, memoryAllocations, calldataAllocations) ); From e2d0e24eef9c2aeda2668f2b4cbcb6e541c7ce08 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 19 Jun 2019 01:48:17 -0400 Subject: [PATCH 21/89] Rename truffle-decode-utils to truffle-codec-utils --- .../.gitignore | 0 .../.npmignore | 0 .../LICENSE | 0 .../README.md | 0 .../package.json | 10 +-- .../src/ast.ts | 0 .../src/constants.ts | 0 .../src/contexts.ts | 0 .../src/conversion.ts | 0 .../src/definition.ts | 0 .../src/evm.ts | 0 .../src/index.ts | 0 .../src/types/types.ts | 0 .../src/types/values.ts | 0 .../tsconfig.json | 0 packages/truffle-codec/lib/allocate/abi.ts | 56 +++++++-------- packages/truffle-codec/lib/allocate/memory.ts | 10 +-- .../truffle-codec/lib/allocate/storage.ts | 72 +++++++++---------- packages/truffle-codec/lib/decode/abi.ts | 18 ++--- packages/truffle-codec/lib/decode/constant.ts | 8 +-- packages/truffle-codec/lib/decode/event.ts | 2 +- packages/truffle-codec/lib/decode/index.ts | 2 +- packages/truffle-codec/lib/decode/memory.ts | 22 +++--- packages/truffle-codec/lib/decode/special.ts | 8 +-- packages/truffle-codec/lib/decode/stack.ts | 26 +++---- packages/truffle-codec/lib/decode/storage.ts | 30 ++++---- packages/truffle-codec/lib/decode/value.ts | 68 +++++++++--------- packages/truffle-codec/lib/encode/abi.ts | 2 +- .../truffle-codec/lib/interface/decoding.ts | 14 ++-- packages/truffle-codec/lib/read/bytes.ts | 4 +- packages/truffle-codec/lib/read/constant.ts | 14 ++-- packages/truffle-codec/lib/read/index.ts | 2 +- packages/truffle-codec/lib/read/stack.ts | 10 +-- packages/truffle-codec/lib/read/storage.ts | 20 +++--- .../truffle-codec/lib/types/allocation.ts | 2 +- packages/truffle-codec/lib/types/evm.ts | 2 +- packages/truffle-codec/lib/types/pointer.ts | 2 +- packages/truffle-codec/lib/types/request.ts | 2 +- packages/truffle-codec/lib/types/storage.ts | 6 +- packages/truffle-codec/lib/types/wire.ts | 8 +-- packages/truffle-codec/package.json | 2 +- .../truffle-debugger/lib/data/reducers.js | 2 +- .../truffle-debugger/lib/data/sagas/index.js | 68 +++++++++--------- .../lib/data/selectors/index.js | 44 ++++++------ packages/truffle-debugger/lib/evm/reducers.js | 16 ++--- .../lib/evm/selectors/index.js | 16 ++--- .../truffle-debugger/lib/helpers/index.js | 2 +- .../truffle-debugger/lib/trace/sagas/index.js | 6 +- .../truffle-debugger/lib/web3/sagas/index.js | 4 +- .../truffle-debugger/test/data/calldata.js | 10 +-- .../test/data/function-decoding.js | 6 +- packages/truffle-debugger/test/data/global.js | 14 ++-- .../test/data/more-decoding.js | 16 ++--- packages/truffle-debugger/test/data/utils.js | 14 ++-- packages/truffle-debugger/test/endstate.js | 4 +- packages/truffle-debugger/test/load.js | 8 +-- packages/truffle-debugger/test/reset.js | 14 ++-- packages/truffle-decoder/lib/contract.ts | 40 +++++------ packages/truffle-decoder/lib/types.ts | 2 +- packages/truffle-decoder/lib/utils.ts | 2 +- packages/truffle-decoder/lib/wire.ts | 36 +++++----- packages/truffle-decoder/test/test/test.js | 4 +- yarn.lock | 11 +++ 63 files changed, 386 insertions(+), 375 deletions(-) rename packages/{truffle-decode-utils => truffle-codec-utils}/.gitignore (100%) rename packages/{truffle-decode-utils => truffle-codec-utils}/.npmignore (100%) rename packages/{truffle-decode-utils => truffle-codec-utils}/LICENSE (100%) rename packages/{truffle-decode-utils => truffle-codec-utils}/README.md (100%) rename packages/{truffle-decode-utils => truffle-codec-utils}/package.json (77%) rename packages/{truffle-decode-utils => truffle-codec-utils}/src/ast.ts (100%) rename packages/{truffle-decode-utils => truffle-codec-utils}/src/constants.ts (100%) rename packages/{truffle-decode-utils => truffle-codec-utils}/src/contexts.ts (100%) rename packages/{truffle-decode-utils => truffle-codec-utils}/src/conversion.ts (100%) rename packages/{truffle-decode-utils => truffle-codec-utils}/src/definition.ts (100%) rename packages/{truffle-decode-utils => truffle-codec-utils}/src/evm.ts (100%) rename packages/{truffle-decode-utils => truffle-codec-utils}/src/index.ts (100%) rename packages/{truffle-decode-utils => truffle-codec-utils}/src/types/types.ts (100%) rename packages/{truffle-decode-utils => truffle-codec-utils}/src/types/values.ts (100%) rename packages/{truffle-decode-utils => truffle-codec-utils}/tsconfig.json (100%) diff --git a/packages/truffle-decode-utils/.gitignore b/packages/truffle-codec-utils/.gitignore similarity index 100% rename from packages/truffle-decode-utils/.gitignore rename to packages/truffle-codec-utils/.gitignore diff --git a/packages/truffle-decode-utils/.npmignore b/packages/truffle-codec-utils/.npmignore similarity index 100% rename from packages/truffle-decode-utils/.npmignore rename to packages/truffle-codec-utils/.npmignore diff --git a/packages/truffle-decode-utils/LICENSE b/packages/truffle-codec-utils/LICENSE similarity index 100% rename from packages/truffle-decode-utils/LICENSE rename to packages/truffle-codec-utils/LICENSE diff --git a/packages/truffle-decode-utils/README.md b/packages/truffle-codec-utils/README.md similarity index 100% rename from packages/truffle-decode-utils/README.md rename to packages/truffle-codec-utils/README.md diff --git a/packages/truffle-decode-utils/package.json b/packages/truffle-codec-utils/package.json similarity index 77% rename from packages/truffle-decode-utils/package.json rename to packages/truffle-codec-utils/package.json index a0269b794bb..57b0f23ab73 100644 --- a/packages/truffle-decode-utils/package.json +++ b/packages/truffle-codec-utils/package.json @@ -1,7 +1,7 @@ { - "name": "truffle-decode-utils", + "name": "truffle-codec-utils", "version": "1.0.14", - "description": "Utilities for decoding data from the EVM", + "description": "Utilities for decoding data from the EVM or encoding it for the EVM", "dependencies": { "bn.js": "^4.11.8", "json-schema-to-typescript": "^6.1.3", @@ -20,13 +20,13 @@ "start": "node_modules/.bin/tsc --watch", "test": "echo \"No test specified\" && exit 0;" }, - "repository": "https://github.com/trufflesuite/truffle/tree/master/packages/truffle-decode-utils", + "repository": "https://github.com/trufflesuite/truffle/tree/master/packages/truffle-codec-utils", "author": "Truffle Suite ", "license": "MIT", "bugs": { - "url": "https://github.com/trufflesuite/truffle-decode-utils/issues" + "url": "https://github.com/trufflesuite/truffle-codec-utils/issues" }, - "homepage": "https://github.com/trufflesuite/truffle-decode-utils#readme", + "homepage": "https://github.com/trufflesuite/truffle-codec-utils#readme", "publishConfig": { "access": "public" }, diff --git a/packages/truffle-decode-utils/src/ast.ts b/packages/truffle-codec-utils/src/ast.ts similarity index 100% rename from packages/truffle-decode-utils/src/ast.ts rename to packages/truffle-codec-utils/src/ast.ts diff --git a/packages/truffle-decode-utils/src/constants.ts b/packages/truffle-codec-utils/src/constants.ts similarity index 100% rename from packages/truffle-decode-utils/src/constants.ts rename to packages/truffle-codec-utils/src/constants.ts diff --git a/packages/truffle-decode-utils/src/contexts.ts b/packages/truffle-codec-utils/src/contexts.ts similarity index 100% rename from packages/truffle-decode-utils/src/contexts.ts rename to packages/truffle-codec-utils/src/contexts.ts diff --git a/packages/truffle-decode-utils/src/conversion.ts b/packages/truffle-codec-utils/src/conversion.ts similarity index 100% rename from packages/truffle-decode-utils/src/conversion.ts rename to packages/truffle-codec-utils/src/conversion.ts diff --git a/packages/truffle-decode-utils/src/definition.ts b/packages/truffle-codec-utils/src/definition.ts similarity index 100% rename from packages/truffle-decode-utils/src/definition.ts rename to packages/truffle-codec-utils/src/definition.ts diff --git a/packages/truffle-decode-utils/src/evm.ts b/packages/truffle-codec-utils/src/evm.ts similarity index 100% rename from packages/truffle-decode-utils/src/evm.ts rename to packages/truffle-codec-utils/src/evm.ts diff --git a/packages/truffle-decode-utils/src/index.ts b/packages/truffle-codec-utils/src/index.ts similarity index 100% rename from packages/truffle-decode-utils/src/index.ts rename to packages/truffle-codec-utils/src/index.ts diff --git a/packages/truffle-decode-utils/src/types/types.ts b/packages/truffle-codec-utils/src/types/types.ts similarity index 100% rename from packages/truffle-decode-utils/src/types/types.ts rename to packages/truffle-codec-utils/src/types/types.ts diff --git a/packages/truffle-decode-utils/src/types/values.ts b/packages/truffle-codec-utils/src/types/values.ts similarity index 100% rename from packages/truffle-decode-utils/src/types/values.ts rename to packages/truffle-codec-utils/src/types/values.ts diff --git a/packages/truffle-decode-utils/tsconfig.json b/packages/truffle-codec-utils/tsconfig.json similarity index 100% rename from packages/truffle-decode-utils/tsconfig.json rename to packages/truffle-codec-utils/tsconfig.json diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index 9f92c580389..d4031cace4f 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -3,8 +3,8 @@ const debug = debugModule("decoder-core:allocate:abi"); import * as Pointer from "../types/pointer"; import * as Allocations from "../types/allocation"; -import { AstDefinition, AstReferences } from "truffle-decode-utils"; -import * as DecodeUtils from "truffle-decode-utils"; +import { AstDefinition, AstReferences } from "truffle-codec-utils"; +import * as CodecUtils from "truffle-codec-utils"; import partition from "lodash.partition"; import { AbiCoder } from "web3-eth-abi"; @@ -70,7 +70,7 @@ function allocateMembers(parentNode: AstDefinition, definitions: AstDefinition[] allocations[parentNode.id] = { definition: parentNode, members: memberAllocations, - length: dynamic ? DecodeUtils.EVM.WORD_SIZE : start, + length: dynamic ? CodecUtils.EVM.WORD_SIZE : start, dynamic }; @@ -92,7 +92,7 @@ export function abiSize(definition: AstDefinition, referenceDeclarations?: AstRe //third return value is resulting allocations, INCLUDING the ones passed in //TODO: add error handling function abiSizeAndAllocate(definition: AstDefinition, referenceDeclarations?: AstReferences, existingAllocations?: Allocations.AbiAllocations): [number | undefined, boolean | undefined, Allocations.AbiAllocations] { - switch (DecodeUtils.Definition.typeClass(definition)) { + switch (CodecUtils.Definition.typeClass(definition)) { case "bool": case "address": case "contract": @@ -101,33 +101,33 @@ function abiSizeAndAllocate(definition: AstDefinition, referenceDeclarations?: A case "fixed": case "ufixed": case "enum": - return [DecodeUtils.EVM.WORD_SIZE, false, existingAllocations]; + return [CodecUtils.EVM.WORD_SIZE, false, existingAllocations]; case "string": - return [DecodeUtils.EVM.WORD_SIZE, true, existingAllocations]; + return [CodecUtils.EVM.WORD_SIZE, true, existingAllocations]; case "bytes": - return [DecodeUtils.EVM.WORD_SIZE, DecodeUtils.Definition.specifiedSize(definition) == null, + return [CodecUtils.EVM.WORD_SIZE, CodecUtils.Definition.specifiedSize(definition) == null, existingAllocations]; case "mapping": return [undefined, undefined, existingAllocations]; case "function": - switch (DecodeUtils.Definition.visibility(definition)) { + switch (CodecUtils.Definition.visibility(definition)) { case "external": - return [DecodeUtils.EVM.WORD_SIZE, false, existingAllocations]; + return [CodecUtils.EVM.WORD_SIZE, false, existingAllocations]; case "internal": return [undefined, undefined, existingAllocations]; } case "array": { - if(DecodeUtils.Definition.isDynamicArray(definition)) { - return [DecodeUtils.EVM.WORD_SIZE, true, existingAllocations]; + if(CodecUtils.Definition.isDynamicArray(definition)) { + return [CodecUtils.EVM.WORD_SIZE, true, existingAllocations]; } else { //static array case - const length: number = DecodeUtils.Definition.staticLength(definition); + const length: number = CodecUtils.Definition.staticLength(definition); if(length === 0) { //arrays of length 0 are static regardless of base type return [0, false, existingAllocations]; @@ -139,7 +139,7 @@ function abiSizeAndAllocate(definition: AstDefinition, referenceDeclarations?: A } case "struct": { - const referenceId: number = DecodeUtils.Definition.typeId(definition); + const referenceId: number = CodecUtils.Definition.typeId(definition); let allocations: Allocations.AbiAllocations = existingAllocations; let allocation: Allocations.AbiAllocation | null | undefined = allocations[referenceId]; if(allocation === undefined) { @@ -163,12 +163,12 @@ function abiSizeAndAllocate(definition: AstDefinition, referenceDeclarations?: A //like abiSize, but for a Type object; also assumes you've already done allocation //(note: function for dynamic is separate, see below) //also, does not attempt to handle types that don't occur in calldata -export function abiSizeForType(dataType: DecodeUtils.Types.Type, allocations?: Allocations.AbiAllocations): number { +export function abiSizeForType(dataType: CodecUtils.Types.Type, allocations?: Allocations.AbiAllocations): number { switch(dataType.typeClass) { case "array": switch(dataType.kind) { case "dynamic": - return DecodeUtils.EVM.WORD_SIZE; + return CodecUtils.EVM.WORD_SIZE; case "static": const length = dataType.length.toNumber(); //if this is too big, we have a problem! const baseSize = abiSizeForType(dataType.baseType, allocations); @@ -177,18 +177,18 @@ export function abiSizeForType(dataType: DecodeUtils.Types.Type, allocations?: A case "struct": const allocation = allocations[dataType.id]; if(!allocation) { - throw new DecodeUtils.Values.DecodingError( - new DecodeUtils.Values.UserDefinedTypeNotFoundError(dataType) + throw new CodecUtils.Values.DecodingError( + new CodecUtils.Values.UserDefinedTypeNotFoundError(dataType) ); } return allocation.length; default: - return DecodeUtils.EVM.WORD_SIZE; + return CodecUtils.EVM.WORD_SIZE; } } //again, this function does not attempt to handle types that don't occur in the abi -export function isTypeDynamic(dataType: DecodeUtils.Types.Type, allocations?: Allocations.AbiAllocations): boolean { +export function isTypeDynamic(dataType: CodecUtils.Types.Type, allocations?: Allocations.AbiAllocations): boolean { switch(dataType.typeClass) { case "string": return true; @@ -199,8 +199,8 @@ export function isTypeDynamic(dataType: DecodeUtils.Types.Type, allocations?: Al case "struct": const allocation = allocations[dataType.id]; if(!allocation) { - throw new DecodeUtils.Values.DecodingError( - new DecodeUtils.Values.UserDefinedTypeNotFoundError(dataType) + throw new CodecUtils.Values.DecodingError( + new CodecUtils.Values.UserDefinedTypeNotFoundError(dataType) ); } return allocation.dynamic; @@ -218,7 +218,7 @@ function allocateCalldata( contractId: number, referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations, - constructorContext?: DecodeUtils.Contexts.DecoderContext + constructorContext?: CodecUtils.Contexts.DecoderContext ): Allocations.CalldataAllocation { const linearizedBaseContracts = referenceDeclarations[contractId].linearizedBaseContracts; //first: determine the corresponding function node @@ -232,18 +232,18 @@ function allocateCalldata( //for a constructor, we only want to search the particular contract, which let contractNode = referenceDeclarations[contractId]; node = contractNode.nodes.find( - functionNode => DecodeUtils.Contexts.matchesAbi( + functionNode => CodecUtils.Contexts.matchesAbi( abiEntry, functionNode, referenceDeclarations ) ); //TODO: handle case if node undefined break; case "function": - offset = DecodeUtils.EVM.SELECTOR_SIZE; + offset = CodecUtils.EVM.SELECTOR_SIZE; //search through base contracts, from most derived (right) to most base (left) node = linearizedBaseContracts.reduceRight( (foundNode, baseContractId) => foundNode || referenceDeclarations[baseContractId].nodes.find( - functionNode => DecodeUtils.Contexts.matchesAbi( + functionNode => CodecUtils.Contexts.matchesAbi( abiEntry, functionNode, referenceDeclarations ) ), @@ -291,7 +291,7 @@ function allocateEvent( //search through base contracts, from most derived (right) to most base (left) const node: AstDefinition = linearizedBaseContracts.reduceRight( (foundNode, baseContractId) => foundNode || referenceDeclarations[baseContractId].nodes.find( - eventNode => DecodeUtils.Contexts.matchesAbi( + eventNode => CodecUtils.Contexts.matchesAbi( abiEntry, eventNode, referenceDeclarations ) ), @@ -343,7 +343,7 @@ function allocateEvent( function getCalldataAllocationsForContract( abi: AbiItem[], contractId: number, - constructorContext: DecodeUtils.Contexts.DecoderContext, + constructorContext: CodecUtils.Contexts.DecoderContext, referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations ): Allocations.CalldataContractAllocation { @@ -377,7 +377,7 @@ function getCalldataAllocationsForContract( return allocations; } -function defaultConstructorAllocation(constructorContext: DecodeUtils.Contexts.DecoderContext) { +function defaultConstructorAllocation(constructorContext: CodecUtils.Contexts.DecoderContext) { let rawLength = constructorContext.binary.length; let offset = (rawLength - 2)/2; //number of bytes in 0x-prefixed bytestring return { diff --git a/packages/truffle-codec/lib/allocate/memory.ts b/packages/truffle-codec/lib/allocate/memory.ts index 08fd8b4807e..119e3b71453 100644 --- a/packages/truffle-codec/lib/allocate/memory.ts +++ b/packages/truffle-codec/lib/allocate/memory.ts @@ -3,8 +3,8 @@ const debug = debugModule("decoder-core:allocate:memory"); import { MemoryPointer } from "../types/pointer"; import { MemoryAllocations, MemoryAllocation, MemoryMemberAllocation } from "../types/allocation"; -import { AstDefinition, AstReferences } from "truffle-decode-utils"; -import * as DecodeUtils from "truffle-decode-utils"; +import { AstDefinition, AstReferences } from "truffle-codec-utils"; +import * as CodecUtils from "truffle-codec-utils"; export function getMemoryAllocations(referenceDeclarations: AstReferences): MemoryAllocations { let allocations: MemoryAllocations = {}; @@ -22,7 +22,7 @@ function allocateStruct(definition: AstDefinition): MemoryAllocation { let memberAllocations: MemoryMemberAllocation[] = []; let nonMappingIndex = 0; for(const member of definition.members) { - if(DecodeUtils.Definition.isMapping(member)) { + if(CodecUtils.Definition.isMapping(member)) { memberAllocations.push({ definition: member, pointer: null @@ -33,8 +33,8 @@ function allocateStruct(definition: AstDefinition): MemoryAllocation { definition: member, pointer: { location: "memory", - start: nonMappingIndex * DecodeUtils.EVM.WORD_SIZE, - length: DecodeUtils.EVM.WORD_SIZE + start: nonMappingIndex * CodecUtils.EVM.WORD_SIZE, + length: CodecUtils.EVM.WORD_SIZE } }); nonMappingIndex++; diff --git a/packages/truffle-codec/lib/allocate/storage.ts b/packages/truffle-codec/lib/allocate/storage.ts index 9df65b487bd..9502e84e645 100644 --- a/packages/truffle-codec/lib/allocate/storage.ts +++ b/packages/truffle-codec/lib/allocate/storage.ts @@ -5,9 +5,9 @@ import { StoragePointer } from "../types/pointer"; import { StorageAllocations, StorageAllocation, StorageMemberAllocation } from "../types/allocation"; import { StorageLength, isWordsLength, Range } from "../types/storage"; import { UnknownBaseContractIdError, UnknownUserDefinedTypeError } from "../types/errors"; -import { AstDefinition, AstReferences } from "truffle-decode-utils"; +import { AstDefinition, AstReferences } from "truffle-codec-utils"; import { readDefinition } from "../read/constant" -import * as DecodeUtils from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; import BN from "bn.js"; //contracts contains only the contracts to be allocated; any base classes not @@ -31,7 +31,7 @@ function allocateStruct(structDefinition: AstDefinition, referenceDeclarations: function allocateMembers(parentNode: AstDefinition, definitions: AstDefinition[], referenceDeclarations: AstReferences, existingAllocations: StorageAllocations, suppressSize: boolean = false): StorageAllocations { let offset: number = 0; //will convert to BN when placing in slot - let index: number = DecodeUtils.EVM.WORD_SIZE - 1; + let index: number = CodecUtils.EVM.WORD_SIZE - 1; //don't allocate things that have already been allocated if(parentNode.id in existingAllocations) { @@ -50,7 +50,7 @@ function allocateMembers(parentNode: AstDefinition, definitions: AstDefinition[] if(node.constant) { let pointer = { location: "definition" as "definition", definition: node.value }; //HACK restrict ourselves to the types of constants we know how to handle - if(DecodeUtils.Definition.isSimpleConstant(node.value)) { + if(CodecUtils.Definition.isSimpleConstant(node.value)) { memberAllocations.push({definition: node, pointer}); } //if we don't know how to handle it, we just ignore it @@ -63,9 +63,9 @@ function allocateMembers(parentNode: AstDefinition, definitions: AstDefinition[] //if it's sized in words (and we're not at the start of slot) we need to start on a new slot //if it's sized in bytes but there's not enough room, we also need a new slot if ( isWordsLength(size) - ? index < DecodeUtils.EVM.WORD_SIZE - 1 + ? index < CodecUtils.EVM.WORD_SIZE - 1 : size.bytes > index + 1) { - index = DecodeUtils.EVM.WORD_SIZE - 1; + index = CodecUtils.EVM.WORD_SIZE - 1; offset += 1; } //otherwise, we remain in place @@ -85,7 +85,7 @@ function allocateMembers(parentNode: AstDefinition, definitions: AstDefinition[] slot: { offset: new BN(offset + size.words - 1) //end at the current slot plus # of words minus 1... }, - index: DecodeUtils.EVM.WORD_SIZE - 1 //...at the end of the word. + index: CodecUtils.EVM.WORD_SIZE - 1 //...at the end of the word. }, }; } @@ -119,14 +119,14 @@ function allocateMembers(parentNode: AstDefinition, definitions: AstDefinition[] //if it was sized in words, move down that many slots and reset position w/in slot if(isWordsLength(size)) { offset += size.words; - index = DecodeUtils.EVM.WORD_SIZE - 1; + index = CodecUtils.EVM.WORD_SIZE - 1; } //if it was sized in bytes, move down an appropriate number of bytes. else { index -= size.bytes; //but if this puts us into the next word, move to the next word. if(index < 0) { - index = DecodeUtils.EVM.WORD_SIZE - 1; + index = CodecUtils.EVM.WORD_SIZE - 1; offset += 1; } } @@ -145,7 +145,7 @@ function allocateMembers(parentNode: AstDefinition, definitions: AstDefinition[] //SPECIAL CASE: if *nothing* has been used, allocate a single word (that's how //empty structs behave in versions where they're legal) if(!suppressSize) { - if(index === DecodeUtils.EVM.WORD_SIZE - 1 && offset !== 0) { + if(index === CodecUtils.EVM.WORD_SIZE - 1 && offset !== 0) { allocations[parentNode.id].size = {words: offset}; } else { @@ -195,35 +195,35 @@ export function storageSize(definition: AstDefinition, referenceDeclarations?: A //first return value is the actual size. //second return value is resulting allocations, INCLUDING the ones passed in function storageSizeAndAllocate(definition: AstDefinition, referenceDeclarations?: AstReferences, existingAllocations?: StorageAllocations): [StorageLength, StorageAllocations] { - switch (DecodeUtils.Definition.typeClass(definition)) { + switch (CodecUtils.Definition.typeClass(definition)) { case "bool": return [{bytes: 1}, existingAllocations]; case "address": case "contract": - return [{bytes: DecodeUtils.EVM.ADDRESS_SIZE}, existingAllocations]; + return [{bytes: CodecUtils.EVM.ADDRESS_SIZE}, existingAllocations]; case "int": case "uint": { - return [{bytes: DecodeUtils.Definition.specifiedSize(definition) || 32 }, existingAllocations]; // default of 256 bits + return [{bytes: CodecUtils.Definition.specifiedSize(definition) || 32 }, existingAllocations]; // default of 256 bits //(should 32 here be WORD_SIZE? I thought so, but comparing with case //of fixed/ufixed makes the appropriate generalization less clear) } case "fixed": case "ufixed": { - return [{bytes: DecodeUtils.Definition.specifiedSize(definition) || 16 }, existingAllocations]; // default of 128 bits + return [{bytes: CodecUtils.Definition.specifiedSize(definition) || 16 }, existingAllocations]; // default of 128 bits } case "enum": { debug("enum definition %O", definition); - const referenceId: number = DecodeUtils.Definition.typeId(definition); + const referenceId: number = CodecUtils.Definition.typeId(definition); //note: we use the preexisting function here for convenience, but we //should never need to worry about faked-up enum definitions, so just //checking the referencedDeclaration field would also work const referenceDeclaration: AstDefinition = referenceDeclarations[referenceId]; if(referenceDeclaration === undefined) { - let typeString = DecodeUtils.Definition.typeString(definition); + let typeString = CodecUtils.Definition.typeString(definition); throw new UnknownUserDefinedTypeError(referenceId, typeString); } const numValues: number = referenceDeclaration.members.length; @@ -232,7 +232,7 @@ function storageSizeAndAllocate(definition: AstDefinition, referenceDeclarations case "bytes": { //this case is really two different cases! - const staticSize: number = DecodeUtils.Definition.specifiedSize(definition); + const staticSize: number = CodecUtils.Definition.specifiedSize(definition); if(staticSize) { return [{bytes: staticSize}, existingAllocations]; } @@ -250,30 +250,30 @@ function storageSizeAndAllocate(definition: AstDefinition, referenceDeclarations case "function": { //this case is also really two different cases - switch (DecodeUtils.Definition.visibility(definition)) { + switch (CodecUtils.Definition.visibility(definition)) { case "internal": - return [{bytes: DecodeUtils.EVM.PC_SIZE * 2}, existingAllocations]; + return [{bytes: CodecUtils.EVM.PC_SIZE * 2}, existingAllocations]; case "external": - return [{bytes: DecodeUtils.EVM.ADDRESS_SIZE + DecodeUtils.EVM.SELECTOR_SIZE}, existingAllocations]; + return [{bytes: CodecUtils.EVM.ADDRESS_SIZE + CodecUtils.EVM.SELECTOR_SIZE}, existingAllocations]; } } case "array": { - if(DecodeUtils.Definition.isDynamicArray(definition)) { + if(CodecUtils.Definition.isDynamicArray(definition)) { return [{words: 1}, existingAllocations]; } else { //static array case - const length: number = DecodeUtils.Definition.staticLength(definition); + const length: number = CodecUtils.Definition.staticLength(definition); if(length === 0) { //in versions of Solidity where it's legal, arrays of length 0 still take up 1 word return [{words: 1}, existingAllocations]; } - const baseDefinition: AstDefinition = DecodeUtils.Definition.baseDefinition(definition); + const baseDefinition: AstDefinition = CodecUtils.Definition.baseDefinition(definition); const [baseSize, allocations] = storageSizeAndAllocate(baseDefinition, referenceDeclarations, existingAllocations); if(!isWordsLength(baseSize)) { //bytes case - const perWord: number = Math.floor(DecodeUtils.EVM.WORD_SIZE / baseSize.bytes); + const perWord: number = Math.floor(CodecUtils.EVM.WORD_SIZE / baseSize.bytes); debug("length %o", length); const numWords: number = Math.ceil(length / perWord); return [{words: numWords}, allocations]; @@ -286,14 +286,14 @@ function storageSizeAndAllocate(definition: AstDefinition, referenceDeclarations } case "struct": { - const referenceId: number = DecodeUtils.Definition.typeId(definition); + const referenceId: number = CodecUtils.Definition.typeId(definition); let allocations: StorageAllocations = existingAllocations; let allocation: StorageAllocation | undefined = allocations[referenceId]; //may be undefined! if(allocation === undefined) { //if we don't find an allocation, we'll have to do the allocation ourselves const referenceDeclaration: AstDefinition = referenceDeclarations[referenceId]; if(referenceDeclaration === undefined) { - let typeString = DecodeUtils.Definition.typeString(definition); + let typeString = CodecUtils.Definition.typeString(definition); throw new UnknownUserDefinedTypeError(referenceId, typeString); } debug("definition %O", definition); @@ -307,23 +307,23 @@ function storageSizeAndAllocate(definition: AstDefinition, referenceDeclarations } //like storageSize, but for a Type object; also assumes you've already done allocation -export function storageSizeForType(dataType: DecodeUtils.Types.Type, userDefinedTypes?: DecodeUtils.Types.TypesById, allocations?: StorageAllocations): StorageLength { +export function storageSizeForType(dataType: CodecUtils.Types.Type, userDefinedTypes?: CodecUtils.Types.TypesById, allocations?: StorageAllocations): StorageLength { switch(dataType.typeClass) { case "bool": return {bytes: 1}; case "address": case "contract": - return {bytes: DecodeUtils.EVM.ADDRESS_SIZE}; + return {bytes: CodecUtils.EVM.ADDRESS_SIZE}; case "int": case "uint": case "fixed": case "ufixed": return {bytes: dataType.bits / 8 }; case "enum": { - let fullType = DecodeUtils.Types.fullType(dataType, userDefinedTypes); + let fullType = CodecUtils.Types.fullType(dataType, userDefinedTypes); if(!fullType.options) { - throw new DecodeUtils.Values.DecodingError( - new DecodeUtils.Values.UserDefinedTypeNotFoundError(dataType) + throw new CodecUtils.Values.DecodingError( + new CodecUtils.Values.UserDefinedTypeNotFoundError(dataType) ); } return {bytes: Math.ceil(Math.log2(fullType.options.length) / 8)}; @@ -331,9 +331,9 @@ export function storageSizeForType(dataType: DecodeUtils.Types.Type, userDefined case "function": switch (dataType.visibility) { case "internal": - return {bytes: DecodeUtils.EVM.PC_SIZE * 2}; + return {bytes: CodecUtils.EVM.PC_SIZE * 2}; case "external": - return {bytes: DecodeUtils.EVM.ADDRESS_SIZE + DecodeUtils.EVM.SELECTOR_SIZE}; + return {bytes: CodecUtils.EVM.ADDRESS_SIZE + CodecUtils.EVM.SELECTOR_SIZE}; } break; //to satisfy typescript :P case "bytes": @@ -358,7 +358,7 @@ export function storageSizeForType(dataType: DecodeUtils.Types.Type, userDefined let baseSize = storageSizeForType(dataType.baseType, userDefinedTypes, allocations); if(!isWordsLength(baseSize)) { //bytes case - const perWord: number = Math.floor(DecodeUtils.EVM.WORD_SIZE / baseSize.bytes); + const perWord: number = Math.floor(CodecUtils.EVM.WORD_SIZE / baseSize.bytes); debug("length %o", length); const numWords: number = Math.ceil(length / perWord); return {words: numWords}; @@ -371,8 +371,8 @@ export function storageSizeForType(dataType: DecodeUtils.Types.Type, userDefined case "struct": let allocation = allocations[dataType.id]; if(!allocation) { - throw new DecodeUtils.Values.DecodingError( - new DecodeUtils.Values.UserDefinedTypeNotFoundError(dataType) + throw new CodecUtils.Values.DecodingError( + new CodecUtils.Values.UserDefinedTypeNotFoundError(dataType) ); } return allocation.size; diff --git a/packages/truffle-codec/lib/decode/abi.ts b/packages/truffle-codec/lib/decode/abi.ts index 25ad734a1fb..2fd56e55fc6 100644 --- a/packages/truffle-codec/lib/decode/abi.ts +++ b/packages/truffle-codec/lib/decode/abi.ts @@ -2,8 +2,8 @@ import debugModule from "debug"; const debug = debugModule("decoder-core:decode:abi"); import read from "../read"; -import * as DecodeUtils from "truffle-decode-utils"; -import { Types, Values } from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import decodeValue from "./value"; import { AbiPointer, DataPointer } from "../types/pointer"; import { AbiMemberAllocation } from "../types/allocation"; @@ -49,7 +49,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin return Values.makeGenericErrorResult(dataType, error.error); } - let startPosition = DecodeUtils.Conversion.toBN(rawValue).toNumber() + base; + let startPosition = CodecUtils.Conversion.toBN(rawValue).toNumber() + base; debug("startPosition %d", startPosition); let dynamic: boolean; @@ -85,17 +85,17 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin rawLength = (yield* read({ location, start: startPosition, - length: DecodeUtils.EVM.WORD_SIZE + length: CodecUtils.EVM.WORD_SIZE }, state)); } catch(error) { //error: Values.DecodingError return Values.makeGenericErrorResult(dataType, error.error); } - length = DecodeUtils.Conversion.toBN(rawLength).toNumber(); + length = CodecUtils.Conversion.toBN(rawLength).toNumber(); let childPointer: AbiPointer = { location, - start: startPosition + DecodeUtils.EVM.WORD_SIZE, + start: startPosition + CodecUtils.EVM.WORD_SIZE, length } @@ -110,14 +110,14 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin rawLength = (yield* read({ location, start: startPosition, - length: DecodeUtils.EVM.WORD_SIZE + length: CodecUtils.EVM.WORD_SIZE }, state)); } catch(error) { //error: Values.DecodingError return Values.makeGenericErrorResult(dataType, error.error); } - length = DecodeUtils.Conversion.toBN(rawLength).toNumber(); - startPosition += DecodeUtils.EVM.WORD_SIZE; //increment startPosition + length = CodecUtils.Conversion.toBN(rawLength).toNumber(); + startPosition += CodecUtils.EVM.WORD_SIZE; //increment startPosition //to next word, as first word was used for length break; case "static": diff --git a/packages/truffle-codec/lib/decode/constant.ts b/packages/truffle-codec/lib/decode/constant.ts index c37cffe5438..6bed4b191d2 100644 --- a/packages/truffle-codec/lib/decode/constant.ts +++ b/packages/truffle-codec/lib/decode/constant.ts @@ -1,8 +1,8 @@ import debugModule from "debug"; const debug = debugModule("decoder-core:decode:constant"); -import * as DecodeUtils from "truffle-decode-utils"; -import { Types, Values } from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import read from "../read"; import decodeValue from "./value"; import { ConstantDefinitionPointer} from "../types/pointer"; @@ -30,10 +30,10 @@ export default function* decodeConstant(dataType: Types.Type, pointer: ConstantD return Values.makeGenericErrorResult(dataType, error.error); } //not bothering to check padding; shouldn't be necessary - let bytes = word.slice(DecodeUtils.EVM.WORD_SIZE - size); + let bytes = word.slice(CodecUtils.EVM.WORD_SIZE - size); return new Values.BytesValue( dataType, - DecodeUtils.Conversion.toHexString(bytes) + CodecUtils.Conversion.toHexString(bytes) ); } diff --git a/packages/truffle-codec/lib/decode/event.ts b/packages/truffle-codec/lib/decode/event.ts index 8ec2291a312..167b363de94 100644 --- a/packages/truffle-codec/lib/decode/event.ts +++ b/packages/truffle-codec/lib/decode/event.ts @@ -3,7 +3,7 @@ const debug = debugModule("decoder-core:decode:event"); import decodeValue from "./value"; import read from "../read"; -import { Types, Values, Conversion as ConversionUtils } from "truffle-decode-utils"; +import { Types, Values, Conversion as ConversionUtils } from "truffle-codec-utils"; import { EventTopicPointer } from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; diff --git a/packages/truffle-codec/lib/decode/index.ts b/packages/truffle-codec/lib/decode/index.ts index d5b7dc646fa..ecf71054aaf 100644 --- a/packages/truffle-codec/lib/decode/index.ts +++ b/packages/truffle-codec/lib/decode/index.ts @@ -10,7 +10,7 @@ import decodeAbi from "./abi"; import decodeConstant from "./constant"; import decodeSpecial from "./special"; import decodeTopic from "./event"; -import { Types, Values } from "truffle-decode-utils"; +import { Types, Values } from "truffle-codec-utils"; import * as Pointer from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; diff --git a/packages/truffle-codec/lib/decode/memory.ts b/packages/truffle-codec/lib/decode/memory.ts index a72d5456534..99eeaf1eb6f 100644 --- a/packages/truffle-codec/lib/decode/memory.ts +++ b/packages/truffle-codec/lib/decode/memory.ts @@ -2,8 +2,8 @@ import debugModule from "debug"; const debug = debugModule("decoder-core:decode:memory"); import read from "../read"; -import * as DecodeUtils from "truffle-decode-utils"; -import { Types, Values } from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import decodeValue from "./value"; import { MemoryPointer, DataPointer } from "../types/pointer"; import { MemoryMemberAllocation } from "../types/allocation"; @@ -30,7 +30,7 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p return Values.makeGenericErrorResult(dataType, error.error); } - let startPosition = DecodeUtils.Conversion.toBN(rawValue).toNumber(); + let startPosition = CodecUtils.Conversion.toBN(rawValue).toNumber(); let rawLength: Uint8Array; let length: number; @@ -43,17 +43,17 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p rawLength = yield* read({ location: "memory", start: startPosition, - length: DecodeUtils.EVM.WORD_SIZE + length: CodecUtils.EVM.WORD_SIZE }, state); } catch(error) { //error: Values.DecodingError return Values.makeGenericErrorResult(dataType, error.error); } - length = DecodeUtils.Conversion.toBN(rawLength).toNumber(); + length = CodecUtils.Conversion.toBN(rawLength).toNumber(); let childPointer: MemoryPointer = { location: "memory", - start: startPosition + DecodeUtils.EVM.WORD_SIZE, + start: startPosition + CodecUtils.EVM.WORD_SIZE, length } @@ -67,14 +67,14 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p rawLength = yield* read({ location: "memory", start: startPosition, - length: DecodeUtils.EVM.WORD_SIZE + length: CodecUtils.EVM.WORD_SIZE }, state); } catch(error) { //error: Values.DecodingError return Values.makeGenericErrorResult(dataType, error.error); } - length = DecodeUtils.Conversion.toBN(rawLength).toNumber(); - startPosition += DecodeUtils.EVM.WORD_SIZE; //increment startPosition + length = CodecUtils.Conversion.toBN(rawLength).toNumber(); + startPosition += CodecUtils.EVM.WORD_SIZE; //increment startPosition //to next word, as first word was used for length } else { @@ -90,8 +90,8 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p baseType, { location: "memory", - start: startPosition + index * DecodeUtils.EVM.WORD_SIZE, - length: DecodeUtils.EVM.WORD_SIZE + start: startPosition + index * CodecUtils.EVM.WORD_SIZE, + length: CodecUtils.EVM.WORD_SIZE }, info )) diff --git a/packages/truffle-codec/lib/decode/special.ts b/packages/truffle-codec/lib/decode/special.ts index 965f3c1d093..e834791995d 100644 --- a/packages/truffle-codec/lib/decode/special.ts +++ b/packages/truffle-codec/lib/decode/special.ts @@ -1,8 +1,8 @@ import debugModule from "debug"; const debug = debugModule("decoder-core:decode:special"); -import * as DecodeUtils from "truffle-decode-utils"; -import { Types, Values } from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import decodeValue from "./value"; import { EvmInfo } from "../types/evm"; import { SpecialPointer } from "../types/pointer"; @@ -42,12 +42,12 @@ export function* decodeMagic(dataType: Types.MagicType, pointer: SpecialPointer, { typeClass: "bytes", kind: "static", - length: DecodeUtils.EVM.SELECTOR_SIZE + length: CodecUtils.EVM.SELECTOR_SIZE }, { location: "calldata", start: 0, - length: DecodeUtils.EVM.SELECTOR_SIZE, + length: CodecUtils.EVM.SELECTOR_SIZE, }, info )), diff --git a/packages/truffle-codec/lib/decode/stack.ts b/packages/truffle-codec/lib/decode/stack.ts index de4a6a0a79c..79b15a85dcd 100644 --- a/packages/truffle-codec/lib/decode/stack.ts +++ b/packages/truffle-codec/lib/decode/stack.ts @@ -1,8 +1,8 @@ import debugModule from "debug"; const debug = debugModule("decoder-core:decode:stack"); -import * as DecodeUtils from "truffle-decode-utils"; -import { Types, Values } from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import read from "../read"; import decodeValue from "./value"; import { decodeExternalFunction, checkPaddingLeft } from "./value"; @@ -49,8 +49,8 @@ export function* decodeLiteral(dataType: Types.Type, pointer: StackLiteralPointe //straight to decodeValue. this is to allow us to correctly handle the //case of msg.data used as a mapping key. if(dataType.typeClass === "bytes" || dataType.typeClass === "string") { - let start = DecodeUtils.Conversion.toBN(pointer.literal.slice(0, DecodeUtils.EVM.WORD_SIZE)).toNumber(); - let length = DecodeUtils.Conversion.toBN(pointer.literal.slice(DecodeUtils.EVM.WORD_SIZE)).toNumber(); + let start = CodecUtils.Conversion.toBN(pointer.literal.slice(0, CodecUtils.EVM.WORD_SIZE)).toNumber(); + let length = CodecUtils.Conversion.toBN(pointer.literal.slice(CodecUtils.EVM.WORD_SIZE)).toNumber(); let newPointer = { location: "calldata" as "calldata", start, length }; return yield* decodeValue(dataType, newPointer, info); } @@ -60,11 +60,11 @@ export function* decodeLiteral(dataType: Types.Type, pointer: StackLiteralPointe //in this case, we're actually going to *throw away* the length info, //because it makes the logic simpler -- we'll get the length info back //from calldata - let locationOnly = pointer.literal.slice(0, DecodeUtils.EVM.WORD_SIZE); + let locationOnly = pointer.literal.slice(0, CodecUtils.EVM.WORD_SIZE); //HACK -- in order to read the correct location, we need to add an offset //of -32 (since, again, we're throwing away the length info), so we pass //that in as the "base" value - return yield* decodeAbiReferenceByAddress(dataType, {location: "stackliteral", literal: locationOnly}, info, -DecodeUtils.EVM.WORD_SIZE); + return yield* decodeAbiReferenceByAddress(dataType, {location: "stackliteral", literal: locationOnly}, info, -CodecUtils.EVM.WORD_SIZE); } else { //multivalue case -- this case is straightforward @@ -77,19 +77,19 @@ export function* decodeLiteral(dataType: Types.Type, pointer: StackLiteralPointe //next: do we have an external function? these work differently on the stack //than elsewhere, so we can't just pass it on to decodeValue. if(dataType.typeClass === "function" && dataType.visibility === "external") { - let address = pointer.literal.slice(0, DecodeUtils.EVM.WORD_SIZE); - let selectorWord = pointer.literal.slice(-DecodeUtils.EVM.WORD_SIZE); - if(!checkPaddingLeft(address, DecodeUtils.EVM.ADDRESS_SIZE) - ||!checkPaddingLeft(selectorWord, DecodeUtils.EVM.SELECTOR_SIZE)) { + let address = pointer.literal.slice(0, CodecUtils.EVM.WORD_SIZE); + let selectorWord = pointer.literal.slice(-CodecUtils.EVM.WORD_SIZE); + if(!checkPaddingLeft(address, CodecUtils.EVM.ADDRESS_SIZE) + ||!checkPaddingLeft(selectorWord, CodecUtils.EVM.SELECTOR_SIZE)) { return new Values.FunctionExternalErrorResult( dataType, new Values.FunctionExternalStackPaddingError( - DecodeUtils.Conversion.toHexString(address), - DecodeUtils.Conversion.toHexString(selectorWord) + CodecUtils.Conversion.toHexString(address), + CodecUtils.Conversion.toHexString(selectorWord) ) ); } - let selector = selectorWord.slice(-DecodeUtils.EVM.SELECTOR_SIZE); + let selector = selectorWord.slice(-CodecUtils.EVM.SELECTOR_SIZE); return new Values.FunctionExternalValue( dataType, (yield* decodeExternalFunction(address, selector, info)) diff --git a/packages/truffle-codec/lib/decode/storage.ts b/packages/truffle-codec/lib/decode/storage.ts index 54cffb97204..db33856bb13 100644 --- a/packages/truffle-codec/lib/decode/storage.ts +++ b/packages/truffle-codec/lib/decode/storage.ts @@ -2,8 +2,8 @@ import debugModule from "debug"; const debug = debugModule("decoder-core:decode:storage"); import read from "../read"; -import * as DecodeUtils from "truffle-decode-utils"; -import { Types, Values } from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import decodeValue from "./value"; import { StoragePointer, DataPointer } from "../types/pointer"; import { EvmInfo } from "../types/evm"; @@ -36,7 +36,7 @@ export function* decodeStorageReferenceByAddress(dataType: Types.ReferenceType, catch(error) { //error: Values.DecodingError return Values.makeGenericErrorResult(dataType, error.error); } - const startOffset = DecodeUtils.Conversion.toBN(rawValue); + const startOffset = CodecUtils.Conversion.toBN(rawValue); let rawSize: StorageTypes.StorageLength; try { rawSize = storageSizeForType(dataType, info.userDefinedTypes, allocations); @@ -60,7 +60,7 @@ export function* decodeStorageReferenceByAddress(dataType: Types.ReferenceType, slot: { offset: startOffset.addn(size - 1) }, - index: DecodeUtils.EVM.WORD_SIZE - 1 + index: CodecUtils.EVM.WORD_SIZE - 1 } }}; //dispatch to decodeStorageReference @@ -87,7 +87,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: catch(error) { //error: Values.DecodingError return Values.makeGenericErrorResult(dataType, error.error); } - length = DecodeUtils.Conversion.toBN(data).toNumber(); + length = CodecUtils.Conversion.toBN(data).toNumber(); break; case "static": debug("static array"); @@ -135,7 +135,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: offset: currentSlot.offset.addn(baseSize.words - 1), hashPath: currentSlot.hashPath }, - index: DecodeUtils.EVM.WORD_SIZE - 1 + index: CodecUtils.EVM.WORD_SIZE - 1 }, }; @@ -146,18 +146,18 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: } else { - const perWord = Math.floor(DecodeUtils.EVM.WORD_SIZE / baseSize.bytes); + const perWord = Math.floor(CodecUtils.EVM.WORD_SIZE / baseSize.bytes); debug("perWord %d", perWord); //currentPosition will point to the start of the entry being decoded - //note we have baseSize.bytes <= DecodeUtils.EVM.WORD_SIZE + //note we have baseSize.bytes <= CodecUtils.EVM.WORD_SIZE let currentPosition: StorageTypes.StoragePosition = { slot: { path: pointer.range.from.slot, offset: new BN(0), hashPath: dataType.kind === "dynamic" }, - index: DecodeUtils.EVM.WORD_SIZE - baseSize.bytes //note the starting index! + index: CodecUtils.EVM.WORD_SIZE - baseSize.bytes //note the starting index! }; for (let i = 0; i < length; i++) { @@ -178,7 +178,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: currentPosition.index -= baseSize.bytes; if (currentPosition.index < 0) { currentPosition.slot.offset.iaddn(1); - currentPosition.index = DecodeUtils.EVM.WORD_SIZE - baseSize.bytes; + currentPosition.index = CodecUtils.EVM.WORD_SIZE - baseSize.bytes; } } } @@ -204,7 +204,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: } debug("data %O", data); - let lengthByte = data[DecodeUtils.EVM.WORD_SIZE - 1]; + let lengthByte = data[CodecUtils.EVM.WORD_SIZE - 1]; if (lengthByte % 2 == 0) { // string lives in word, length is last byte / 2 @@ -217,7 +217,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: }}, info); } else { - length = DecodeUtils.Conversion.toBN(data).subn(1).divn(2).toNumber(); + length = CodecUtils.Conversion.toBN(data).subn(1).divn(2).toNumber(); debug("new-word, length %o", length); return yield* decodeValue(dataType, { location: "storage", @@ -344,7 +344,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: path: baseSlot, offset: new BN(valueSize.words - 1) }, - index: DecodeUtils.EVM.WORD_SIZE - 1 + index: CodecUtils.EVM.WORD_SIZE - 1 } } }; @@ -359,7 +359,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: path: baseSlot, offset: new BN(0) }, - index: DecodeUtils.EVM.WORD_SIZE - valueSize.bytes + index: CodecUtils.EVM.WORD_SIZE - valueSize.bytes }, to: { slot: { @@ -367,7 +367,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: path: baseSlot, offset: new BN(0) }, - index: DecodeUtils.EVM.WORD_SIZE - 1 + index: CodecUtils.EVM.WORD_SIZE - 1 } } }; diff --git a/packages/truffle-codec/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts index c3d56b0df31..5f5a03b2a14 100644 --- a/packages/truffle-codec/lib/decode/value.ts +++ b/packages/truffle-codec/lib/decode/value.ts @@ -2,8 +2,8 @@ import debugModule from "debug"; const debug = debugModule("decoder-core:decode:value"); import read from "../read"; -import * as DecodeUtils from "truffle-decode-utils"; -import { Types, Values } from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import BN from "bn.js"; import { DataPointer } from "../types/pointer"; import { EvmInfo } from "../types/evm"; @@ -32,10 +32,10 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, if(!checkPaddingLeft(bytes, 1)) { return new Values.BoolErrorResult( dataType, - new Values.BoolPaddingError(DecodeUtils.Conversion.toHexString(bytes)) + new Values.BoolPaddingError(CodecUtils.Conversion.toHexString(bytes)) ); } - const numeric = DecodeUtils.Conversion.toBN(bytes); + const numeric = CodecUtils.Conversion.toBN(bytes); if(numeric.eqn(0)) { return new Values.BoolValue(dataType, false); } @@ -55,38 +55,38 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, if(!permissivePadding && !checkPaddingLeft(bytes, dataType.bits/8)) { return new Values.UintErrorResult( dataType, - new Values.UintPaddingError(DecodeUtils.Conversion.toHexString(bytes)) + new Values.UintPaddingError(CodecUtils.Conversion.toHexString(bytes)) ); } //now, truncate to appropriate length (keeping the bytes on the right) bytes = bytes.slice(-dataType.bits/8); - return new Values.UintValue(dataType, DecodeUtils.Conversion.toBN(bytes)); + return new Values.UintValue(dataType, CodecUtils.Conversion.toBN(bytes)); case "int": //first, check padding (if needed) if(!permissivePadding && !checkPaddingSigned(bytes, dataType.bits/8)) { return new Values.IntErrorResult( dataType, - new Values.IntPaddingError(DecodeUtils.Conversion.toHexString(bytes)) + new Values.IntPaddingError(CodecUtils.Conversion.toHexString(bytes)) ); } //now, truncate to appropriate length (keeping the bytes on the right) bytes = bytes.slice(-dataType.bits/8); - return new Values.IntValue(dataType, DecodeUtils.Conversion.toSignedBN(bytes)); + return new Values.IntValue(dataType, CodecUtils.Conversion.toSignedBN(bytes)); case "address": - if(!permissivePadding && !checkPaddingLeft(bytes, DecodeUtils.EVM.ADDRESS_SIZE)) { + if(!permissivePadding && !checkPaddingLeft(bytes, CodecUtils.EVM.ADDRESS_SIZE)) { return new Values.AddressErrorResult( dataType, - new Values.AddressPaddingError(DecodeUtils.Conversion.toHexString(bytes)) + new Values.AddressPaddingError(CodecUtils.Conversion.toHexString(bytes)) ); } - return new Values.AddressValue(dataType, DecodeUtils.Conversion.toAddress(bytes)); + return new Values.AddressValue(dataType, CodecUtils.Conversion.toAddress(bytes)); case "contract": - if(!permissivePadding && !checkPaddingLeft(bytes, DecodeUtils.EVM.ADDRESS_SIZE)) { + if(!permissivePadding && !checkPaddingLeft(bytes, CodecUtils.EVM.ADDRESS_SIZE)) { return new Values.ContractErrorResult( dataType, - new Values.ContractPaddingError(DecodeUtils.Conversion.toHexString(bytes)) + new Values.ContractPaddingError(CodecUtils.Conversion.toHexString(bytes)) ); } const fullType = Types.fullType(dataType, info.userDefinedTypes); @@ -99,7 +99,7 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, if(!permissivePadding && !checkPaddingRight(bytes, dataType.length)) { return new Values.BytesErrorResult( dataType, - new Values.BytesPaddingError(DecodeUtils.Conversion.toHexString(bytes)) + new Values.BytesPaddingError(CodecUtils.Conversion.toHexString(bytes)) ); } //now, truncate to appropriate length @@ -107,7 +107,7 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, } //we don't need to pass in length to the conversion, since that's for *adding* padding //(there is also no padding check for dynamic bytes) - return new Values.BytesValue(dataType, DecodeUtils.Conversion.toHexString(bytes)); + return new Values.BytesValue(dataType, CodecUtils.Conversion.toHexString(bytes)); case "string": //there is no padding check for strings @@ -119,32 +119,32 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "function": switch(dataType.visibility) { case "external": - if(!checkPaddingRight(bytes, DecodeUtils.EVM.ADDRESS_SIZE + DecodeUtils.EVM.SELECTOR_SIZE)) { + if(!checkPaddingRight(bytes, CodecUtils.EVM.ADDRESS_SIZE + CodecUtils.EVM.SELECTOR_SIZE)) { return new Values.FunctionExternalErrorResult( dataType, - new Values.FunctionExternalNonStackPaddingError(DecodeUtils.Conversion.toHexString(bytes)) + new Values.FunctionExternalNonStackPaddingError(CodecUtils.Conversion.toHexString(bytes)) ); } - const address = bytes.slice(0, DecodeUtils.EVM.ADDRESS_SIZE); - const selector = bytes.slice(DecodeUtils.EVM.ADDRESS_SIZE, DecodeUtils.EVM.ADDRESS_SIZE + DecodeUtils.EVM.SELECTOR_SIZE); + const address = bytes.slice(0, CodecUtils.EVM.ADDRESS_SIZE); + const selector = bytes.slice(CodecUtils.EVM.ADDRESS_SIZE, CodecUtils.EVM.ADDRESS_SIZE + CodecUtils.EVM.SELECTOR_SIZE); return new Values.FunctionExternalValue(dataType, (yield* decodeExternalFunction(address, selector, info)) ); case "internal": - if(!checkPaddingLeft(bytes, 2 * DecodeUtils.EVM.PC_SIZE)) { + if(!checkPaddingLeft(bytes, 2 * CodecUtils.EVM.PC_SIZE)) { return new Values.FunctionInternalErrorResult( dataType, - new Values.FunctionInternalPaddingError(DecodeUtils.Conversion.toHexString(bytes)) + new Values.FunctionInternalPaddingError(CodecUtils.Conversion.toHexString(bytes)) ); } - const deployedPc = bytes.slice(-DecodeUtils.EVM.PC_SIZE); - const constructorPc = bytes.slice(-DecodeUtils.EVM.PC_SIZE * 2, -DecodeUtils.EVM.PC_SIZE); + const deployedPc = bytes.slice(-CodecUtils.EVM.PC_SIZE); + const constructorPc = bytes.slice(-CodecUtils.EVM.PC_SIZE * 2, -CodecUtils.EVM.PC_SIZE); return decodeInternalFunction(dataType, deployedPc, constructorPc, info); } break; //to satisfy TypeScript case "enum": { - const numeric = DecodeUtils.Conversion.toBN(bytes); + const numeric = CodecUtils.Conversion.toBN(bytes); const fullType = Types.fullType(dataType, info.userDefinedTypes); if(!fullType.options) { return new Values.EnumErrorResult( @@ -157,7 +157,7 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, if(!checkPaddingLeft(bytes, numBytes)) { return new Values.EnumErrorResult( fullType, - new Values.EnumPaddingError(fullType, DecodeUtils.Conversion.toHexString(bytes)) + new Values.EnumPaddingError(fullType, CodecUtils.Conversion.toHexString(bytes)) ); } if(numeric.ltn(numOptions)) { @@ -174,7 +174,7 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "fixed": { //skipping padding check as we don't support this anyway - const hex = DecodeUtils.Conversion.toHexString(bytes); + const hex = CodecUtils.Conversion.toHexString(bytes); return new Values.FixedErrorResult( dataType, new Values.FixedPointNotYetSupportedError(hex) @@ -182,7 +182,7 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, } case "ufixed": { //skipping padding check as we don't support this anyway - const hex = DecodeUtils.Conversion.toHexString(bytes); + const hex = CodecUtils.Conversion.toHexString(bytes); return new Values.UfixedErrorResult( dataType, new Values.FixedPointNotYetSupportedError(hex) @@ -193,17 +193,17 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, //NOTE that this function returns a ContractValueInfo, not a ContractResult export function* decodeContract(addressBytes: Uint8Array, info: EvmInfo): IterableIterator { - let address = DecodeUtils.Conversion.toAddress(addressBytes); + let address = CodecUtils.Conversion.toAddress(addressBytes); let codeBytes: Uint8Array = yield { type: "code", address }; - let code = DecodeUtils.Conversion.toHexString(codeBytes); - let context = DecodeUtils.Contexts.findDecoderContext(info.contexts, code); + let code = CodecUtils.Conversion.toHexString(codeBytes); + let context = CodecUtils.Contexts.findDecoderContext(info.contexts, code); if(context !== null && context.contractName !== undefined) { return new Values.ContractValueInfoKnown( address, - DecodeUtils.Contexts.contextToType(context) + CodecUtils.Contexts.contextToType(context) ); } else { @@ -215,7 +215,7 @@ export function* decodeContract(addressBytes: Uint8Array, info: EvmInfo): Iterab //NOTE this again returns a FunctionExternalValueInfo, not a FunctionExternalResult export function* decodeExternalFunction(addressBytes: Uint8Array, selectorBytes: Uint8Array, info: EvmInfo): IterableIterator { let contract = (yield* decodeContract(addressBytes, info)); - let selector = DecodeUtils.Conversion.toHexString(selectorBytes); + let selector = CodecUtils.Conversion.toHexString(selectorBytes); if(contract.kind === "unknown") { return new Values.FunctionExternalValueInfoUnknown(contract, selector) } @@ -235,8 +235,8 @@ export function* decodeExternalFunction(addressBytes: Uint8Array, selectorBytes: //this one works a bit differently -- in order to handle errors, it *does* return a FunctionInternalResult export function decodeInternalFunction(dataType: Types.FunctionType, deployedPcBytes: Uint8Array, constructorPcBytes: Uint8Array, info: EvmInfo): Values.FunctionInternalResult { - let deployedPc: number = DecodeUtils.Conversion.toBN(deployedPcBytes).toNumber(); - let constructorPc: number = DecodeUtils.Conversion.toBN(constructorPcBytes).toNumber(); + let deployedPc: number = CodecUtils.Conversion.toBN(deployedPcBytes).toNumber(); + let constructorPc: number = CodecUtils.Conversion.toBN(constructorPcBytes).toNumber(); let context: Types.ContractType = { typeClass: "contract", id: info.currentContext.contractId, diff --git a/packages/truffle-codec/lib/encode/abi.ts b/packages/truffle-codec/lib/encode/abi.ts index 341d071b3bb..539ee707151 100644 --- a/packages/truffle-codec/lib/encode/abi.ts +++ b/packages/truffle-codec/lib/encode/abi.ts @@ -1,4 +1,4 @@ -import { Types, Values, Conversion as ConversionUtils, EVM as EVMUtils } from "truffle-decode-utils"; +import { Types, Values, Conversion as ConversionUtils, EVM as EVMUtils } from "truffle-codec-utils"; import { AbiAllocations } from "../types/allocation"; import { isTypeDynamic, abiSizeForType } from "../allocate/abi"; import sum from "lodash.sum"; diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index a823371dc35..c43c1cb74fe 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -1,8 +1,8 @@ import debugModule from "debug"; const debug = debugModule("decoder-core:interface"); -import { AstDefinition, Types, Values } from "truffle-decode-utils"; -import * as DecodeUtils from "truffle-decode-utils"; +import { AstDefinition, Types, Values } from "truffle-codec-utils"; +import * as CodecUtils from "truffle-codec-utils"; import * as Pointer from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; @@ -28,7 +28,7 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator read( { location: "calldata", start: 0, - length: DecodeUtils.EVM.SELECTOR_SIZE + length: CodecUtils.EVM.SELECTOR_SIZE }, info.state ).next().value; //no requests should occur, we can just get the first value - let selector = DecodeUtils.Conversion.toHexString(rawSelector); + let selector = CodecUtils.Conversion.toHexString(rawSelector); allocation = allocations.functionAllocations[selector]; } if(allocation === undefined) { @@ -94,7 +94,7 @@ export function* decodeEvent(info: EvmInfo): IterableIterator { const value = decode( diff --git a/packages/truffle-codec/lib/read/bytes.ts b/packages/truffle-codec/lib/read/bytes.ts index 52a7c5c2eb6..76764b17439 100644 --- a/packages/truffle-codec/lib/read/bytes.ts +++ b/packages/truffle-codec/lib/read/bytes.ts @@ -1,6 +1,6 @@ import BN from "bn.js"; -import * as DecodeUtils from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; /** * read word from memory @@ -12,7 +12,7 @@ import * as DecodeUtils from "truffle-decode-utils"; * @return {BN} */ export function read(memory: Uint8Array, offset: number) { - return readBytes(memory, offset, DecodeUtils.EVM.WORD_SIZE); + return readBytes(memory, offset, CodecUtils.EVM.WORD_SIZE); } /** diff --git a/packages/truffle-codec/lib/read/constant.ts b/packages/truffle-codec/lib/read/constant.ts index 835740cf36e..c2d883dddc5 100644 --- a/packages/truffle-codec/lib/read/constant.ts +++ b/packages/truffle-codec/lib/read/constant.ts @@ -1,24 +1,24 @@ import debugModule from "debug"; const debug = debugModule("decoder-core:read:constant"); -import * as DecodeUtils from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; import BN from "bn.js"; -import { Values } from "truffle-decode-utils"; +import { Values } from "truffle-codec-utils"; -export function readDefinition(definition: DecodeUtils.AstDefinition): Uint8Array { +export function readDefinition(definition: CodecUtils.AstDefinition): Uint8Array { debug("definition %o", definition); - switch(DecodeUtils.Definition.typeClass(definition)) + switch(CodecUtils.Definition.typeClass(definition)) { case "rational": - let numericalValue: BN = DecodeUtils.Definition.rationalValue(definition); - return DecodeUtils.Conversion.toBytes(numericalValue, DecodeUtils.EVM.WORD_SIZE); + let numericalValue: BN = CodecUtils.Definition.rationalValue(definition); + return CodecUtils.Conversion.toBytes(numericalValue, CodecUtils.EVM.WORD_SIZE); //you may be wondering, why do we not just use definition.value here, //like we do below? answer: because if this isn't a literal, that may not //exist case "stringliteral": - return DecodeUtils.Conversion.toBytes(definition.hexValue); + return CodecUtils.Conversion.toBytes(definition.hexValue); default: //unfortunately, other types of constants are just too complicated to //handle right now. sorry. diff --git a/packages/truffle-codec/lib/read/index.ts b/packages/truffle-codec/lib/read/index.ts index 8d6e86604ca..4b72cb0649c 100644 --- a/packages/truffle-codec/lib/read/index.ts +++ b/packages/truffle-codec/lib/read/index.ts @@ -5,7 +5,7 @@ import * as constant from "./constant"; import * as Pointer from "../types/pointer"; import { EvmState } from "../types/evm"; import { DecoderRequest } from "../types/request"; -import { Values } from "truffle-decode-utils"; +import { Values } from "truffle-codec-utils"; export default function* read(pointer: Pointer.DataPointer, state: EvmState): IterableIterator { switch(pointer.location) { diff --git a/packages/truffle-codec/lib/read/stack.ts b/packages/truffle-codec/lib/read/stack.ts index 9842b30c773..5d563e22ddc 100644 --- a/packages/truffle-codec/lib/read/stack.ts +++ b/packages/truffle-codec/lib/read/stack.ts @@ -1,12 +1,12 @@ import debugModule from "debug"; const debug = debugModule("decoder-core:read:stack"); -import * as DecodeUtils from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; export function readStack(stack: Uint8Array[], from: number, to: number): Uint8Array { if(from < 0 || to >= stack.length) { - throw new DecodeUtils.Values.DecodingError( - new DecodeUtils.Values.ReadErrorStack(from, to) + throw new CodecUtils.Values.DecodingError( + new CodecUtils.Values.ReadErrorStack(from, to) ); } //unforunately, Uint8Arrays don't support concat; if they did the rest of @@ -14,11 +14,11 @@ export function readStack(stack: Uint8Array[], from: number, to: number): Uint8A //but they don't support that either. But neither of those are the case, so //we'll have to concatenate a bit more manually. let words = stack.slice(from, to + 1); - let result = new Uint8Array(words.length * DecodeUtils.EVM.WORD_SIZE); + let result = new Uint8Array(words.length * CodecUtils.EVM.WORD_SIZE); //shouldn't we total up the lengths? yeah, but each one should have a //length of 32, so unless somehting's gone wrong we can just multiply for(let index = 0; index < words.length; index++) { - result.set(words[index], index * DecodeUtils.EVM.WORD_SIZE); + result.set(words[index], index * CodecUtils.EVM.WORD_SIZE); } return result; } diff --git a/packages/truffle-codec/lib/read/storage.ts b/packages/truffle-codec/lib/read/storage.ts index 5172255cb58..42b209e9b14 100644 --- a/packages/truffle-codec/lib/read/storage.ts +++ b/packages/truffle-codec/lib/read/storage.ts @@ -1,7 +1,7 @@ import debugModule from "debug"; const debug = debugModule("decoder-core:read:storage"); -import * as DecodeUtils from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; import { Slot, Range } from "../types/storage"; import { WordMapping } from "../types/evm"; import { DecoderRequest } from "../types/request"; @@ -18,11 +18,11 @@ import BN from "bn.js"; export function slotAddress(slot: Slot): BN { if (slot.key !== undefined && slot.path !== undefined) { // mapping reference - return DecodeUtils.EVM.keccak256(slot.key.toSoliditySha3Input(), slotAddress(slot.path)).add(slot.offset); + return CodecUtils.EVM.keccak256(slot.key.toSoliditySha3Input(), slotAddress(slot.path)).add(slot.offset); } else if (slot.path !== undefined) { const pathAddress = slotAddress(slot.path); - const path: BN = slot.hashPath ? DecodeUtils.EVM.keccak256(pathAddress) : pathAddress; + const path: BN = slot.hashPath ? CodecUtils.EVM.keccak256(pathAddress) : pathAddress; return path.add(slot.offset); } else { @@ -57,9 +57,9 @@ export function* read(storage: WordMapping, slot: Slot): IterableIterator node.nodeType === "FunctionDefinition" && - DecodeUtils.Definition.functionKind(node) === "constructor" + CodecUtils.Definition.functionKind(node) === "constructor" ); default: //we should never hit this case @@ -76,7 +76,7 @@ function debuggerContextToDecoderContext(context) { contractId, contractKind, isConstructor, - abi: DecodeUtils.Contexts.abiToFunctionAbiWithSignatures(abi), + abi: CodecUtils.Contexts.abiToFunctionAbiWithSignatures(abi), payable, compiler }; @@ -157,7 +157,7 @@ const data = createSelectorTree({ return Object.assign( {}, ...Object.entries(referenceDeclarations).map(([id, node]) => ({ - [id]: DecodeUtils.Types.definitionToStoredType( + [id]: CodecUtils.Types.definitionToStoredType( node, contexts[findAncestorOfType(node, types, scopes).id].compiler ) @@ -223,7 +223,7 @@ const data = createSelectorTree({ Object.assign( {}, ...Object.entries(instances).map(([address, { binary }]) => ({ - [address]: DecodeUtils.Conversion.toBytes(binary) + [address]: CodecUtils.Conversion.toBytes(binary) })) ) ), @@ -301,7 +301,7 @@ const data = createSelectorTree({ let definition = inlined[variable.id].definition; return ( !definition.constant || - DecodeUtils.Definition.isSimpleConstant(definition.value) + CodecUtils.Definition.isSimpleConstant(definition.value) ); }); @@ -418,7 +418,7 @@ const data = createSelectorTree({ stack: createLeaf( [evm.current.state.stack], - words => (words || []).map(word => DecodeUtils.Conversion.toBytes(word)) + words => (words || []).map(word => CodecUtils.Conversion.toBytes(word)) ), /** @@ -427,7 +427,7 @@ const data = createSelectorTree({ memory: createLeaf( [evm.current.state.memory], - words => DecodeUtils.Conversion.toBytes(words.join("")) + words => CodecUtils.Conversion.toBytes(words.join("")) ), /** @@ -436,7 +436,7 @@ const data = createSelectorTree({ calldata: createLeaf( [evm.current.call], - ({ data }) => DecodeUtils.Conversion.toBytes(data) + ({ data }) => CodecUtils.Conversion.toBytes(data) ), /** @@ -449,7 +449,7 @@ const data = createSelectorTree({ Object.assign( {}, ...Object.entries(mapping).map(([address, word]) => ({ - [`0x${address}`]: DecodeUtils.Conversion.toBytes(word) + [`0x${address}`]: CodecUtils.Conversion.toBytes(word) })) ) ), @@ -463,24 +463,24 @@ const data = createSelectorTree({ specials: createLeaf( ["/current/address", evm.current.call, evm.transaction.globals], (address, { sender, value }, { tx, block }) => ({ - this: DecodeUtils.Conversion.toBytes(address), + this: CodecUtils.Conversion.toBytes(address), - sender: DecodeUtils.Conversion.toBytes(sender), + sender: CodecUtils.Conversion.toBytes(sender), - value: DecodeUtils.Conversion.toBytes(value), + value: CodecUtils.Conversion.toBytes(value), //let's crack open that tx and block! ...Object.assign( {}, ...Object.entries(tx).map(([variable, value]) => ({ - [variable]: DecodeUtils.Conversion.toBytes(value) + [variable]: CodecUtils.Conversion.toBytes(value) })) ), ...Object.assign( {}, ...Object.entries(block).map(([variable, value]) => ({ - [variable]: DecodeUtils.Conversion.toBytes(value) + [variable]: CodecUtils.Conversion.toBytes(value) })) ) }) @@ -743,10 +743,10 @@ const data = createSelectorTree({ }) ); let builtins = { - msg: DecodeUtils.Definition.MSG_DEFINITION, - tx: DecodeUtils.Definition.TX_DEFINITION, - block: DecodeUtils.Definition.BLOCK_DEFINITION, - now: DecodeUtils.Definition.NOW_DEFINITION + msg: CodecUtils.Definition.MSG_DEFINITION, + tx: CodecUtils.Definition.TX_DEFINITION, + block: CodecUtils.Definition.BLOCK_DEFINITION, + now: CodecUtils.Definition.NOW_DEFINITION }; //only include this when it has a proper definition if (thisDefinition) { @@ -765,7 +765,7 @@ const data = createSelectorTree({ ["/current/contract"], contractNode => contractNode && contractNode.nodeType === "ContractDefinition" - ? DecodeUtils.Definition.spoofThisDefinition( + ? CodecUtils.Definition.spoofThisDefinition( contractNode.name, contractNode.id, contractNode.contractKind @@ -861,7 +861,7 @@ const data = createSelectorTree({ stack: createLeaf( [evm.next.state.stack], - words => (words || []).map(word => DecodeUtils.Conversion.toBytes(word)) + words => (words || []).map(word => CodecUtils.Conversion.toBytes(word)) ) }, @@ -938,7 +938,7 @@ const data = createSelectorTree({ step => ((step || {}).stack || []).map(word => - DecodeUtils.Conversion.toBytes(word) + CodecUtils.Conversion.toBytes(word) ) ) } diff --git a/packages/truffle-debugger/lib/evm/reducers.js b/packages/truffle-debugger/lib/evm/reducers.js index ca8ba8bd846..b9f9875be77 100644 --- a/packages/truffle-debugger/lib/evm/reducers.js +++ b/packages/truffle-debugger/lib/evm/reducers.js @@ -5,7 +5,7 @@ import { combineReducers } from "redux"; import * as actions from "./actions"; import { keccak256, extractPrimarySource } from "lib/helpers"; -import * as DecodeUtils from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; import BN from "bn.js"; @@ -54,14 +54,14 @@ function contexts(state = DEFAULT_CONTEXTS, action) { contractId, contractKind, isConstructor, - payable: DecodeUtils.Contexts.isABIPayable(abi) + payable: CodecUtils.Contexts.isABIPayable(abi) } } }; case actions.NORMALIZE_CONTEXTS: return { - byContext: DecodeUtils.Contexts.normalizeContexts(state.byContext) + byContext: CodecUtils.Contexts.normalizeContexts(state.byContext) }; /* @@ -78,7 +78,7 @@ const info = combineReducers({ const DEFAULT_TX = { gasprice: new BN(0), - origin: DecodeUtils.EVM.ZERO_ADDRESS + origin: CodecUtils.EVM.ZERO_ADDRESS }; function tx(state = DEFAULT_TX, action) { @@ -94,7 +94,7 @@ function tx(state = DEFAULT_TX, action) { } const DEFAULT_BLOCK = { - coinbase: DecodeUtils.EVM.ZERO_ADDRESS, + coinbase: CodecUtils.EVM.ZERO_ADDRESS, difficulty: new BN(0), gaslimit: new BN(0), number: new BN(0), @@ -252,7 +252,7 @@ function codex(state = DEFAULT_CODEX, action) { //now, do we need to add a new address to this stackframe? if ( topCodex.accounts[action.storageAddress] !== undefined || - action.storageAddress === DecodeUtils.EVM.ZERO_ADDRESS + action.storageAddress === CodecUtils.EVM.ZERO_ADDRESS ) { //if we don't return newState; @@ -276,7 +276,7 @@ function codex(state = DEFAULT_CODEX, action) { //on a store, the relevant page should already exist, so we can just //add or update the needed slot const { address, slot, value } = action; - if (address === DecodeUtils.EVM.ZERO_ADDRESS) { + if (address === CodecUtils.EVM.ZERO_ADDRESS) { //as always, we do not maintain a zero page return state; } @@ -296,7 +296,7 @@ function codex(state = DEFAULT_CODEX, action) { //it's an external load (there was nothing already there), then we want //to update *every* stackframe const { address, slot, value } = action; - if (address === DecodeUtils.EVM.ZERO_ADDRESS) { + if (address === CodecUtils.EVM.ZERO_ADDRESS) { //as always, we do not maintain a zero page return state; } diff --git a/packages/truffle-debugger/lib/evm/selectors/index.js b/packages/truffle-debugger/lib/evm/selectors/index.js index d92fec50228..2c60e0b958d 100644 --- a/packages/truffle-debugger/lib/evm/selectors/index.js +++ b/packages/truffle-debugger/lib/evm/selectors/index.js @@ -6,7 +6,7 @@ import BN from "bn.js"; import trace from "lib/trace/selectors"; -import * as DecodeUtils from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; import { isCallMnemonic, isCreateMnemonic, @@ -150,7 +150,7 @@ function createStepSelectors(step, state = null) { } let address = stack[stack.length - 2]; - return DecodeUtils.Conversion.toAddress(address); + return CodecUtils.Conversion.toAddress(address); } ), @@ -220,7 +220,7 @@ function createStepSelectors(step, state = null) { //otherwise, for CALL and CALLCODE, it's the 3rd argument let value = stack[stack.length - 3]; - return DecodeUtils.Conversion.toBN(value); + return CodecUtils.Conversion.toBN(value); } ), @@ -236,7 +236,7 @@ function createStepSelectors(step, state = null) { //creates have the value as the first argument let value = stack[stack.length - 1]; - return DecodeUtils.Conversion.toBN(value); + return CodecUtils.Conversion.toBN(value); }), /** @@ -316,7 +316,7 @@ const evm = createSelectorTree({ * (returns null on no match) */ search: createLeaf(["/info/contexts"], contexts => binary => - DecodeUtils.Contexts.findDebuggerContext(contexts, binary) + CodecUtils.Contexts.findDebuggerContext(contexts, binary) ) } }, @@ -446,7 +446,7 @@ const evm = createSelectorTree({ return null; } let address = stack[stack.length - 1]; - return DecodeUtils.Conversion.toAddress(address); + return CodecUtils.Conversion.toAddress(address); } ), @@ -508,7 +508,7 @@ const evm = createSelectorTree({ if (remaining <= 1) { return finalStatus; } else { - const ZERO_WORD = "00".repeat(DecodeUtils.EVM.WORD_SIZE); + const ZERO_WORD = "00".repeat(CodecUtils.EVM.WORD_SIZE); return stack[stack.length - 1] !== ZERO_WORD; } } @@ -535,7 +535,7 @@ const evm = createSelectorTree({ storage: createLeaf( ["./_", "../state/storage", "../call"], (codex, rawStorage, { storageAddress }) => - storageAddress === DecodeUtils.EVM.ZERO_ADDRESS + storageAddress === CodecUtils.EVM.ZERO_ADDRESS ? rawStorage //HACK -- if zero address ignore the codex : codex[codex.length - 1].accounts[storageAddress].storage ), diff --git a/packages/truffle-debugger/lib/helpers/index.js b/packages/truffle-debugger/lib/helpers/index.js index ceee76cbcdf..94f6217942a 100644 --- a/packages/truffle-debugger/lib/helpers/index.js +++ b/packages/truffle-debugger/lib/helpers/index.js @@ -1,4 +1,4 @@ -import * as utils from "truffle-decode-utils"; +import * as utils from "truffle-codec-utils"; const stringify = require("json-stable-stringify"); diff --git a/packages/truffle-debugger/lib/trace/sagas/index.js b/packages/truffle-debugger/lib/trace/sagas/index.js index fe8bca6fe2d..160648befa2 100644 --- a/packages/truffle-debugger/lib/trace/sagas/index.js +++ b/packages/truffle-debugger/lib/trace/sagas/index.js @@ -4,7 +4,7 @@ const debug = debugModule("debugger:trace:sagas"); import { take, takeEvery, put, select } from "redux-saga/effects"; import { prefixName, isCallMnemonic } from "lib/helpers"; -import * as DecodeUtils from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; import * as actions from "../actions"; @@ -70,7 +70,7 @@ export function* processTrace(steps) { ({ op, stack }) => isCallMnemonic(op) ? //if it's a call, just fetch the address off the stack - DecodeUtils.Conversion.toAddress(stack[stack.length - 2]) + CodecUtils.Conversion.toAddress(stack[stack.length - 2]) : //if it's not a call, just return undefined (we've gone back to //skipping creates) undefined @@ -78,7 +78,7 @@ export function* processTrace(steps) { //filter out zero addresses from failed creates (as well as undefineds) .filter( address => - address !== undefined && address !== DecodeUtils.EVM.ZERO_ADDRESS + address !== undefined && address !== CodecUtils.EVM.ZERO_ADDRESS ) ) ]; diff --git a/packages/truffle-debugger/lib/web3/sagas/index.js b/packages/truffle-debugger/lib/web3/sagas/index.js index 05dbfd99faf..7abfd02e38d 100644 --- a/packages/truffle-debugger/lib/web3/sagas/index.js +++ b/packages/truffle-debugger/lib/web3/sagas/index.js @@ -17,7 +17,7 @@ import * as session from "lib/session/actions"; import BN from "bn.js"; import Web3 from "web3"; //just for utils! -import * as DecodeUtils from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; import Web3Adapter from "../adapter"; @@ -71,7 +71,7 @@ function* fetchTransactionInfo(adapter, { txHash }) { } else { let storageAddress = Web3.utils.isAddress(receipt.contractAddress) ? receipt.contractAddress - : DecodeUtils.EVM.ZERO_ADDRESS; + : CodecUtils.EVM.ZERO_ADDRESS; yield put( actions.receiveCall({ binary: tx.input, diff --git a/packages/truffle-debugger/test/data/calldata.js b/packages/truffle-debugger/test/data/calldata.js index a7158ca26cf..f00472be89c 100644 --- a/packages/truffle-debugger/test/data/calldata.js +++ b/packages/truffle-debugger/test/data/calldata.js @@ -8,7 +8,7 @@ import Ganache from "ganache-core"; import { prepareContracts, lineOf } from "../helpers"; import Debugger from "lib/debugger"; -import * as TruffleDecodeUtils from "truffle-decode-utils"; +import * as TruffleCodecUtils from "truffle-codec-utils"; import solidity from "lib/solidity/selectors"; @@ -135,7 +135,7 @@ describe("Calldata Decoding", function() { await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); @@ -171,7 +171,7 @@ describe("Calldata Decoding", function() { await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); @@ -205,7 +205,7 @@ describe("Calldata Decoding", function() { await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); @@ -239,7 +239,7 @@ describe("Calldata Decoding", function() { await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); diff --git a/packages/truffle-debugger/test/data/function-decoding.js b/packages/truffle-debugger/test/data/function-decoding.js index d52175e7a64..de2cabb4401 100644 --- a/packages/truffle-debugger/test/data/function-decoding.js +++ b/packages/truffle-debugger/test/data/function-decoding.js @@ -7,7 +7,7 @@ import Ganache from "ganache-core"; import { prepareContracts, lineOf } from "../helpers"; import Debugger from "lib/debugger"; -import * as TruffleDecodeUtils from "truffle-decode-utils"; +import * as TruffleCodecUtils from "truffle-codec-utils"; import solidity from "lib/solidity/selectors"; @@ -207,7 +207,7 @@ describe("Function Pointer Decoding", function() { await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); @@ -246,7 +246,7 @@ describe("Function Pointer Decoding", function() { await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); diff --git a/packages/truffle-debugger/test/data/global.js b/packages/truffle-debugger/test/data/global.js index 437d3536dc4..54c9eda4ae3 100644 --- a/packages/truffle-debugger/test/data/global.js +++ b/packages/truffle-debugger/test/data/global.js @@ -8,7 +8,7 @@ import Ganache from "ganache-core"; import { prepareContracts, lineOf } from "../helpers"; import Debugger from "lib/debugger"; -import * as TruffleDecodeUtils from "truffle-decode-utils"; +import * as TruffleCodecUtils from "truffle-codec-utils"; import solidity from "lib/solidity/selectors"; @@ -189,7 +189,7 @@ describe("Globally-available variables", function() { await session.continueUntilBreakpoint(); //run till end - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); @@ -223,7 +223,7 @@ describe("Globally-available variables", function() { }); await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); @@ -257,7 +257,7 @@ describe("Globally-available variables", function() { }); await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); @@ -291,7 +291,7 @@ describe("Globally-available variables", function() { }); await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); @@ -318,7 +318,7 @@ describe("Globally-available variables", function() { await session.continueUntilBreakpoint(); //run till end - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); @@ -352,7 +352,7 @@ describe("Globally-available variables", function() { }); await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); diff --git a/packages/truffle-debugger/test/data/more-decoding.js b/packages/truffle-debugger/test/data/more-decoding.js index 94ef8919e70..6aa163dc025 100644 --- a/packages/truffle-debugger/test/data/more-decoding.js +++ b/packages/truffle-debugger/test/data/more-decoding.js @@ -11,7 +11,7 @@ import Debugger from "lib/debugger"; import solidity from "lib/solidity/selectors"; import data from "lib/data/selectors"; -import * as TruffleDecodeUtils from "truffle-decode-utils"; +import * as TruffleCodecUtils from "truffle-codec-utils"; const __CONTAINERS = ` pragma solidity ^0.5.0; @@ -275,7 +275,7 @@ describe("Further Decoding", function() { await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); @@ -319,7 +319,7 @@ describe("Further Decoding", function() { await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); debug("variables %O", variables); @@ -363,7 +363,7 @@ describe("Further Decoding", function() { await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); @@ -398,7 +398,7 @@ describe("Further Decoding", function() { //we're only testing storage so run till end await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); @@ -460,7 +460,7 @@ describe("Further Decoding", function() { await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); debug("variables %O", variables); @@ -496,7 +496,7 @@ describe("Further Decoding", function() { await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); debug("variables %O", variables); @@ -532,7 +532,7 @@ describe("Further Decoding", function() { await session.continueUntilBreakpoint(); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); debug("variables %O", variables); diff --git a/packages/truffle-debugger/test/data/utils.js b/packages/truffle-debugger/test/data/utils.js index ff72674dc5d..5d9239aa3de 100644 --- a/packages/truffle-debugger/test/data/utils.js +++ b/packages/truffle-debugger/test/data/utils.js @@ -2,7 +2,7 @@ import { assert } from "chai"; import BN from "bn.js"; -import * as TruffleDecodeUtils from "truffle-decode-utils"; +import * as TruffleCodecUtils from "truffle-codec-utils"; describe("Utils", function() { describe("typeClass()", function() { @@ -14,7 +14,7 @@ describe("Utils", function() { }; assert.equal( - TruffleDecodeUtils.Definition.typeClass(definition), + TruffleCodecUtils.Definition.typeClass(definition), "mapping" ); }); @@ -25,7 +25,7 @@ describe("Utils", function() { let bytes = [0xf5, 0xe2, 0xc5, 0x17]; let expectedValue = new BN("f5e2c517", 16); - let result = TruffleDecodeUtils.Conversion.toBN(bytes); + let result = TruffleCodecUtils.Conversion.toBN(bytes); assert.equal(result.toString(), expectedValue.toString()); }); @@ -46,7 +46,7 @@ describe("Utils", function() { let expectedValue = bitflipped.addn(1).neg(); - let result = TruffleDecodeUtils.Conversion.toSignedBN(bytes); + let result = TruffleCodecUtils.Conversion.toSignedBN(bytes); assert.equal(result.toString(), expectedValue.toString()); }); @@ -56,7 +56,7 @@ describe("Utils", function() { let raw = new BN("05e2c517", 16); let expectedValue = raw; - let result = TruffleDecodeUtils.Conversion.toSignedBN(bytes); + let result = TruffleCodecUtils.Conversion.toSignedBN(bytes); assert.equal(result.toString(), expectedValue.toString()); }); @@ -66,11 +66,11 @@ describe("Utils", function() { it("returns correct representation with full bytes", function() { // ie, 0x00 instead of 0x0 assert.equal( - TruffleDecodeUtils.Conversion.toHexString([0x05, 0x11]), + TruffleCodecUtils.Conversion.toHexString([0x05, 0x11]), "0x0511" ); assert.equal( - TruffleDecodeUtils.Conversion.toHexString([0xff, 0x00, 0xff]), + TruffleCodecUtils.Conversion.toHexString([0xff, 0x00, 0xff]), "0xff00ff" ); }); diff --git a/packages/truffle-debugger/test/endstate.js b/packages/truffle-debugger/test/endstate.js index ea90bddcc89..684968b58c3 100644 --- a/packages/truffle-debugger/test/endstate.js +++ b/packages/truffle-debugger/test/endstate.js @@ -11,7 +11,7 @@ import Debugger from "lib/debugger"; import evm from "lib/evm/selectors"; import data from "lib/data/selectors"; -import * as TruffleDecodeUtils from "truffle-decode-utils"; +import * as TruffleCodecUtils from "truffle-codec-utils"; const __FAILURE = ` pragma solidity ~0.5; @@ -102,7 +102,7 @@ describe("End State", function() { debug("proc.assignments %O", session.view(data.proc.assignments)); assert.ok(session.view(evm.transaction.status)); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); assert.include(variables, { x: 107 }); diff --git a/packages/truffle-debugger/test/load.js b/packages/truffle-debugger/test/load.js index 5542f5737ac..79fa966e5f0 100644 --- a/packages/truffle-debugger/test/load.js +++ b/packages/truffle-debugger/test/load.js @@ -6,7 +6,7 @@ import { assert } from "chai"; import Ganache from "ganache-core"; import { prepareContracts } from "./helpers"; -import * as TruffleDecodeUtils from "truffle-decode-utils"; +import * as TruffleCodecUtils from "truffle-codec-utils"; import Debugger from "lib/debugger"; import trace from "lib/trace/selectors"; @@ -71,7 +71,7 @@ describe("Loading and unloading transactions", function() { await session.load(txHash); assert.isTrue(session.view(trace.loaded)); await session.continueUntilBreakpoint(); //continue to end - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); const expected = { x: 1 }; @@ -97,7 +97,7 @@ describe("Loading and unloading transactions", function() { assert.isTrue(session.view(trace.loaded)); await session.continueUntilBreakpoint(); //continue to end - let variables = TruffleDecodeUtils.Conversion.nativizeVariables( + let variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); let expected = { x: 1 }; @@ -107,7 +107,7 @@ describe("Loading and unloading transactions", function() { await session.load(txHash2); assert.isTrue(session.view(trace.loaded)); await session.continueUntilBreakpoint(); //continue to end - variables = TruffleDecodeUtils.Conversion.nativizeVariables( + variables = TruffleCodecUtils.Conversion.nativizeVariables( await session.variables() ); expected = { y: 2 }; diff --git a/packages/truffle-debugger/test/reset.js b/packages/truffle-debugger/test/reset.js index 90c74daa3ec..5122b2d3485 100644 --- a/packages/truffle-debugger/test/reset.js +++ b/packages/truffle-debugger/test/reset.js @@ -6,7 +6,7 @@ import { assert } from "chai"; import Ganache from "ganache-core"; import { prepareContracts, lineOf } from "./helpers"; -import * as TruffleDecodeUtils from "truffle-decode-utils"; +import * as TruffleCodecUtils from "truffle-codec-utils"; import Debugger from "lib/debugger"; import solidity from "lib/solidity/selectors"; @@ -74,31 +74,31 @@ describe("Reset Button", function() { await session.addBreakpoint({ sourceId, line: lineOf("BREAK", source) }); variables[0].push( - TruffleDecodeUtils.Conversion.nativizeVariables(await session.variables()) + TruffleCodecUtils.Conversion.nativizeVariables(await session.variables()) ); await session.continueUntilBreakpoint(); //advance to line 10 variables[0].push( - TruffleDecodeUtils.Conversion.nativizeVariables(await session.variables()) + TruffleCodecUtils.Conversion.nativizeVariables(await session.variables()) ); await session.continueUntilBreakpoint(); //advance to the end variables[0].push( - TruffleDecodeUtils.Conversion.nativizeVariables(await session.variables()) + TruffleCodecUtils.Conversion.nativizeVariables(await session.variables()) ); //now, reset and do it again await session.reset(); variables[1].push( - TruffleDecodeUtils.Conversion.nativizeVariables(await session.variables()) + TruffleCodecUtils.Conversion.nativizeVariables(await session.variables()) ); await session.addBreakpoint({ sourceId, line: lineOf("BREAK", source) }); await session.continueUntilBreakpoint(); //advance to line 10 variables[1].push( - TruffleDecodeUtils.Conversion.nativizeVariables(await session.variables()) + TruffleCodecUtils.Conversion.nativizeVariables(await session.variables()) ); await session.continueUntilBreakpoint(); //advance to the end variables[1].push( - TruffleDecodeUtils.Conversion.nativizeVariables(await session.variables()) + TruffleCodecUtils.Conversion.nativizeVariables(await session.variables()) ); assert.deepEqual(variables[1], variables[0]); diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index fd9f2bbd789..d8f2a110377 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -1,13 +1,13 @@ import debugModule from "debug"; const debug = debugModule("decoder:decoder"); -import * as DecodeUtils from "truffle-decode-utils"; -import { Types, Values } from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import AsyncEventEmitter from "async-eventemitter"; import Web3 from "web3"; import { ContractObject } from "truffle-contract-schema/spec"; import BN from "bn.js"; -import { Definition as DefinitionUtils, EVM, AstDefinition, AstReferences } from "truffle-decode-utils"; +import { Definition as DefinitionUtils, EVM, AstDefinition, AstReferences } from "truffle-codec-utils"; import { BlockType, Transaction } from "web3/eth/types"; import { EventLog, Log } from "web3/types"; import { Provider } from "web3/providers"; @@ -28,10 +28,10 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { private contracts: DecoderTypes.ContractMapping = {}; private contractNodes: AstReferences = {}; - private contexts: DecodeUtils.Contexts.DecoderContexts = {}; - private contextsById: DecodeUtils.Contexts.DecoderContextsById = {}; //deployed contexts only - private context: DecodeUtils.Contexts.DecoderContext; - private constructorContext: DecodeUtils.Contexts.DecoderContext; + private contexts: CodecUtils.Contexts.DecoderContexts = {}; + private contextsById: CodecUtils.Contexts.DecoderContextsById = {}; //deployed contexts only + private context: CodecUtils.Contexts.DecoderContext; + private constructorContext: CodecUtils.Contexts.DecoderContext; private contextHash: string; private constructorContextHash: string; @@ -71,8 +71,8 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.contractNodes[this.contractNode.id] = this.contractNode; if(this.contract.deployedBytecode) { //just to be safe const context = Utils.makeContext(this.contract, this.contractNode); - const hash = DecodeUtils.Conversion.toHexString( - DecodeUtils.EVM.keccak256({type: "string", + const hash = CodecUtils.Conversion.toHexString( + CodecUtils.EVM.keccak256({type: "string", value: context.binary }) ); @@ -81,8 +81,8 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } if(this.contract.bytecode) { //now the constructor version const constructorContext = Utils.makeContext(this.contract, this.contractNode, true); - const hash = DecodeUtils.Conversion.toHexString( - DecodeUtils.EVM.keccak256({type: "string", + const hash = CodecUtils.Conversion.toHexString( + CodecUtils.EVM.keccak256({type: "string", value: constructorContext.binary }) ); @@ -97,8 +97,8 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.contractNodes[node.id] = node; if(relevantContract.deployedBytecode) { const context = Utils.makeContext(relevantContract, node); - const hash = DecodeUtils.Conversion.toHexString( - DecodeUtils.EVM.keccak256({type: "string", + const hash = CodecUtils.Conversion.toHexString( + CodecUtils.EVM.keccak256({type: "string", value: context.binary }) ); @@ -107,7 +107,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } } - this.contexts = DecodeUtils.Contexts.normalizeContexts(this.contexts); + this.contexts = CodecUtils.Contexts.normalizeContexts(this.contexts); this.context = this.contexts[this.contextHash]; this.constructorContext = this.contexts[this.constructorContextHash]; this.contextsById = Object.assign({}, ...Object.values(this.contexts).filter( @@ -268,13 +268,13 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { return this.storageCache[block][address][slot.toString()]; } //otherwise, get it, cache it, and return it - let word = DecodeUtils.Conversion.toBytes( + let word = CodecUtils.Conversion.toBytes( await this.web3.eth.getStorageAt( address, slot, block ), - DecodeUtils.EVM.WORD_SIZE + CodecUtils.EVM.WORD_SIZE ); this.storageCache[block][address][slot.toString()] = word; return word; @@ -290,7 +290,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { return this.codeCache[block][address]; } //otherwise, get it, cache it, and return it - let code = DecodeUtils.Conversion.toBytes( + let code = CodecUtils.Conversion.toBytes( await this.web3.eth.getCode( address, block @@ -358,7 +358,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } } const block = transaction.blockNumber; - const data = DecodeUtils.Conversion.toBytes(transaction.input); + const data = CodecUtils.Conversion.toBytes(transaction.input); const info: Codec.EvmInfo = { state: { storage: {}, @@ -396,8 +396,8 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { throw new DecoderTypes.EventOrTransactionIsNotForThisContractError(log.address, this.contractAddress); } const block = log.blockNumber; - const data = DecodeUtils.Conversion.toBytes(log.data); - const topics = log.topics.map(DecodeUtils.Conversion.toBytes); + const data = CodecUtils.Conversion.toBytes(log.data); + const topics = log.topics.map(CodecUtils.Conversion.toBytes); const info: Codec.EvmInfo = { state: { storage: {}, diff --git a/packages/truffle-decoder/lib/types.ts b/packages/truffle-decoder/lib/types.ts index d5152b31934..be6766fb22d 100644 --- a/packages/truffle-decoder/lib/types.ts +++ b/packages/truffle-decoder/lib/types.ts @@ -1,6 +1,6 @@ import BN from "bn.js"; import { ContractObject } from "truffle-contract-schema/spec"; -import { Values } from "truffle-decode-utils"; +import { Values } from "truffle-codec-utils"; import { CalldataDecoding, EventDecoding } from "truffle-codec"; import { Transaction, Log } from "web3-core"; diff --git a/packages/truffle-decoder/lib/utils.ts b/packages/truffle-decoder/lib/utils.ts index 71cdb30f602..93db142711a 100644 --- a/packages/truffle-decoder/lib/utils.ts +++ b/packages/truffle-decoder/lib/utils.ts @@ -1,4 +1,4 @@ -import { AstDefinition, AstReferences, Contexts as ContextsUtils } from "truffle-decode-utils"; +import { AstDefinition, AstReferences, Contexts as ContextsUtils } from "truffle-codec-utils"; import { ContractObject } from "truffle-contract-schema/spec"; export function getContractNode(contract: ContractObject): AstDefinition { diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 9a0e27f2b20..ecd4498fcfd 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -1,13 +1,13 @@ import debugModule from "debug"; const debug = debugModule("decoder:decoder"); -import * as DecodeUtils from "truffle-decode-utils"; -import { Types, Values } from "truffle-decode-utils"; +import * as CodecUtils from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import AsyncEventEmitter from "async-eventemitter"; import Web3 from "web3"; import { ContractObject } from "truffle-contract-schema/spec"; import BN from "bn.js"; -import { Definition as DefinitionUtils, EVM, AstDefinition, AstReferences } from "truffle-decode-utils"; +import { Definition as DefinitionUtils, EVM, AstDefinition, AstReferences } from "truffle-codec-utils"; import { BlockType, Transaction } from "web3/eth/types"; import { EventLog, Log } from "web3/types"; import { Provider } from "web3/providers"; @@ -22,9 +22,9 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { private contracts: DecoderTypes.ContractMapping = {}; private contractNodes: AstReferences = {}; - private contexts: DecodeUtils.Contexts.DecoderContexts = {}; - private contextsById: DecodeUtils.Contexts.DecoderContextsById = {}; //deployed contexts only - private constructorContextsById: DecodeUtils.Contexts.DecoderContextsById = {}; + private contexts: CodecUtils.Contexts.DecoderContexts = {}; + private contextsById: CodecUtils.Contexts.DecoderContextsById = {}; //deployed contexts only + private constructorContextsById: CodecUtils.Contexts.DecoderContextsById = {}; private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; @@ -49,8 +49,8 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { this.contractNodes[node.id] = node; if(contract.deployedBytecode) { const context = Utils.makeContext(contract, node); - const hash = DecodeUtils.Conversion.toHexString( - DecodeUtils.EVM.keccak256({type: "string", + const hash = CodecUtils.Conversion.toHexString( + CodecUtils.EVM.keccak256({type: "string", value: context.binary }) ); @@ -58,8 +58,8 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { } if(contract.byteCode) { const constructorContext = Utils.makeContext(contract, node, true); - const hash = DecodeUtils.Conversion.toHexString( - DecodeUtils.EVM.keccak256({type: "string", + const hash = CodecUtils.Conversion.toHexString( + CodecUtils.EVM.keccak256({type: "string", value: constructorContext.binary }) ); @@ -68,7 +68,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { } } - this.contexts = DecodeUtils.Contexts.normalizeContexts(this.contexts); + this.contexts = CodecUtils.Contexts.normalizeContexts(this.contexts); this.contextsById = Object.assign({}, ...Object.values(this.contexts).filter( ({isConstructor}) => !isConstructor ).map(context => @@ -132,7 +132,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { return this.codeCache[block][address]; } //otherwise, get it, cache it, and return it - let code = DecodeUtils.Conversion.toBytes( + let code = CodecUtils.Conversion.toBytes( await this.web3.eth.getCode( address, block @@ -146,7 +146,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { const block = transaction.blockNumber; const context = await this.getContextByAddress(transaction.to, block, transaction.input); - const data = DecodeUtils.Conversion.toBytes(transaction.input); + const data = CodecUtils.Conversion.toBytes(transaction.input); const info: Codec.EvmInfo = { state: { storage: {}, @@ -180,8 +180,8 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { public async decodeLog(log: Log): Promise { const block = log.blockNumber; - const data = DecodeUtils.Conversion.toBytes(log.data); - const topics = log.topics.map(DecodeUtils.Conversion.toBytes); + const data = CodecUtils.Conversion.toBytes(log.data); + const topics = log.topics.map(CodecUtils.Conversion.toBytes); const info: Codec.EvmInfo = { state: { storage: {}, @@ -246,10 +246,10 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { //and checks this against the known contexts to determine the contract type //however, if this fails and constructorBinary is passed in, it will then also //attempt to determine it from that - private async getContextByAddress(address: string, block: number, constructorBinary?: string): Promise { + private async getContextByAddress(address: string, block: number, constructorBinary?: string): Promise { let code: string; if(address !== null) { - code = DecodeUtils.Conversion.toHexString( + code = CodecUtils.Conversion.toHexString( await this.getCode(address, block) ); } @@ -257,6 +257,6 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { code = constructorBinary; } //otherwise... we have a problem - return DecodeUtils.Contexts.findDecoderContext(this.contexts, code); + return CodecUtils.Contexts.findDecoderContext(this.contexts, code); } } diff --git a/packages/truffle-decoder/test/test/test.js b/packages/truffle-decoder/test/test/test.js index 6031c8dfdc0..25035d6e11e 100644 --- a/packages/truffle-decoder/test/test/test.js +++ b/packages/truffle-decoder/test/test/test.js @@ -2,7 +2,7 @@ const assert = require("assert"); const util = require("util"); // eslint-disable-line no-unused-vars const TruffleContractDecoder = require("../../../truffle-decoder"); -const TruffleDecodeUtils = require("../../../truffle-decode-utils"); +const TruffleCodecUtils = require("../../../truffle-codec-utils"); const DecodingSample = artifacts.require("DecodingSample"); @@ -53,7 +53,7 @@ contract("DecodingSample", _accounts => { // ); assert.equal(initialState.name, "DecodingSample"); - const variables = TruffleDecodeUtils.Conversion.nativizeVariables( + const variables = TruffleCodecUtils.Conversion.nativizeVariables( initialState.variables ); diff --git a/yarn.lock b/yarn.lock index 78490c77062..e77f231dd63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14451,6 +14451,17 @@ truffle-contract@^2.0.3: truffle-contract-schema "^0.0.5" web3 "^0.20.1" +truffle-decode-utils@^1.0.14: + version "1.0.14" + resolved "https://registry.yarnpkg.com/truffle-decode-utils/-/truffle-decode-utils-1.0.14.tgz#25784462145e696d8cd1534ab788e67e189a2a09" + integrity sha512-Ue1k5nNE/Tj38Ld9jH+/PWzw1q1PgDDMFT7yyqur8Yz/MQu9IT7NyYGx51+7LKWxF+yefyxL3c9GJ488Deic1g== + dependencies: + bn.js "^4.11.8" + lodash.clonedeep "^4.5.0" + lodash.escaperegexp "^4.1.2" + web3 "1.0.0-beta.37" + web3-eth-abi "1.0.0-beta.52" + truffle-error@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/truffle-error/-/truffle-error-0.0.3.tgz#4bf55242e14deee1c7194932709182deff2c97ca" From 4f933cbf282e4e2b2a86ac3887465db54c7f15cd Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 19 Jun 2019 19:38:22 -0400 Subject: [PATCH 22/89] Remove dependence on web3-eth-abi; handle all ABI operations ourself --- packages/truffle-codec-utils/package.json | 7 +- packages/truffle-codec-utils/src/abi.ts | 224 ++++++++++++++++++ packages/truffle-codec-utils/src/compiler.ts | 6 + packages/truffle-codec-utils/src/contexts.ts | 149 +----------- .../truffle-codec-utils/src/definition.ts | 3 +- packages/truffle-codec-utils/src/index.ts | 2 + .../truffle-codec-utils/src/types/types.ts | 5 +- packages/truffle-codec/lib/allocate/abi.ts | 32 ++- packages/truffle-codec/package.json | 5 +- .../lib/data/selectors/index.js | 4 +- packages/truffle-debugger/lib/evm/reducers.js | 2 +- packages/truffle-debugger/package.json | 3 +- packages/truffle-decoder/lib/utils.ts | 6 +- packages/truffle-decoder/package.json | 2 +- yarn.lock | 10 +- 15 files changed, 278 insertions(+), 182 deletions(-) create mode 100644 packages/truffle-codec-utils/src/abi.ts create mode 100644 packages/truffle-codec-utils/src/compiler.ts diff --git a/packages/truffle-codec-utils/package.json b/packages/truffle-codec-utils/package.json index 57b0f23ab73..f51f674a237 100644 --- a/packages/truffle-codec-utils/package.json +++ b/packages/truffle-codec-utils/package.json @@ -4,13 +4,11 @@ "description": "Utilities for decoding data from the EVM or encoding it for the EVM", "dependencies": { "bn.js": "^4.11.8", - "json-schema-to-typescript": "^6.1.3", "lodash.clonedeep": "^4.5.0", "lodash.escaperegexp": "^4.1.2", "semver": "^6.1.1", "truffle-contract-schema": "^3.0.11", - "web3": "1.0.0-beta.37", - "web3-eth-abi": "1.0.0-beta.52" + "web3": "1.0.0-beta.37" }, "main": "dist/index.js", "types": "src/index.ts", @@ -35,7 +33,8 @@ "@types/lodash.clonedeep": "^4.5.4", "@types/lodash.escaperegexp": "^4.1.6", "@types/semver": "^6.0.0", - "@types/web3": "^1.0.5", + "@types/web3": "^1.0.19", + "json-schema-to-typescript": "^6.1.3", "typescript": "^3.1.3" }, "gitHead": "b207efb3c1409746537293b3e0fc27350029188e" diff --git a/packages/truffle-codec-utils/src/abi.ts b/packages/truffle-codec-utils/src/abi.ts new file mode 100644 index 00000000000..16a71c901c1 --- /dev/null +++ b/packages/truffle-codec-utils/src/abi.ts @@ -0,0 +1,224 @@ +import debugModule from "debug"; +const debug = debugModule("decode-utils:abi"); + +import { Abi as SchemaAbi } from "truffle-contract-schema/spec"; +import { EVM as EVMUtils } from "./evm"; +import { AstDefinition, AstReferences } from "./ast"; +import { Definition as DefinitionUtils } from "./definition"; +import Web3 from "web3"; + +//NOTE: SchemaAbi is kind of loose and a pain to use. +//So we'll generally coerce things to Abi before use. + +export namespace AbiUtils { + + export type Abi = AbiEntry[]; + + export type AbiEntry = FunctionAbiEntry | ConstructorAbiEntry | FallbackAbiEntry | EventAbiEntry; + + export interface FunctionAbiEntry { + type: "function"; + name: string; + inputs: AbiParameter[]; + outputs: AbiParameter[]; + stateMutability?: "payable" | "nonpayable" | "view" | "pure"; //only in newer ones + constant?: boolean; //only in older ones + payable?: boolean; //only in older ones + } + + export interface ConstructorAbiEntry { + type: "constructor"; + inputs: AbiParameter[]; + stateMutability?: "payable" | "nonpayable"; //only in newer ones + payable?: boolean; //only in older ones + } + + export interface FallbackAbiEntry { + type: "fallback"; + stateMutability?: "payable" | "nonpayable"; //only in newer ones + payable?: boolean; //only in older ones + } + + export interface EventAbiEntry { + type: "event"; + name: string; + inputs: AbiParameter[]; + anonymous: boolean; + } + + export interface AbiParameter { + name: string; + type: string; + indexed?: boolean; //only present for inputs + components?: AbiParameter[]; //only preset for tuples (structs) + } + + export interface FunctionAbiEntryWithSelector extends FunctionAbiEntry { + signature: string; //note: this should be the SELECTOR, + //not the written-out signature. it's called "signature" + //for compatibility. + } + + export interface EventAbiEntryWithSelector extends EventAbiEntry { + signature: string; //note: this should be the SELECTOR, + //not the written-out signature. it's called "signature" + //for compatibility. + } + + export type AbiEntryWithSelector = FunctionAbiEntryWithSelector | EventAbiEntryWithSelector; + + export interface AbiBySelectors { + [selector: string]: AbiEntryWithSelector + //note this necessary excludes constructor/fallback + } + + export function computeSelectors(abiLoose: Abi | SchemaAbi | undefined): AbiBySelectors | undefined { + if(abiLoose === undefined) { + return undefined; + } + const abi = abiLoose; + return Object.assign({}, + ...abi.filter( + (abiEntry: AbiEntry) => abiEntry.type === "function" || abiEntry.type === "event" + ).map( + (abiEntry: FunctionAbiEntry | EventAbiEntry) => { + let signature = abiSelector(abiEntry); + return { + [signature]: { + ...abiEntry, + signature + } + }; + } + ) + ) + } + + //does this ABI have a payable fallback function? + export function isABIPayable(abiLoose: Abi | SchemaAbi | undefined): boolean | undefined { + if(abiLoose === undefined) { + return undefined; + } + const abi = abiLoose; + return abi.some( + (abiEntry: AbiEntry) => + abiEntry.type === "fallback" && abiMutability(abiEntry) === "payable" + ); + } + + //shim for old abi versions + function abiMutability(abiEntry: FunctionAbiEntry | ConstructorAbiEntry | FallbackAbiEntry): "pure" | "view" | "nonpayable" | "payable" { + if(abiEntry.stateMutability !== undefined) { + return abiEntry.stateMutability; + } + if(abiEntry.payable) { + return "payable"; + } + if(abiEntry.type === "function" && abiEntry.constant) { + return "view"; + } + return "nonpayable"; + } + + export function matchesAbi(abiEntry: AbiEntry, node: AstDefinition, referenceDeclarations: AstReferences): boolean { + //first: does the basic name and type match? + switch(node.nodeType) { + case "FunctionDefinition": + if(node.visibility !== "external" && node.visibility !== "public") { + return false; + } + if(abiEntry.type !== DefinitionUtils.functionKind(node)) { + return false; + } + if(abiEntry.type === "function") { + if(node.name !== abiEntry.name) { + return false; + } + } + break; + case "EventDefinition": + if(abiEntry.type !== "event") { + return false; + } + if(node.name !== abiEntry.name) { + return false; + } + break; + default: + return false; + } + //if it's a fallback function, we're done + if(abiEntry.type === "fallback") { + return true; + } + //otherwise, we've got to start checking input types (we don't check output types) + return matchesAbiParameters(abiEntry.inputs, node.parameters.parameters, referenceDeclarations); + } + + function matchesAbiParameters(abiParameters: AbiParameter[], nodeParameters: AstDefinition[], referenceDeclarations: AstReferences): boolean { + if(abiParameters.length !== nodeParameters.length) { + return false; + } + for(let i = 0; i < abiParameters.length; i++) { + if(!matchesAbiType(abiParameters[i], nodeParameters[i], referenceDeclarations)) { + return false; + } + } + return true; + } + + //TODO: add error-handling + function matchesAbiType(abiParameter: AbiParameter, nodeParameter: AstDefinition, referenceDeclarations: AstReferences): boolean { + if(DefinitionUtils.toAbiType(nodeParameter, referenceDeclarations) !== abiParameter.type) { + return false; + } + if(abiParameter.type.startsWith("tuple")) { + let referenceDeclaration = referenceDeclarations[DefinitionUtils.typeId(nodeParameter)]; + return matchesAbiParameters(abiParameter.components, referenceDeclaration.members, referenceDeclarations); + } + else { + return true; + } + } + + //NOTE: this function returns the written out SIGNATURE, not the SELECTOR + function abiSignature(abiEntry: FunctionAbiEntry | EventAbiEntry): string { + return abiEntry.name + abiTupleSignature(abiEntry.inputs); + } + + function abiTupleSignature(parameters: AbiParameter[]): string { + let components = parameters.map(abiTypeSignature); + return "(" + components.join(",") + ")"; + } + + function abiTypeSignature(parameter: AbiParameter): string { + let tupleMatch = parameter.type.match(/tuple(.*)/); + if(tupleMatch === null) { //does not start with "tuple" + return parameter.type; + } + else { + let tail = tupleMatch[1]; //everything after "tuple" + let tupleSignature = abiTupleSignature(parameter.components); + return tupleSignature + tail; + } + } + + export function abiSelector(abiEntry: FunctionAbiEntry | EventAbiEntry): string { + //first, try reading it from the entry; only recompute if needed + let storedSelector = (abiEntry).signature; + if(storedSelector !== undefined) { + return storedSelector; + } + let signature = abiSignature(abiEntry); + //NOTE: web3's soliditySha3 has a problem if the empty + //string is passed in. Fortunately, that should never happen here. + let hash = Web3.utils.soliditySha3({type: "string", value: signature}); + switch(abiEntry.type) { + case "event": + return hash; + case "function": + return hash.slice(0, 2 + 2 * EVMUtils.SELECTOR_SIZE); //arithmetic to account for hex string + } + } + +} diff --git a/packages/truffle-codec-utils/src/compiler.ts b/packages/truffle-codec-utils/src/compiler.ts new file mode 100644 index 00000000000..9df329800bd --- /dev/null +++ b/packages/truffle-codec-utils/src/compiler.ts @@ -0,0 +1,6 @@ +export interface CompilerVersion { + name?: string; + version?: string; + //NOTE: both these should really be present, + //but they need to be optional for compilation reasons +} diff --git a/packages/truffle-codec-utils/src/contexts.ts b/packages/truffle-codec-utils/src/contexts.ts index 5ab8b430e01..f9666dd35f4 100644 --- a/packages/truffle-codec-utils/src/contexts.ts +++ b/packages/truffle-codec-utils/src/contexts.ts @@ -1,16 +1,14 @@ import debugModule from "debug"; const debug = debugModule("decode-utils:contexts"); -import { Abi } from "truffle-contract-schema/spec"; -import { AbiCoder } from "web3-eth-abi"; -import { AbiItem, AbiInput } from "web3-utils"; -const abiCoder = new AbiCoder(); import escapeRegExp from "lodash.escaperegexp"; import { EVM } from "./evm"; +import { Abi as SchemaAbi } from "truffle-contract-schema/spec"; +import { AbiUtils } from "./abi"; import { Types } from "./types/types"; import { AstDefinition, AstReferences } from "./ast"; -import { Definition as DefinitionUtils } from "./definition"; +import { CompilerVersion } from "./compiler"; export namespace Contexts { @@ -39,7 +37,7 @@ export namespace Contexts { contractName?: string; contractId?: number; contractKind?: "contract" | "library"; //should never be "interface" - abi?: FunctionAbiWithSignatures; + abi?: AbiUtils.AbiBySelectors; payable?: boolean; compiler?: CompilerVersion; } @@ -53,93 +51,13 @@ export namespace Contexts { contractName?: string; contractId?: number; contractKind?: "contract" | "library"; //should never be "interface" - abi?: Abi; + abi?: SchemaAbi; sourceMap?: string; primarySource?: number; compiler?: CompilerVersion; payable?: boolean; } - export interface CompilerVersion { - name?: string; - version?: string; - //NOTE: both these should really be present, - //but they need to be optional for compilation reasons - } - - //really, we should import the ABI spec from truffle-contract-schema; - //unfotunately, that doesn't include signatures. so here's a sloppy - //recreation that includes those; sorry for the duplication, but this seems - //the easiest way offhand - //(note that this is explicitly restricted to type function, since that's all - //we care about here) - export interface FunctionAbiEntryWithSignature { - constant: boolean; - inputs: FunctionAbiParameter[]; - name: string; - outputs: FunctionAbiParameter[]; - payable: boolean; - stateMutability: "pure" | "view" | "nonpayable" | "payable"; - type: "function"; //again, event/fallback/constructor are excluded - signature: string; - } - - export interface FunctionAbiWithSignatures { - [signature: string]: FunctionAbiEntryWithSignature - } - - //again, this is only for function parameters, not event parameters - export interface FunctionAbiParameter { - components?: FunctionAbiParameter[]; - name: string; - type: string; //restricting this is a lot of the work of the real spec :P - [k: string]: any //this really should *not* be here, but it's in the spec - //for some reason, so we have to be able to handle it :-/ - } - - export function abiToFunctionAbiWithSignatures(abi: Abi | undefined): FunctionAbiWithSignatures | undefined { - if(abi === undefined) { - return undefined; - } - return Object.assign({}, - ...abi.filter( - abiEntry => abiEntry.type === "function" - ).map( - abiEntry => { - let signature: string; - //let's try forcing it and see if it already has a signature :P - signature = (abiEntry).signature; - debug("signature read: %s", signature); - //if not, compute it ourselves - if(signature === undefined) { - signature = abiCoder.encodeFunctionSignature(abiEntry); - //Notice the type coercion -- web3 and our schema describe things a little - //differently, and TypeScript complains. I think we just have to force it, - //sorry. - debug("signature computed: %s", signature); - } - return { - [signature]: { - ...abiEntry, - signature - } - }; - } - ) - ) - } - - //does this ABI have a payable fallback function? - export function isABIPayable(abi: Abi | undefined): boolean | undefined { - if(abi === undefined) { - return undefined; - } - return abi.some( - abiEntry => abiEntry.type === "fallback" && - (abiEntry.stateMutability === "payable" || abiEntry.payable) - ); - } - //I split these next two apart because the type system was giving me rouble export function findDecoderContext(contexts: DecoderContexts | DecoderContextsById, binary: string): DecoderContext | null { debug("binary %s", binary); @@ -273,63 +191,6 @@ export namespace Contexts { return newContexts; } - export function matchesAbi(abiEntry: AbiItem, node: AstDefinition, referenceDeclarations: AstReferences): boolean { - //first: does the basic name and type match? - switch(node.nodeType) { - case "FunctionDefinition": - if(node.visibility !== "external" && node.visibility !== "public") { - return false; - } - if(abiEntry.type !== DefinitionUtils.functionKind(node)) { - return false; - } - if(DefinitionUtils.functionKind(node) === "function") { - if(node.name !== abiEntry.name) { - return false; - } - } - break; - case "EventDefinition": - if(abiEntry.type !== "event") { - return false; - } - if(node.name !== abiEntry.name) { - return false; - } - break; - default: - return false; - } - //if so, we've got to start checking input types (we don't check output types) - return matchesAbiParameters(abiEntry.inputs, node.parameters.parameters, referenceDeclarations); - } - - function matchesAbiParameters(abiParameters: AbiInput[], nodeParameters: AstDefinition[], referenceDeclarations: AstReferences): boolean { - if(abiParameters.length !== nodeParameters.length) { - return false; - } - for(let i = 0; i < abiParameters.length; i++) { - if(!matchesAbiType(abiParameters[i], nodeParameters[i], referenceDeclarations)) { - return false; - } - } - return true; - } - - //TODO: add error-handling - function matchesAbiType(abiParameter: AbiInput, nodeParameter: AstDefinition, referenceDeclarations: AstReferences): boolean { - if(DefinitionUtils.toAbiType(nodeParameter, referenceDeclarations) !== abiParameter.type) { - return false; - } - if(abiParameter.type.startsWith("tuple")) { - let referenceDeclaration = referenceDeclarations[DefinitionUtils.typeId(nodeParameter)]; - return matchesAbiParameters(abiParameter.components, referenceDeclaration.members, referenceDeclarations); - } - else { - return true; - } - } - export function contextToType(context: DecoderContext | DebuggerContext): Types.ContractType { return { typeClass: "contract", diff --git a/packages/truffle-codec-utils/src/definition.ts b/packages/truffle-codec-utils/src/definition.ts index c8c03f08817..149fcd84225 100644 --- a/packages/truffle-codec-utils/src/definition.ts +++ b/packages/truffle-codec-utils/src/definition.ts @@ -4,6 +4,7 @@ const debug = debugModule("decode-utils:definition"); import { EVM as EVMUtils } from "./evm"; import { AstDefinition, AstReferences, Scopes } from "./ast"; import { Contexts } from "./contexts"; +import { CompilerVersion } from "./compiler"; import BN from "bn.js"; import cloneDeep from "lodash.clonedeep"; import semver from "semver"; @@ -136,7 +137,7 @@ export namespace Definition { } //HACK: you can set compiler to null to force nonpayable - export function isAddressPayable(definition: AstDefinition, compiler: Contexts.CompilerVersion | null): boolean { + export function isAddressPayable(definition: AstDefinition, compiler: CompilerVersion | null): boolean { if(compiler === null) { return false; } diff --git a/packages/truffle-codec-utils/src/index.ts b/packages/truffle-codec-utils/src/index.ts index 01f2f5ebae1..cd0182bbf21 100644 --- a/packages/truffle-codec-utils/src/index.ts +++ b/packages/truffle-codec-utils/src/index.ts @@ -3,5 +3,7 @@ export * from "./evm"; export * from "./definition"; export * from "./ast"; export * from "./contexts"; +export * from "./abi"; +export * from "./compiler"; export * from "./types/types"; export * from "./types/values"; diff --git a/packages/truffle-codec-utils/src/types/types.ts b/packages/truffle-codec-utils/src/types/types.ts index 8d5b39b51bf..8c17ca0dfa1 100644 --- a/packages/truffle-codec-utils/src/types/types.ts +++ b/packages/truffle-codec-utils/src/types/types.ts @@ -22,6 +22,7 @@ import BN from "bn.js"; import { AstDefinition, AstReferences } from "../ast"; import { Definition as DefinitionUtils } from "../definition"; import { Contexts } from "../contexts"; +import { CompilerVersion } from "../compiler"; export namespace Types { @@ -172,7 +173,7 @@ export namespace Types { //NOTE: set forceLocation to *null* to force no location. leave it undefined //to not force a location. //NOTE: set compiler to null to force addresses to *not* be payable (HACK)? - export function definitionToType(definition: AstDefinition, compiler: Contexts.CompilerVersion | null, forceLocation?: string | null): Type { + export function definitionToType(definition: AstDefinition, compiler: CompilerVersion | null, forceLocation?: string | null): Type { debug("definition %O", definition); let typeClass = DefinitionUtils.typeClass(definition); switch(typeClass) { @@ -397,7 +398,7 @@ export namespace Types { //whereas the above takes variable definitions, this takes the actual type //definition - export function definitionToStoredType(definition: AstDefinition, compiler: Contexts.CompilerVersion): UserDefinedType { + export function definitionToStoredType(definition: AstDefinition, compiler: CompilerVersion): UserDefinedType { switch(definition.nodeType) { case "StructDefinition": { let id = definition.id; diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index d4031cace4f..3793f4ece5c 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -3,14 +3,10 @@ const debug = debugModule("decoder-core:allocate:abi"); import * as Pointer from "../types/pointer"; import * as Allocations from "../types/allocation"; -import { AstDefinition, AstReferences } from "truffle-codec-utils"; +import { AstDefinition, AstReferences, AbiUtils } from "truffle-codec-utils"; import * as CodecUtils from "truffle-codec-utils"; import partition from "lodash.partition"; -import { AbiCoder } from "web3-eth-abi"; -import { AbiItem } from "web3-utils"; -const abiCoder = new AbiCoder(); - export function getAbiAllocations(referenceDeclarations: AstReferences): Allocations.AbiAllocations { let allocations: Allocations.AbiAllocations = {}; for(const node of Object.values(referenceDeclarations)) { @@ -214,7 +210,7 @@ export function isTypeDynamic(dataType: CodecUtils.Types.Type, allocations?: All //TODO add error-handling //TODO: check accesses to abi & node members function allocateCalldata( - abiEntry: AbiItem, + abiEntry: AbiUtils.FunctionAbiEntry | AbiUtils.ConstructorAbiEntry, contractId: number, referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations, @@ -232,7 +228,7 @@ function allocateCalldata( //for a constructor, we only want to search the particular contract, which let contractNode = referenceDeclarations[contractId]; node = contractNode.nodes.find( - functionNode => CodecUtils.Contexts.matchesAbi( + functionNode => AbiUtils.matchesAbi( abiEntry, functionNode, referenceDeclarations ) ); @@ -243,7 +239,7 @@ function allocateCalldata( //search through base contracts, from most derived (right) to most base (left) node = linearizedBaseContracts.reduceRight( (foundNode, baseContractId) => foundNode || referenceDeclarations[baseContractId].nodes.find( - functionNode => CodecUtils.Contexts.matchesAbi( + functionNode => AbiUtils.matchesAbi( abiEntry, functionNode, referenceDeclarations ) ), @@ -281,7 +277,7 @@ function allocateCalldata( //TODO add error-handling //TODO: check accesses to abi & node members function allocateEvent( - abiEntry: AbiItem, + abiEntry: AbiUtils.EventAbiEntry, contractId: number, referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations @@ -291,7 +287,7 @@ function allocateEvent( //search through base contracts, from most derived (right) to most base (left) const node: AstDefinition = linearizedBaseContracts.reduceRight( (foundNode, baseContractId) => foundNode || referenceDeclarations[baseContractId].nodes.find( - eventNode => CodecUtils.Contexts.matchesAbi( + eventNode => AbiUtils.matchesAbi( abiEntry, eventNode, referenceDeclarations ) ), @@ -341,7 +337,7 @@ function allocateEvent( } function getCalldataAllocationsForContract( - abi: AbiItem[], + abi: AbiUtils.Abi, contractId: number, constructorContext: CodecUtils.Contexts.DecoderContext, referenceDeclarations: AstReferences, @@ -359,7 +355,7 @@ function getCalldataAllocationsForContract( ); } else if(abiEntry.type === "function") { - allocations.functionAllocations[abiCoder.encodeFunctionSignature(abiEntry)] = + allocations.functionAllocations[AbiUtils.abiSelector(abiEntry)] = allocateCalldata( abiEntry, contractId, @@ -391,22 +387,22 @@ export function getCalldataAllocations(contracts: Allocations.ContractAllocation return Object.assign({}, ...contracts.map( ({abi, id, constructorContext}) => ({ [id]: getCalldataAllocationsForContract( - abi, id, constructorContext, referenceDeclarations, abiAllocations + abi, id, constructorContext, referenceDeclarations, abiAllocations ) }) )); } function getEventAllocationsForContract( - abi: AbiItem[], + abi: AbiUtils.Abi, contractId: number, referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations ): Allocations.EventAllocations { return Object.assign({}, ...abi - .filter(abiEntry => abiEntry.type === "event") - .map(abiEntry => - ({ [abiCoder.encodeEventSignature(abiEntry)] : + .filter((abiEntry: AbiUtils.AbiEntry) => abiEntry.type === "event") + .map((abiEntry: AbiUtils.EventAbiEntry) => + ({ [AbiUtils.abiSelector(abiEntry)] : allocateEvent(abiEntry, contractId, referenceDeclarations, abiAllocations) }) ) @@ -416,6 +412,6 @@ function getEventAllocationsForContract( //note: constructor context is ignored by this function; no need to pass it in export function getEventAllocations(contracts: Allocations.ContractAllocationInfo[], referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations): Allocations.EventAllocations { return Object.assign({}, ...contracts.map( - ({abi, id}) => getEventAllocationsForContract(abi, id, referenceDeclarations, abiAllocations) + ({abi, id}) => getEventAllocationsForContract(abi, id, referenceDeclarations, abiAllocations) )); } diff --git a/packages/truffle-codec/package.json b/packages/truffle-codec/package.json index 8bd3e62d814..4046ee3ef0a 100644 --- a/packages/truffle-codec/package.json +++ b/packages/truffle-codec/package.json @@ -31,17 +31,16 @@ "@types/debug": "^0.0.31", "@types/lodash.partition": "^4.6.6", "@types/lodash.sum": "^4.0.6", + "json-schema-to-typescript": "^6.1.3", "typescript": "^3.1.3" }, "dependencies": { "bn.js": "^4.11.8", "debug": "^4.1.0", - "json-schema-to-typescript": "^6.1.3", "lodash.partition": "^4.6.0", "lodash.sum": "^4.0.2", - "truffle-contract-schema": "^3.0.11", "truffle-codec-utils": "^1.0.12", - "web3-eth-abi": "1.0.0-beta.52" + "truffle-contract-schema": "^3.0.11" }, "peerDependencies": { "truffle": "^5.0.14" diff --git a/packages/truffle-debugger/lib/data/selectors/index.js b/packages/truffle-debugger/lib/data/selectors/index.js index c5a68ead27f..c12b66392cd 100644 --- a/packages/truffle-debugger/lib/data/selectors/index.js +++ b/packages/truffle-debugger/lib/data/selectors/index.js @@ -76,7 +76,7 @@ function debuggerContextToDecoderContext(context) { contractId, contractKind, isConstructor, - abi: CodecUtils.Contexts.abiToFunctionAbiWithSignatures(abi), + abi: CodecUtils.AbiUtils.computeSelectors(abi), payable, compiler }; @@ -235,7 +235,7 @@ const data = createSelectorTree({ * 1. we now index by contract ID rather than hash * 2. we strip out context, sourceMap, and primarySource * 3. we alter abi in several ways: - * 3a. we strip abi down to just (ordinary) functions + * 3a. we strip out constructors and fallback functions * 3b. we augment these functions with signatures (here meaning selectors) * 3c. abi is now an object, not an array, and indexed by these signatures */ diff --git a/packages/truffle-debugger/lib/evm/reducers.js b/packages/truffle-debugger/lib/evm/reducers.js index b9f9875be77..a101c62016f 100644 --- a/packages/truffle-debugger/lib/evm/reducers.js +++ b/packages/truffle-debugger/lib/evm/reducers.js @@ -54,7 +54,7 @@ function contexts(state = DEFAULT_CONTEXTS, action) { contractId, contractKind, isConstructor, - payable: CodecUtils.Contexts.isABIPayable(abi) + payable: CodecUtils.AbiUtils.isABIPayable(abi) } } }; diff --git a/packages/truffle-debugger/package.json b/packages/truffle-debugger/package.json index 1e0dd2dab04..02c7f6d599a 100644 --- a/packages/truffle-debugger/package.json +++ b/packages/truffle-debugger/package.json @@ -35,8 +35,7 @@ "truffle-codec": "^3.0.3", "truffle-expect": "^0.0.9", "truffle-solidity-utils": "^1.2.3", - "web3": "1.0.0-beta.37", - "web3-eth-abi": "1.0.0-beta.37" + "web3": "1.0.0-beta.37" }, "devDependencies": { "async": "2.6.1", diff --git a/packages/truffle-decoder/lib/utils.ts b/packages/truffle-decoder/lib/utils.ts index 93db142711a..3af63214b2b 100644 --- a/packages/truffle-decoder/lib/utils.ts +++ b/packages/truffle-decoder/lib/utils.ts @@ -1,4 +1,4 @@ -import { AstDefinition, AstReferences, Contexts as ContextsUtils } from "truffle-codec-utils"; +import { AstDefinition, AstReferences, AbiUtils, Contexts as ContextsUtils } from "truffle-codec-utils"; import { ContractObject } from "truffle-contract-schema/spec"; export function getContractNode(contract: ContractObject): AstDefinition { @@ -17,8 +17,8 @@ export function makeContext(contract: ContractObject, node: AstDefinition, isCon contractId: node.id, contractKind: node.contractKind, isConstructor, - abi: ContextsUtils.abiToFunctionAbiWithSignatures(contract.abi), - payable: ContextsUtils.isABIPayable(contract.abi), + abi: AbiUtils.computeSelectors(contract.abi), + payable: AbiUtils.isABIPayable(contract.abi), compiler: contract.compiler }; } diff --git a/packages/truffle-decoder/package.json b/packages/truffle-decoder/package.json index 912cb4729b2..6a5d9ecd0d4 100644 --- a/packages/truffle-decoder/package.json +++ b/packages/truffle-decoder/package.json @@ -32,7 +32,6 @@ "async-eventemitter": "^0.2.4", "bn.js": "^4.11.8", "debug": "^4.1.1", - "json-schema-to-typescript": "^6.1.3", "lodash.isequal": "^4.5.0", "truffle-contract-schema": "^3.0.9", "truffle-decode-utils": "^1.0.12", @@ -46,6 +45,7 @@ "@types/lodash.isequal": "^4.5.5", "@types/web3": "1.0.18", "typescript": "^3.5.1", + "json-schema-to-typescript": "^6.1.3", "web3-core": "1.0.0-beta.37" } } diff --git a/yarn.lock b/yarn.lock index e77f231dd63..49774cc9f29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1096,7 +1096,7 @@ resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.8.18.tgz#025371cfe062439aaa088d2b64af87cbcfe3c2b7" integrity sha512-mXQ8u416FWMPjp2zWrcVmOZtzKQM6IeyVcuE+RGF/04JLBrMjfnmNKn74VN8fIkFzU97TpzkP0ny0p0h65eC7w== -"@types/web3@1.0.18", "@types/web3@^1.0.18", "@types/web3@^1.0.5": +"@types/web3@1.0.18", "@types/web3@^1.0.18": version "1.0.18" resolved "https://registry.yarnpkg.com/@types/web3/-/web3-1.0.18.tgz#87a8651041d21fc37602ff02327df2c7ecf105d1" integrity sha512-uXQL0LDszt2f476LEmYM6AvSv9F4vU4hWQvlUhwfLHNlIB6OyBXoYsCzWAIhhnc5U0HA7ZBcPybxRJ/yfA6THg== @@ -1104,6 +1104,14 @@ "@types/bn.js" "*" "@types/underscore" "*" +"@types/web3@^1.0.19": + version "1.0.19" + resolved "https://registry.yarnpkg.com/@types/web3/-/web3-1.0.19.tgz#46b85d91d398ded9ab7c85a5dd57cb33ac558924" + integrity sha512-fhZ9DyvDYDwHZUp5/STa9XW2re0E8GxoioYJ4pEUZ13YHpApSagixj7IAdoYH5uAK+UalGq6Ml8LYzmgRA/q+A== + dependencies: + "@types/bn.js" "*" + "@types/underscore" "*" + "@webassemblyjs/ast@1.8.5": version "1.8.5" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" From 66c4eda2e13faeecb85340fbc6007c07b1e9194c Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 19 Jun 2019 19:45:50 -0400 Subject: [PATCH 23/89] Rename debug modules --- packages/truffle-codec-utils/src/abi.ts | 2 +- packages/truffle-codec-utils/src/contexts.ts | 2 +- packages/truffle-codec-utils/src/conversion.ts | 5 ++++- packages/truffle-codec-utils/src/definition.ts | 2 +- packages/truffle-codec-utils/src/evm.ts | 2 +- packages/truffle-codec-utils/src/types/types.ts | 2 +- packages/truffle-codec-utils/src/types/values.ts | 2 +- packages/truffle-codec/lib/allocate/abi.ts | 2 +- packages/truffle-codec/lib/allocate/memory.ts | 2 +- packages/truffle-codec/lib/allocate/storage.ts | 2 +- packages/truffle-codec/lib/decode/abi.ts | 2 +- packages/truffle-codec/lib/decode/constant.ts | 2 +- packages/truffle-codec/lib/decode/event.ts | 2 +- packages/truffle-codec/lib/decode/index.ts | 2 +- packages/truffle-codec/lib/decode/memory.ts | 2 +- packages/truffle-codec/lib/decode/special.ts | 2 +- packages/truffle-codec/lib/decode/stack.ts | 2 +- packages/truffle-codec/lib/decode/storage.ts | 2 +- packages/truffle-codec/lib/decode/value.ts | 2 +- packages/truffle-codec/lib/interface/decoding.ts | 2 +- packages/truffle-codec/lib/read/constant.ts | 2 +- packages/truffle-codec/lib/read/stack.ts | 2 +- packages/truffle-codec/lib/read/storage.ts | 2 +- packages/truffle-codec/lib/types/storage.ts | 2 +- 24 files changed, 27 insertions(+), 24 deletions(-) diff --git a/packages/truffle-codec-utils/src/abi.ts b/packages/truffle-codec-utils/src/abi.ts index 16a71c901c1..804fc5e3f3c 100644 --- a/packages/truffle-codec-utils/src/abi.ts +++ b/packages/truffle-codec-utils/src/abi.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decode-utils:abi"); +const debug = debugModule("codec-utils:abi"); import { Abi as SchemaAbi } from "truffle-contract-schema/spec"; import { EVM as EVMUtils } from "./evm"; diff --git a/packages/truffle-codec-utils/src/contexts.ts b/packages/truffle-codec-utils/src/contexts.ts index f9666dd35f4..3f6f44fe417 100644 --- a/packages/truffle-codec-utils/src/contexts.ts +++ b/packages/truffle-codec-utils/src/contexts.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decode-utils:contexts"); +const debug = debugModule("codec-utils:contexts"); import escapeRegExp from "lodash.escaperegexp"; diff --git a/packages/truffle-codec-utils/src/conversion.ts b/packages/truffle-codec-utils/src/conversion.ts index f45e0d49e3e..3725c262df3 100644 --- a/packages/truffle-codec-utils/src/conversion.ts +++ b/packages/truffle-codec-utils/src/conversion.ts @@ -1,3 +1,6 @@ +import debugModule from "debug"; +const debug = debugModule("codec-utils:conversion"); + import BN from "bn.js"; import Web3 from "web3"; import { Constants } from "./constants"; @@ -62,7 +65,7 @@ export namespace Conversion { bytes.set(prior, padLength - prior.length); } - // debug("bytes: %o", bytes); + debug("bytes: %o", bytes); let string = bytes.reduce( (str, byte) => `${str}${pad(byte.toString(16))}`, "" diff --git a/packages/truffle-codec-utils/src/definition.ts b/packages/truffle-codec-utils/src/definition.ts index 149fcd84225..9c3ec98a3ea 100644 --- a/packages/truffle-codec-utils/src/definition.ts +++ b/packages/truffle-codec-utils/src/definition.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decode-utils:definition"); +const debug = debugModule("codec-utils:definition"); import { EVM as EVMUtils } from "./evm"; import { AstDefinition, AstReferences, Scopes } from "./ast"; diff --git a/packages/truffle-codec-utils/src/evm.ts b/packages/truffle-codec-utils/src/evm.ts index de8710a62da..c80ca6b42a3 100644 --- a/packages/truffle-codec-utils/src/evm.ts +++ b/packages/truffle-codec-utils/src/evm.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decode-utils:evm"); +const debug = debugModule("codec-utils:evm"); import BN from "bn.js"; import Web3 from "web3"; diff --git a/packages/truffle-codec-utils/src/types/types.ts b/packages/truffle-codec-utils/src/types/types.ts index 8c17ca0dfa1..a7b643d113c 100644 --- a/packages/truffle-codec-utils/src/types/types.ts +++ b/packages/truffle-codec-utils/src/types/types.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decode-utils:types"); +const debug = debugModule("codec-utils:types"); //type objects for Solidity types //these will just be defined as interfaces; there's not any particular need for diff --git a/packages/truffle-codec-utils/src/types/values.ts b/packages/truffle-codec-utils/src/types/values.ts index 9fb236df96a..5fdaf064d47 100644 --- a/packages/truffle-codec-utils/src/types/values.ts +++ b/packages/truffle-codec-utils/src/types/values.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decode-utils:values"); +const debug = debugModule("codec-utils:values"); //objects for Solidity values diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index 3793f4ece5c..db38aad8493 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:allocate:abi"); +const debug = debugModule("codec:allocate:abi"); import * as Pointer from "../types/pointer"; import * as Allocations from "../types/allocation"; diff --git a/packages/truffle-codec/lib/allocate/memory.ts b/packages/truffle-codec/lib/allocate/memory.ts index 119e3b71453..5a27f5a3f5c 100644 --- a/packages/truffle-codec/lib/allocate/memory.ts +++ b/packages/truffle-codec/lib/allocate/memory.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:allocate:memory"); +const debug = debugModule("codec:allocate:memory"); import { MemoryPointer } from "../types/pointer"; import { MemoryAllocations, MemoryAllocation, MemoryMemberAllocation } from "../types/allocation"; diff --git a/packages/truffle-codec/lib/allocate/storage.ts b/packages/truffle-codec/lib/allocate/storage.ts index 9502e84e645..457a79c9ecc 100644 --- a/packages/truffle-codec/lib/allocate/storage.ts +++ b/packages/truffle-codec/lib/allocate/storage.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:allocate:storage"); +const debug = debugModule("codec:allocate:storage"); import { StoragePointer } from "../types/pointer"; import { StorageAllocations, StorageAllocation, StorageMemberAllocation } from "../types/allocation"; diff --git a/packages/truffle-codec/lib/decode/abi.ts b/packages/truffle-codec/lib/decode/abi.ts index 2fd56e55fc6..6026708b6ff 100644 --- a/packages/truffle-codec/lib/decode/abi.ts +++ b/packages/truffle-codec/lib/decode/abi.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:decode:abi"); +const debug = debugModule("codec:decode:abi"); import read from "../read"; import * as CodecUtils from "truffle-codec-utils"; diff --git a/packages/truffle-codec/lib/decode/constant.ts b/packages/truffle-codec/lib/decode/constant.ts index 6bed4b191d2..185a8ea837e 100644 --- a/packages/truffle-codec/lib/decode/constant.ts +++ b/packages/truffle-codec/lib/decode/constant.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:decode:constant"); +const debug = debugModule("codec:decode:constant"); import * as CodecUtils from "truffle-codec-utils"; import { Types, Values } from "truffle-codec-utils"; diff --git a/packages/truffle-codec/lib/decode/event.ts b/packages/truffle-codec/lib/decode/event.ts index 167b363de94..e855dd8815d 100644 --- a/packages/truffle-codec/lib/decode/event.ts +++ b/packages/truffle-codec/lib/decode/event.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:decode:event"); +const debug = debugModule("codec:decode:event"); import decodeValue from "./value"; import read from "../read"; diff --git a/packages/truffle-codec/lib/decode/index.ts b/packages/truffle-codec/lib/decode/index.ts index ecf71054aaf..138c836ae0a 100644 --- a/packages/truffle-codec/lib/decode/index.ts +++ b/packages/truffle-codec/lib/decode/index.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:decode"); +const debug = debugModule("codec:decode"); import decodeValue from "./value"; import decodeMemory from "./memory"; diff --git a/packages/truffle-codec/lib/decode/memory.ts b/packages/truffle-codec/lib/decode/memory.ts index 99eeaf1eb6f..1e39f5084cb 100644 --- a/packages/truffle-codec/lib/decode/memory.ts +++ b/packages/truffle-codec/lib/decode/memory.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:decode:memory"); +const debug = debugModule("codec:decode:memory"); import read from "../read"; import * as CodecUtils from "truffle-codec-utils"; diff --git a/packages/truffle-codec/lib/decode/special.ts b/packages/truffle-codec/lib/decode/special.ts index e834791995d..54e693e337a 100644 --- a/packages/truffle-codec/lib/decode/special.ts +++ b/packages/truffle-codec/lib/decode/special.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:decode:special"); +const debug = debugModule("codec:decode:special"); import * as CodecUtils from "truffle-codec-utils"; import { Types, Values } from "truffle-codec-utils"; diff --git a/packages/truffle-codec/lib/decode/stack.ts b/packages/truffle-codec/lib/decode/stack.ts index 79b15a85dcd..58f13cb4086 100644 --- a/packages/truffle-codec/lib/decode/stack.ts +++ b/packages/truffle-codec/lib/decode/stack.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:decode:stack"); +const debug = debugModule("codec:decode:stack"); import * as CodecUtils from "truffle-codec-utils"; import { Types, Values } from "truffle-codec-utils"; diff --git a/packages/truffle-codec/lib/decode/storage.ts b/packages/truffle-codec/lib/decode/storage.ts index db33856bb13..70afbce4578 100644 --- a/packages/truffle-codec/lib/decode/storage.ts +++ b/packages/truffle-codec/lib/decode/storage.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:decode:storage"); +const debug = debugModule("codec:decode:storage"); import read from "../read"; import * as CodecUtils from "truffle-codec-utils"; diff --git a/packages/truffle-codec/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts index 5f5a03b2a14..7a362053f71 100644 --- a/packages/truffle-codec/lib/decode/value.ts +++ b/packages/truffle-codec/lib/decode/value.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:decode:value"); +const debug = debugModule("codec:decode:value"); import read from "../read"; import * as CodecUtils from "truffle-codec-utils"; diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index c43c1cb74fe..ee3c5137fb0 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:interface"); +const debug = debugModule("codec:interface"); import { AstDefinition, Types, Values } from "truffle-codec-utils"; import * as CodecUtils from "truffle-codec-utils"; diff --git a/packages/truffle-codec/lib/read/constant.ts b/packages/truffle-codec/lib/read/constant.ts index c2d883dddc5..f5547cac7c6 100644 --- a/packages/truffle-codec/lib/read/constant.ts +++ b/packages/truffle-codec/lib/read/constant.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:read:constant"); +const debug = debugModule("codec:read:constant"); import * as CodecUtils from "truffle-codec-utils"; import BN from "bn.js"; diff --git a/packages/truffle-codec/lib/read/stack.ts b/packages/truffle-codec/lib/read/stack.ts index 5d563e22ddc..35c3797f471 100644 --- a/packages/truffle-codec/lib/read/stack.ts +++ b/packages/truffle-codec/lib/read/stack.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:read:stack"); +const debug = debugModule("codec:read:stack"); import * as CodecUtils from "truffle-codec-utils"; diff --git a/packages/truffle-codec/lib/read/storage.ts b/packages/truffle-codec/lib/read/storage.ts index 42b209e9b14..50af05e6626 100644 --- a/packages/truffle-codec/lib/read/storage.ts +++ b/packages/truffle-codec/lib/read/storage.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:read:storage"); +const debug = debugModule("codec:read:storage"); import * as CodecUtils from "truffle-codec-utils"; import { Slot, Range } from "../types/storage"; diff --git a/packages/truffle-codec/lib/types/storage.ts b/packages/truffle-codec/lib/types/storage.ts index 4dcdb8e7439..518244b338f 100644 --- a/packages/truffle-codec/lib/types/storage.ts +++ b/packages/truffle-codec/lib/types/storage.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder-core:types:storage"); +const debug = debugModule("codec:types:storage"); import * as CodecUtils from "truffle-codec-utils"; import BN from "bn.js"; From ca1592686f8542b4a405c8afd280c3eb486872c9 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 19 Jun 2019 20:29:17 -0400 Subject: [PATCH 24/89] Further reduce reliance on schema abi type --- packages/truffle-codec/lib/allocate/abi.ts | 4 ++-- packages/truffle-codec/lib/types/allocation.ts | 5 ++--- packages/truffle-codec/package.json | 4 +--- packages/truffle-decoder/lib/contract.ts | 6 +++--- packages/truffle-decoder/lib/wire.ts | 4 ++-- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index db38aad8493..3f6d81a9d7e 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -387,7 +387,7 @@ export function getCalldataAllocations(contracts: Allocations.ContractAllocation return Object.assign({}, ...contracts.map( ({abi, id, constructorContext}) => ({ [id]: getCalldataAllocationsForContract( - abi, id, constructorContext, referenceDeclarations, abiAllocations + abi, id, constructorContext, referenceDeclarations, abiAllocations ) }) )); @@ -412,6 +412,6 @@ function getEventAllocationsForContract( //note: constructor context is ignored by this function; no need to pass it in export function getEventAllocations(contracts: Allocations.ContractAllocationInfo[], referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations): Allocations.EventAllocations { return Object.assign({}, ...contracts.map( - ({abi, id}) => getEventAllocationsForContract(abi, id, referenceDeclarations, abiAllocations) + ({abi, id}) => getEventAllocationsForContract(abi, id, referenceDeclarations, abiAllocations) )); } diff --git a/packages/truffle-codec/lib/types/allocation.ts b/packages/truffle-codec/lib/types/allocation.ts index ecd1ff0bdb5..20f9cbac96f 100644 --- a/packages/truffle-codec/lib/types/allocation.ts +++ b/packages/truffle-codec/lib/types/allocation.ts @@ -1,11 +1,10 @@ import { StorageLength } from "./storage"; import * as Pointer from "./pointer"; -import { AstDefinition, Contexts } from "truffle-codec-utils"; -import { Abi } from "truffle-contract-schema/spec"; +import { AstDefinition, Contexts, AbiUtils } from "truffle-codec-utils"; //for passing to calldata/event allocation functions export interface ContractAllocationInfo { - abi: Abi; + abi: AbiUtils.Abi; id: number; constructorContext?: Contexts.DecoderContext; } diff --git a/packages/truffle-codec/package.json b/packages/truffle-codec/package.json index 4046ee3ef0a..76b1f8ffd2a 100644 --- a/packages/truffle-codec/package.json +++ b/packages/truffle-codec/package.json @@ -31,7 +31,6 @@ "@types/debug": "^0.0.31", "@types/lodash.partition": "^4.6.6", "@types/lodash.sum": "^4.0.6", - "json-schema-to-typescript": "^6.1.3", "typescript": "^3.1.3" }, "dependencies": { @@ -39,8 +38,7 @@ "debug": "^4.1.0", "lodash.partition": "^4.6.0", "lodash.sum": "^4.0.2", - "truffle-codec-utils": "^1.0.12", - "truffle-contract-schema": "^3.0.11" + "truffle-codec-utils": "^1.0.12" }, "peerDependencies": { "truffle": "^5.0.14" diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index d8f2a110377..fe9da0d102b 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -7,7 +7,7 @@ import AsyncEventEmitter from "async-eventemitter"; import Web3 from "web3"; import { ContractObject } from "truffle-contract-schema/spec"; import BN from "bn.js"; -import { Definition as DefinitionUtils, EVM, AstDefinition, AstReferences } from "truffle-codec-utils"; +import { Definition as DefinitionUtils, AbiUtils, EVM, AstDefinition, AstReferences } from "truffle-codec-utils"; import { BlockType, Transaction } from "web3/eth/types"; import { EventLog, Log } from "web3/types"; import { Provider } from "web3/providers"; @@ -133,7 +133,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.allocations.abi = Codec.getAbiAllocations(this.referenceDeclarations); this.allocations.calldata = Codec.getCalldataAllocations( [{ - abi: this.contract.abi, + abi: this.contract.abi, id: this.contractNode.id, constructorContext: this.constructorContext }], @@ -142,7 +142,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { ); this.allocations.event = Codec.getEventAllocations( [{ - abi: this.contract.abi, + abi: this.contract.abi, id: this.contractNode.id }], this.referenceDeclarations, diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index ecd4498fcfd..12abe26884f 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -7,7 +7,7 @@ import AsyncEventEmitter from "async-eventemitter"; import Web3 from "web3"; import { ContractObject } from "truffle-contract-schema/spec"; import BN from "bn.js"; -import { Definition as DefinitionUtils, EVM, AstDefinition, AstReferences } from "truffle-codec-utils"; +import { Definition as DefinitionUtils, AbiUtils, EVM, AstDefinition, AstReferences } from "truffle-codec-utils"; import { BlockType, Transaction } from "web3/eth/types"; import { EventLog, Log } from "web3/types"; import { Provider } from "web3/providers"; @@ -89,7 +89,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { let allocationInfo: Codec.ContractAllocationInfo[] = Object.entries(this.contracts).map( ([id, { abi }]) => ({ - abi, + abi: abi, id: parseInt(id), constructorContext: this.constructorContextsById[parseInt(id)] }) From 3ae90b8c3f37167f4ce361172c1939abc3ae823d Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 20 Jun 2019 00:40:15 -0400 Subject: [PATCH 25/89] Make new event allocation format; feed decoded events back through the encoder as a check --- packages/truffle-codec-utils/src/abi.ts | 4 + packages/truffle-codec-utils/src/evm.ts | 12 ++ packages/truffle-codec/lib/allocate/abi.ts | 34 +++-- packages/truffle-codec/lib/decode/abi.ts | 88 ++++++++++--- packages/truffle-codec/lib/decode/event.ts | 9 +- packages/truffle-codec/lib/decode/index.ts | 6 +- packages/truffle-codec/lib/decode/stack.ts | 2 +- packages/truffle-codec/lib/decode/value.ts | 59 ++++++++- .../truffle-codec/lib/interface/decoding.ts | 117 +++++++++++------- .../truffle-codec/lib/types/allocation.ts | 32 ++++- packages/truffle-codec/lib/types/errors.ts | 12 ++ packages/truffle-codec/lib/types/wire.ts | 7 +- packages/truffle-decoder/lib/contract.ts | 16 ++- packages/truffle-decoder/lib/types.ts | 2 +- packages/truffle-decoder/lib/wire.ts | 16 ++- 15 files changed, 317 insertions(+), 99 deletions(-) diff --git a/packages/truffle-codec-utils/src/abi.ts b/packages/truffle-codec-utils/src/abi.ts index 804fc5e3f3c..c4c9727b946 100644 --- a/packages/truffle-codec-utils/src/abi.ts +++ b/packages/truffle-codec-utils/src/abi.ts @@ -221,4 +221,8 @@ export namespace AbiUtils { } } + export function topicsCount(abiEntry: EventAbiEntry): number { + let selectorCount = abiEntry.anonymous ? 0 : 1; //if the event is not anonymous, we must account for the selector + return abiEntry.inputs.filter(({ indexed }) => indexed).length + selectorCount; + } } diff --git a/packages/truffle-codec-utils/src/evm.ts b/packages/truffle-codec-utils/src/evm.ts index c80ca6b42a3..fc0d23ce24e 100644 --- a/packages/truffle-codec-utils/src/evm.ts +++ b/packages/truffle-codec-utils/src/evm.ts @@ -36,4 +36,16 @@ export namespace EVM { } return ConversionUtils.toBN(sha); } + + export function equalData(bytes1: Uint8Array, bytes2: Uint8Array): boolean { + if(bytes1.length !== bytes2.length) { + return false; + } + for(let i = 0; i < bytes1.length; i++) { + if(bytes1[i] !== bytes2[i]) { + return false; + } + } + return true; + } } diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index 3f6d81a9d7e..e040d827678 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -398,20 +398,34 @@ function getEventAllocationsForContract( contractId: number, referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations -): Allocations.EventAllocations { - return Object.assign({}, ...abi - .filter((abiEntry: AbiUtils.AbiEntry) => abiEntry.type === "event") - .map((abiEntry: AbiUtils.EventAbiEntry) => - ({ [AbiUtils.abiSelector(abiEntry)] : - allocateEvent(abiEntry, contractId, referenceDeclarations, abiAllocations) +): Allocations.EventAllocationTemporary[] { + return abi.filter( + (abiEntry: AbiUtils.AbiEntry) => abiEntry.type === "event" && !abiEntry.anonymous + ).map( + (abiEntry: AbiUtils.EventAbiEntry) => + ({ + selector: AbiUtils.abiSelector(abiEntry), + topics: AbiUtils.topicsCount(abiEntry), + allocation: allocateEvent(abiEntry, contractId, referenceDeclarations, abiAllocations) }) - ) ); } //note: constructor context is ignored by this function; no need to pass it in export function getEventAllocations(contracts: Allocations.ContractAllocationInfo[], referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations): Allocations.EventAllocations { - return Object.assign({}, ...contracts.map( - ({abi, id}) => getEventAllocationsForContract(abi, id, referenceDeclarations, abiAllocations) - )); + let allocations: Allocations.EventAllocations = {}; + for(let {abi, id} of contracts) { + let contractKind = referenceDeclarations[id].contractKind; + let contractAllocations = getEventAllocationsForContract(abi, id, referenceDeclarations, abiAllocations); + for(let {selector, topics, allocation} of contractAllocations) { + if(allocations[selector] === undefined) { + allocations[selector] = {}; + } + if(allocations[selector][topics] === undefined) { + allocations[selector][topics] = { contract: {}, library: {} }; + } + allocations[selector][topics][contractKind][id] = allocation; + } + } + return allocations; } diff --git a/packages/truffle-codec/lib/decode/abi.ts b/packages/truffle-codec/lib/decode/abi.ts index 6026708b6ff..33a2dbcdc93 100644 --- a/packages/truffle-codec/lib/decode/abi.ts +++ b/packages/truffle-codec/lib/decode/abi.ts @@ -10,30 +10,38 @@ import { AbiMemberAllocation } from "../types/allocation"; import { abiSizeForType, isTypeDynamic } from "../allocate/abi"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; +import { StopDecodingError } from "../types/errors"; -export default function* decodeAbi(dataType: Types.Type, pointer: AbiPointer, info: EvmInfo, base: number = 0): IterableIterator { +//what is strict mode? +//in strict mode, we don't return errors, we *throw* them! +//it also turns on error checking for overlong arrays or strings + +export default function* decodeAbi(dataType: Types.Type, pointer: AbiPointer, info: EvmInfo, base: number = 0, strict: boolean = false): IterableIterator { if(Types.isReferenceType(dataType)) { let dynamic: boolean; try { dynamic = isTypeDynamic(dataType, info.allocations.abi); } catch(error) { //error: Values.DecodingError + if(strict) { + throw new StopDecodingError(); + } return Values.makeGenericErrorResult(dataType, error.error); } if(dynamic) { - return yield* decodeAbiReferenceByAddress(dataType, pointer, info, base); + return yield* decodeAbiReferenceByAddress(dataType, pointer, info, base, strict); } else { - return yield* decodeAbiReferenceStatic(dataType, pointer, info); + return yield* decodeAbiReferenceStatic(dataType, pointer, info, strict); } } else { debug("pointer %o", pointer); - return yield* decodeValue(dataType, pointer, info); + return yield* decodeValue(dataType, pointer, info, strict ? "strict" : "normal"); } } -export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, pointer: DataPointer, info: EvmInfo, base: number = 0): IterableIterator { +export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, pointer: DataPointer, info: EvmInfo, base: number = 0, strict: boolean = false): IterableIterator { const { allocations: { abi: allocations }, state } = info; debug("pointer %o", pointer); //this variable holds the location we should look to *next* @@ -46,6 +54,9 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin rawValue = yield* read(pointer, state); } catch(error) { //error: Values.DecodingError + if(strict) { + throw new StopDecodingError(); + } return Values.makeGenericErrorResult(dataType, error.error); } @@ -57,6 +68,9 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin dynamic = isTypeDynamic(dataType, allocations); } catch(error) { //error: Values.DecodingError + if(strict) { + throw new StopDecodingError(); + } return Values.makeGenericErrorResult(dataType, error.error); } if(!dynamic) { //this will only come up when called from stack.ts @@ -65,6 +79,9 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin size = abiSizeForType(dataType, allocations); } catch(error) { //error: Values.DecodingError + if(strict) { + throw new StopDecodingError(); + } return Values.makeGenericErrorResult(dataType, error.error); } let staticPointer = { @@ -72,7 +89,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin start: startPosition, length: size } - return yield* decodeAbiReferenceStatic(dataType, staticPointer, info); + return yield* decodeAbiReferenceStatic(dataType, staticPointer, info, strict); } let length: number; let rawLength: Uint8Array; @@ -89,9 +106,21 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin }, state)); } catch(error) { //error: Values.DecodingError + if(strict) { + throw new StopDecodingError(); + } return Values.makeGenericErrorResult(dataType, error.error); } - length = CodecUtils.Conversion.toBN(rawLength).toNumber(); + let lengthAsBN = CodecUtils.Conversion.toBN(rawLength); + if(strict && lengthAsBN.gtn(info.state.eventdata.length)) { + //HACK: yes, we always compare to eventdata, not the actual location! + //this is OK for now as strict mode is only used when decoding events. + //also, you may notice that the comparison is a bit crude; that's OK, + //this is just to prevent huge numbers from DOSing us, other errors will + //still be caught regardless + throw new StopDecodingError(); + } + length = lengthAsBN.toNumber(); let childPointer: AbiPointer = { location, @@ -99,7 +128,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin length } - return yield* decodeValue(dataType, childPointer, info); + return yield* decodeValue(dataType, childPointer, info, strict ? "strict" : "normal"); case "array": @@ -114,9 +143,21 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin }, state)); } catch(error) { //error: Values.DecodingError + if(strict) { + throw new StopDecodingError(); + } return Values.makeGenericErrorResult(dataType, error.error); } - length = CodecUtils.Conversion.toBN(rawLength).toNumber(); + let lengthAsBN = CodecUtils.Conversion.toBN(rawLength); + if(strict && lengthAsBN.gtn(info.state.eventdata.length)) { + //HACK: yes, we always compare to eventdata, not the actual location! + //this is OK for now as strict mode is only used when decoding events. + //also, you may notice that the comparison is a bit crude; that's OK, + //this is just to prevent huge numbers from DOSing us, other errors will + //still be caught regardless + throw new StopDecodingError(); + } + length = lengthAsBN.toNumber(); startPosition += CodecUtils.EVM.WORD_SIZE; //increment startPosition //to next word, as first word was used for length break; @@ -134,6 +175,9 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin baseSize = abiSizeForType(dataType.baseType, allocations); } catch(error) { //error: Values.DecodingError + if(strict) { + throw new StopDecodingError(); + } return Values.makeGenericErrorResult(dataType, error.error); } @@ -147,18 +191,18 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin start: startPosition + index * baseSize, length: baseSize }, - info, startPosition + info, startPosition, strict )) ); //pointer base is always start of list, never the length } return new Values.ArrayValue(dataType, decodedChildren); case "struct": - return yield* decodeAbiStructByPosition(dataType, location, startPosition, info); + return yield* decodeAbiStructByPosition(dataType, location, startPosition, info, strict); } } -export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer: AbiPointer, info: EvmInfo): IterableIterator { +export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer: AbiPointer, info: EvmInfo, strict: boolean = false): IterableIterator { debug("static"); debug("pointer %o", pointer); const location = pointer.location; @@ -173,6 +217,9 @@ export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer baseSize = abiSizeForType(dataType.baseType, info.allocations.abi); } catch(error) { //error: Values.DecodingError + if(strict) { + throw new StopDecodingError(); + } return Values.makeGenericErrorResult(dataType, error.error); } @@ -186,21 +233,21 @@ export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer start: pointer.start + index * baseSize, length: baseSize }, - info + info, 0, strict //the 0 is meaningless, just there as default )) - ); //static case so don't need base + ); } return new Values.ArrayValue(dataType, decodedChildren); case "struct": - return yield* decodeAbiStructByPosition(dataType, location, pointer.start, info); + return yield* decodeAbiStructByPosition(dataType, location, pointer.start, info, strict); } } type AbiLocation = "calldata" | "eventdata" | "abi"; //note that this function takes the start position as a *number*; it does not take a pointer -function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLocation, startPosition: number, info: EvmInfo): IterableIterator { +function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLocation, startPosition: number, info: EvmInfo, strict: boolean = false): IterableIterator { const { userDefinedTypes, allocations: { abi: allocations } } = info; const typeLocation = location === "eventdata" @@ -210,6 +257,9 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc const typeId = dataType.id; const structAllocation = allocations[typeId]; if(!structAllocation) { + if(strict) { + throw new StopDecodingError(); + } return new Values.StructErrorResult( dataType, new Values.UserDefinedTypeNotFoundError(dataType) @@ -229,6 +279,9 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc let memberName = memberAllocation.definition.name; let storedType = userDefinedTypes[typeId]; if(!storedType) { + if(strict) { + throw new StopDecodingError(); + } return new Values.StructErrorResult( dataType, new Values.UserDefinedTypeNotFoundError(dataType) @@ -239,7 +292,8 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc decodedMembers.push([ memberName, - (yield* decodeAbi(memberType, childPointer, info)) + (yield* decodeAbi(memberType, childPointer, info, 0, strict)) + //the 0 is meaningless, just there by default ]); } return new Values.StructValue(dataType, decodedMembers); diff --git a/packages/truffle-codec/lib/decode/event.ts b/packages/truffle-codec/lib/decode/event.ts index e855dd8815d..6cdabe4f467 100644 --- a/packages/truffle-codec/lib/decode/event.ts +++ b/packages/truffle-codec/lib/decode/event.ts @@ -7,8 +7,9 @@ import { Types, Values, Conversion as ConversionUtils } from "truffle-codec-util import { EventTopicPointer } from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; +import { StopDecodingError } from "../types/errors"; -export default function* decode(dataType: Types.Type, pointer: EventTopicPointer, info: EvmInfo): IterableIterator { +export default function* decodeTopic(dataType: Types.Type, pointer: EventTopicPointer, info: EvmInfo, strict: boolean = false): IterableIterator { if(Types.isReferenceType(dataType)) { //we cannot decode reference types "stored" in topics; we have to just return an error let bytes: Uint8Array; @@ -16,9 +17,13 @@ export default function* decode(dataType: Types.Type, pointer: EventTopicPointer bytes = yield* read(pointer, info.state); } catch(error) { //error: Values.DecodingError + if(strict) { + throw new StopDecodingError(); + } return Values.makeGenericErrorResult(dataType, error.error); } let raw: string = ConversionUtils.toHexString(bytes); + //NOTE: even in strict mode we want to just return this, not throw an error here return Values.makeGenericErrorResult( dataType, new Values.IndexedReferenceTypeError( @@ -28,5 +33,5 @@ export default function* decode(dataType: Types.Type, pointer: EventTopicPointer ); } //otherwise, dispatch to decodeValue - return yield* decodeValue(dataType, pointer, info); + return yield* decodeValue(dataType, pointer, info, strict ? "strict" : "normal"); } diff --git a/packages/truffle-codec/lib/decode/index.ts b/packages/truffle-codec/lib/decode/index.ts index 138c836ae0a..deddfdd73bd 100644 --- a/packages/truffle-codec/lib/decode/index.ts +++ b/packages/truffle-codec/lib/decode/index.ts @@ -15,7 +15,7 @@ import * as Pointer from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; -export default function* decode(dataType: Types.Type, pointer: Pointer.DataPointer, info: EvmInfo, base: number = 0): IterableIterator { +export default function* decode(dataType: Types.Type, pointer: Pointer.DataPointer, info: EvmInfo, base: number = 0, strict: boolean = false): IterableIterator { debug("type %O", dataType); debug("pointer %O", pointer); @@ -38,10 +38,10 @@ export default function* decode(dataType: Types.Type, pointer: Pointer.DataPoint case "calldata": case "eventdata": - return yield* decodeAbi(dataType, pointer, info, base); + return yield* decodeAbi(dataType, pointer, info, base, strict); case "eventtopic": - return yield* decodeTopic(dataType, pointer, info); + return yield* decodeTopic(dataType, pointer, info, strict); case "memory": //NOTE: this case should never actually occur, but I'm including it diff --git a/packages/truffle-codec/lib/decode/stack.ts b/packages/truffle-codec/lib/decode/stack.ts index 58f13cb4086..b478fa2e0ba 100644 --- a/packages/truffle-codec/lib/decode/stack.ts +++ b/packages/truffle-codec/lib/decode/stack.ts @@ -100,5 +100,5 @@ export function* decodeLiteral(dataType: Types.Type, pointer: StackLiteralPointe //however, note that because we're on the stack, we use the permissive padding //option so that errors won't result due to values with bad padding //(of numeric or bytesN type, anyway) - return yield* decodeValue(dataType, pointer, info, true); + return yield* decodeValue(dataType, pointer, info, "permissive"); } diff --git a/packages/truffle-codec/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts index 7a362053f71..d58e2b09ff0 100644 --- a/packages/truffle-codec/lib/decode/value.ts +++ b/packages/truffle-codec/lib/decode/value.ts @@ -8,11 +8,23 @@ import BN from "bn.js"; import { DataPointer } from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; +import { StopDecodingError } from "../types/errors"; -export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, info: EvmInfo, permissivePadding: boolean = false): IterableIterator { + +//EXPLANATION OF MODES: +//1. normal mode -- the default +//bad padding causes an error to be returned. +//2. permissive mode -- used for stack decoding +//no error on bad padding for certain types. other things may still cause errors to be returned. +//3. strict mode -- used for event decoding +//bad padding is an error, yes, but in this mode we don't return errors, we THROW them! +//(except for internal functions; strict mode doesn't affect those) +export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, info: EvmInfo, mode: "normal" | "permissive" | "strict" = "normal"): IterableIterator { //NOTE: this does not actually return a Uint8Aarray, but due to the use of yield* read, //we have to include it in the type :-/ const { state } = info; + const permissivePadding = mode === "permissive"; + const strict = mode === "strict"; let bytes: Uint8Array; try { @@ -20,6 +32,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, } catch(error) { //error: Values.DecodingError debug("segfault, pointer %o, state: %O", pointer, state); + if(strict) { + throw new StopDecodingError(); + } return Values.makeGenericErrorResult(dataType, error.error); } @@ -30,6 +45,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "bool": { if(!checkPaddingLeft(bytes, 1)) { + if(strict) { + throw new StopDecodingError(); + } return new Values.BoolErrorResult( dataType, new Values.BoolPaddingError(CodecUtils.Conversion.toHexString(bytes)) @@ -43,6 +61,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, return new Values.BoolValue(dataType, true); } else { + if(strict) { + throw new StopDecodingError(); + } return new Values.BoolErrorResult( dataType, new Values.BoolOutOfRangeError(numeric) @@ -53,6 +74,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "uint": //first, check padding (if needed) if(!permissivePadding && !checkPaddingLeft(bytes, dataType.bits/8)) { + if(strict) { + throw new StopDecodingError(); + } return new Values.UintErrorResult( dataType, new Values.UintPaddingError(CodecUtils.Conversion.toHexString(bytes)) @@ -64,6 +88,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "int": //first, check padding (if needed) if(!permissivePadding && !checkPaddingSigned(bytes, dataType.bits/8)) { + if(strict) { + throw new StopDecodingError(); + } return new Values.IntErrorResult( dataType, new Values.IntPaddingError(CodecUtils.Conversion.toHexString(bytes)) @@ -75,6 +102,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "address": if(!permissivePadding && !checkPaddingLeft(bytes, CodecUtils.EVM.ADDRESS_SIZE)) { + if(strict) { + throw new StopDecodingError(); + } return new Values.AddressErrorResult( dataType, new Values.AddressPaddingError(CodecUtils.Conversion.toHexString(bytes)) @@ -84,6 +114,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "contract": if(!permissivePadding && !checkPaddingLeft(bytes, CodecUtils.EVM.ADDRESS_SIZE)) { + if(strict) { + throw new StopDecodingError(); + } return new Values.ContractErrorResult( dataType, new Values.ContractPaddingError(CodecUtils.Conversion.toHexString(bytes)) @@ -97,6 +130,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, if(dataType.kind === "static") { //first, check padding (if needed) if(!permissivePadding && !checkPaddingRight(bytes, dataType.length)) { + if(strict) { + throw new StopDecodingError(); + } return new Values.BytesErrorResult( dataType, new Values.BytesPaddingError(CodecUtils.Conversion.toHexString(bytes)) @@ -120,6 +156,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, switch(dataType.visibility) { case "external": if(!checkPaddingRight(bytes, CodecUtils.EVM.ADDRESS_SIZE + CodecUtils.EVM.SELECTOR_SIZE)) { + if(strict) { + throw new StopDecodingError(); + } return new Values.FunctionExternalErrorResult( dataType, new Values.FunctionExternalNonStackPaddingError(CodecUtils.Conversion.toHexString(bytes)) @@ -132,6 +171,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, ); case "internal": if(!checkPaddingLeft(bytes, 2 * CodecUtils.EVM.PC_SIZE)) { + if(strict) { + throw new StopDecodingError(); + } return new Values.FunctionInternalErrorResult( dataType, new Values.FunctionInternalPaddingError(CodecUtils.Conversion.toHexString(bytes)) @@ -147,6 +189,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, const numeric = CodecUtils.Conversion.toBN(bytes); const fullType = Types.fullType(dataType, info.userDefinedTypes); if(!fullType.options) { + if(strict) { + throw new StopDecodingError(); + } return new Values.EnumErrorResult( fullType, new Values.EnumNotFoundDecodingError(fullType, numeric) @@ -155,6 +200,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, const numOptions = fullType.options.length; const numBytes = Math.ceil(Math.log2(numOptions) / 8); if(!checkPaddingLeft(bytes, numBytes)) { + if(strict) { + throw new StopDecodingError(); + } return new Values.EnumErrorResult( fullType, new Values.EnumPaddingError(fullType, CodecUtils.Conversion.toHexString(bytes)) @@ -165,6 +213,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, return new Values.EnumValue(fullType, numeric, name); } else { + if(strict) { + throw new StopDecodingError(); + } return new Values.EnumErrorResult( fullType, new Values.EnumOutOfRangeError(fullType, numeric) @@ -175,6 +226,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "fixed": { //skipping padding check as we don't support this anyway const hex = CodecUtils.Conversion.toHexString(bytes); + if(strict) { + throw new StopDecodingError(); + } return new Values.FixedErrorResult( dataType, new Values.FixedPointNotYetSupportedError(hex) @@ -183,6 +237,9 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "ufixed": { //skipping padding check as we don't support this anyway const hex = CodecUtils.Conversion.toHexString(bytes); + if(strict) { + throw new StopDecodingError(); + } return new Values.UfixedErrorResult( dataType, new Values.FixedPointNotYetSupportedError(hex) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index ee3c5137fb0..2034593487b 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -7,7 +7,8 @@ import * as Pointer from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; import { CalldataAllocation, EventAllocation, EventArgumentAllocation } from "../types/allocation"; -import { CalldataDecoding, EventDecoding } from "../types/wire"; +import { CalldataDecoding, EventDecoding, AbiArgument } from "../types/wire"; +import { encodeTupleAbi } from "../encode/abi"; import read from "../read"; import decode from "../decode"; @@ -54,20 +55,22 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator { - const value = decode( - Types.definitionToType(argumentAllocation.definition, compiler), - argumentAllocation.pointer, - info, - allocation.offset //note the use of the offset for decoding pointers! - ); - const name = argumentAllocation.definition.name; - return name === undefined + //you can't map with a generator, so we have to do this map manually + let decodedArguments: AbiArgument[] = []; + for(const argumentAllocation of allocation.arguments) { + const value = (yield* decode( + Types.definitionToType(argumentAllocation.definition, compiler), + argumentAllocation.pointer, + info, + allocation.offset //note the use of the offset for decoding pointers! + )); + const name = argumentAllocation.definition.name; + decodedArguments.push( + name === undefined ? { value } - : { name, value }; - } - ); + : { name, value } + ); + } if(isConstructor) { return { kind: "constructor", @@ -85,7 +88,7 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator { +export function* decodeEvent(info: EvmInfo): IterableIterator { const compiler = info.currentContext.compiler; const allocations = info.allocations.event; const rawSelector = read( @@ -95,37 +98,59 @@ export function* decodeEvent(info: EvmInfo): IterableIterator context.contractId === allocation.contractId - && !context.isConstructor - ); - let newInfo = { ...info, currentContext: context }; - let contractType = CodecUtils.Contexts.contextToType(context); - let decodedArguments = allocation.arguments.map( - (argumentAllocation: EventArgumentAllocation) => { - const value = decode( - Types.definitionToType(argumentAllocation.definition, compiler), - argumentAllocation.pointer, - newInfo, - 0 //offset is always 0 but let's be explicit + const topicsCount = info.state.eventtopics.length; + //yeah, it's not great to read directly from the state like this (bypassing read), but what are you gonna do? + const { contract: contractAllocations, library: libraryAllocations } = allocations[selector][topicsCount]; + //we only want one contract from the contractAllocations -- this one + const contractId = info.currentContext.contractId; + const contractAllocation = contractAllocations[contractId]; + const possibleAllocations: [string, EventAllocation][] = [[contractId.toString(), contractAllocation], ...Object.entries(libraryAllocations)]; + //should be number, but we have to temporarily pass through string to get compilation to work... + let decodings: EventDecoding[] = []; + for(const [id, allocation] of possibleAllocations) { + try { + const context = info.contexts[parseInt(id)]; + const contractType = CodecUtils.Contexts.contextToType(context); + const newInfo = { ...info, currentContext: context }; + //you can't map with a generator, so we have to do this map manually + let decodedArguments: AbiArgument[] = []; + for(const argumentAllocation of allocation.arguments) { + const value = (yield* decode( + Types.definitionToType(argumentAllocation.definition, compiler), + argumentAllocation.pointer, + newInfo, + 0, //offset is always 0 for events + true //turns on STRICT MODE to cause more errors to be thrown + )); + const name = argumentAllocation.definition.name; + decodedArguments.push( + name === undefined + ? { value } + : { name, value } + ); + } + //OK, so, having decoded the result, the question is: does it reencode to the original? + //first, we have to filter out the indexed arguments, and also get rid of the name information + const nonIndexedValues = decodedArguments.filter( + (_, index) => allocation.arguments[index].pointer.location !== "eventtopic" + ).map( + ({value}) => value ); - const name = argumentAllocation.definition.name; - return name === undefined - ? { value } - : { name, value }; + //now, we can encode! + const reEncodedData = encodeTupleAbi(nonIndexedValues, info.allocations.abi); + //are they equal? + const encodedData = info.state.eventdata; //again, not great to read this directly, but oh well + if(CodecUtils.EVM.equalData(encodedData, reEncodedData)) { + decodings.push({ + class: contractType, + name: allocation.definition.name, + arguments: decodedArguments + }); + } + //otherwise, just move on } - ); - return { - kind: "event", - class: contractType, - name: allocation.definition.name, - arguments: decodedArguments - }; + catch(error) { + continue; //if an error occurred, this isn't a valid decoding! + } + } } diff --git a/packages/truffle-codec/lib/types/allocation.ts b/packages/truffle-codec/lib/types/allocation.ts index 20f9cbac96f..c49c62754e8 100644 --- a/packages/truffle-codec/lib/types/allocation.ts +++ b/packages/truffle-codec/lib/types/allocation.ts @@ -9,6 +9,8 @@ export interface ContractAllocationInfo { constructorContext?: Contexts.DecoderContext; } +//let's start with storage allocations + //holds a collection of storage allocations for structs and contracts, indexed //by the ID of the struct or contract export interface StorageAllocations { @@ -54,7 +56,7 @@ export interface AbiMemberAllocation { //memory works the same as abi except we don't bother keeping track of size //(it's always 1 word) or dynamicity (meaningless in memory) -//Also, we allow pointers to be null to indicate that they're omitted +//Also, we allow pointers to be null to indicate that that member is omitted export interface MemoryAllocations { [id: number]: MemoryAllocation @@ -104,11 +106,26 @@ export interface CalldataArgumentAllocation { //finally we have events. these work like calldata, except that there's no //need for an offset, the ultimate pointer can be either an event data pointer -//or an event topic pointer, and, they're given *only* by selector -- not by -//contract ID! Instead the contract ID is included in the allocation +//or an event topic pointer, and, they're given *first* by selector, *then* +//by number of topics, *then* by contract ID (the latter being split into +//contracts and libraries) export interface EventAllocations { - [selector: string]: EventAllocation + [selector: string]: EventSelectorAllocation; +} + +export interface EventSelectorAllocation { + [topics: number]: EventAllocationsNarrow; +} + +export interface EventAllocationsNarrow { + [contractKind: string]: EventContractAllocation; + //yes, this is a stupid way of doing this, but it's the easiest way to + //get things to compile +} + +export interface EventContractAllocation { + [contractId: number]: EventAllocation; } export interface EventAllocation { @@ -121,3 +138,10 @@ export interface EventArgumentAllocation { definition: AstDefinition; pointer: Pointer.EventDataPointer | Pointer.EventTopicPointer; } + +//NOTE: not for outside use! just produced temporarily by the allocator! +export interface EventAllocationTemporary { + selector: string; + topics: number; + allocation: EventAllocation; +} diff --git a/packages/truffle-codec/lib/types/errors.ts b/packages/truffle-codec/lib/types/errors.ts index 4652f1cdc3d..44c1d3f710e 100644 --- a/packages/truffle-codec/lib/types/errors.ts +++ b/packages/truffle-codec/lib/types/errors.ts @@ -1,3 +1,5 @@ +import { Values } from "truffle-codec-utils"; + export class UnknownBaseContractIdError extends Error { public derivedId: number; public derivedName: string; @@ -25,3 +27,13 @@ export class UnknownUserDefinedTypeError extends Error { this.typeString = typeString; } } + +//used to stop decoding; apologies for the lack of details in this one, +//but this one is actually meant to be used for control flow rather than +//display, so I'm hoping that's OK +export class StopDecodingError extends Error { + constructor() { + const message = `Stopping decoding!`; + super(message); + } +} diff --git a/packages/truffle-codec/lib/types/wire.ts b/packages/truffle-codec/lib/types/wire.ts index e1ed25495c4..98eac68d7e2 100644 --- a/packages/truffle-codec/lib/types/wire.ts +++ b/packages/truffle-codec/lib/types/wire.ts @@ -8,10 +8,9 @@ export interface CalldataDecoding { } export interface EventDecoding { - kind: "event" | "unknown"; - class?: CodecUtils.Types.ContractType; //included only if event - name?: string; //included only if event - arguments?: AbiArgument[]; //included only if event + name: string; + class: CodecUtils.Types.ContractType; + arguments: AbiArgument[]; } export interface AbiArgument { diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index fe9da0d102b..2c55a485c6e 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -421,11 +421,11 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { result = decoder.next(response); } //at this point, result.value holds the final value - const decoding = result.value; + const decodings = result.value; return { ...log, - decoding + decodings }; } @@ -444,9 +444,15 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { let events = await this.decodeLogs(logs); if(name !== null) { - events = events.filter(event => - event.decoding.kind === "event" - && event.decoding.name === name + events = events.map( + event => ({ + ...event, + decodings: event.decodings.filter( + decoding => decoding.name === name + ) + }) + ).filter( + event => event.decodings.length > 0 ); } diff --git a/packages/truffle-decoder/lib/types.ts b/packages/truffle-decoder/lib/types.ts index be6766fb22d..ed9d7fc3633 100644 --- a/packages/truffle-decoder/lib/types.ts +++ b/packages/truffle-decoder/lib/types.ts @@ -19,7 +19,7 @@ export interface DecodedTransaction extends Transaction { } export interface DecodedEvent extends Log { - decoding: EventDecoding; + decodings: EventDecoding[]; } export interface ContractMapping { diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 12abe26884f..dc2c57933d0 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -205,11 +205,11 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { result = decoder.next(response); } //at this point, result.value holds the final value - const decoding = result.value; + const decodings = result.value; return { ...log, - decoding + decodings }; } @@ -226,9 +226,15 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { let events = await this.decodeLogs(logs); if(name !== null) { - events = events.filter(event => - event.decoding.kind === "event" - && event.decoding.name === name + events = events.map( + event => ({ + ...event, + decodings: event.decodings.filter( + decoding => decoding.name === name + ) + }) + ).filter( + event => event.decodings.length > 0 ); } From 819b6157c89ea8a1d7f08525108052eb8f50edfe Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 20 Jun 2019 17:17:40 -0400 Subject: [PATCH 26/89] Add error handling --- packages/truffle-codec-utils/src/abi.ts | 52 ++++++++++-- .../truffle-codec-utils/src/definition.ts | 32 -------- packages/truffle-codec-utils/src/errors.ts | 11 +++ packages/truffle-codec-utils/src/index.ts | 1 + packages/truffle-codec/lib/allocate/abi.ts | 79 +++++++++++++------ .../truffle-codec/lib/allocate/storage.ts | 3 +- .../truffle-codec/lib/interface/decoding.ts | 22 ++++-- packages/truffle-codec/lib/types/errors.ts | 32 +++++--- 8 files changed, 155 insertions(+), 77 deletions(-) create mode 100644 packages/truffle-codec-utils/src/errors.ts diff --git a/packages/truffle-codec-utils/src/abi.ts b/packages/truffle-codec-utils/src/abi.ts index c4c9727b946..67807f1092a 100644 --- a/packages/truffle-codec-utils/src/abi.ts +++ b/packages/truffle-codec-utils/src/abi.ts @@ -5,6 +5,8 @@ import { Abi as SchemaAbi } from "truffle-contract-schema/spec"; import { EVM as EVMUtils } from "./evm"; import { AstDefinition, AstReferences } from "./ast"; import { Definition as DefinitionUtils } from "./definition"; +import { Values } from "./types/values"; +import { UnknownUserDefinedTypeError } from "./errors"; import Web3 from "web3"; //NOTE: SchemaAbi is kind of loose and a pain to use. @@ -167,13 +169,17 @@ export namespace AbiUtils { return true; } - //TODO: add error-handling function matchesAbiType(abiParameter: AbiParameter, nodeParameter: AstDefinition, referenceDeclarations: AstReferences): boolean { - if(DefinitionUtils.toAbiType(nodeParameter, referenceDeclarations) !== abiParameter.type) { + if(toAbiType(nodeParameter, referenceDeclarations) !== abiParameter.type) { return false; } if(abiParameter.type.startsWith("tuple")) { - let referenceDeclaration = referenceDeclarations[DefinitionUtils.typeId(nodeParameter)]; + let referenceId = DefinitionUtils.typeId(nodeParameter); + let referenceDeclaration = referenceDeclarations[referenceId]; + if(referenceDeclaration === undefined) { + let typeString = DefinitionUtils.typeString(nodeParameter); + throw new UnknownUserDefinedTypeError(referenceId, typeString); + } return matchesAbiParameters(abiParameter.components, referenceDeclaration.members, referenceDeclarations); } else { @@ -181,12 +187,48 @@ export namespace AbiUtils { } } + //note: this is only meant for types that can go in the ABI + //it returns how that type is notated in the ABI -- just the string, + //to be clear, not components of tuples + function toAbiType(definition: AstDefinition, referenceDeclarations: AstReferences): string { + let basicType = DefinitionUtils.typeClassLongForm(definition); //get that whole first segment! + switch(basicType) { + case "contract": + return "address"; + case "struct": + return "tuple"; //the more detailed checking will be handled elsewhere + case "enum": + let referenceId = DefinitionUtils.typeId(definition); + let referenceDeclaration = referenceDeclarations[referenceId]; + if(referenceDeclaration === undefined) { + let typeString = DefinitionUtils.typeString(definition); + throw new UnknownUserDefinedTypeError(referenceId, typeString); + } + let numOptions = referenceDeclaration.members.length; + let bits = 8 * Math.ceil(Math.log2(numOptions) / 8); + return `uint${bits}`; + case "array": + let baseType = toAbiType(DefinitionUtils.baseDefinition(definition), referenceDeclarations); + return DefinitionUtils.isDynamicArray(definition) + ? `${baseType}[]` + : `${baseType}[${DefinitionUtils.staticLength(definition)}]`; + default: + return basicType; + //note that: int/uint/fixed/ufixed/bytes will have their size and such left on; + //address will have "payable" left off; + //external functions will be reduced to "function" (and internal functions shouldn't + //be passed in!) + //(mappings shouldn't be passed in either obviously) + } + } + + //NOTE: this function returns the written out SIGNATURE, not the SELECTOR - function abiSignature(abiEntry: FunctionAbiEntry | EventAbiEntry): string { + export function abiSignature(abiEntry: FunctionAbiEntry | EventAbiEntry): string { return abiEntry.name + abiTupleSignature(abiEntry.inputs); } - function abiTupleSignature(parameters: AbiParameter[]): string { + export function abiTupleSignature(parameters: AbiParameter[]): string { let components = parameters.map(abiTypeSignature); return "(" + components.join(",") + ")"; } diff --git a/packages/truffle-codec-utils/src/definition.ts b/packages/truffle-codec-utils/src/definition.ts index 9c3ec98a3ea..09b7f7c7dee 100644 --- a/packages/truffle-codec-utils/src/definition.ts +++ b/packages/truffle-codec-utils/src/definition.ts @@ -349,38 +349,6 @@ export namespace Definition { return mutability(fallback) === "payable"; } - //note: this is only meant for types that can go in the ABI - //it returns how that type is notated in the ABI -- just the string, - //to be clear, not components of tuples - //TODO add error handling - export function toAbiType(definition: AstDefinition, referenceDeclarations: AstReferences): string { - let basicType = typeClassLongForm(definition); //get that whole first segment! - switch(basicType) { - case "contract": - return "address"; - case "struct": - return "tuple"; //the more detailed checking will be handled elsewhere - case "enum": - let id = typeId(definition); - let referenceDeclaration = referenceDeclarations[id]; - let numOptions = referenceDeclaration.members.length; - let bits = 8 * Math.ceil(Math.log2(numOptions) / 8); - return `uint${bits}`; - case "array": - let baseType = toAbiType(baseDefinition(definition), referenceDeclarations); - return isDynamicArray(definition) - ? `${baseType}[]` - : `${baseType}[${staticLength(definition)}]`; - default: - return basicType; - //note that: int/uint/fixed/ufixed/bytes will have their size and such left on; - //address will have "payable" left off; - //external functions will be reduced to "function" (and internal functions shouldn't - //be passed in!) - //(mappings shouldn't be passed in either obviously) - } - } - //spoofed definitions we'll need //we'll give them id -1 to indicate that they're spoofed diff --git a/packages/truffle-codec-utils/src/errors.ts b/packages/truffle-codec-utils/src/errors.ts new file mode 100644 index 00000000000..96015f3ec8b --- /dev/null +++ b/packages/truffle-codec-utils/src/errors.ts @@ -0,0 +1,11 @@ +export class UnknownUserDefinedTypeError extends Error { + public typeString: string; + public id: number; + constructor(id: number, typeString: string) { + const message = `Cannot locate definition for ${typeString}$ (ID ${id})`; + super(message); + this.name = "UnknownUserDefinedTypeError"; + this.id = id; + this.typeString = typeString; + } +} diff --git a/packages/truffle-codec-utils/src/index.ts b/packages/truffle-codec-utils/src/index.ts index cd0182bbf21..81663fbd7a8 100644 --- a/packages/truffle-codec-utils/src/index.ts +++ b/packages/truffle-codec-utils/src/index.ts @@ -5,5 +5,6 @@ export * from "./ast"; export * from "./contexts"; export * from "./abi"; export * from "./compiler"; +export * from "./errors"; export * from "./types/types"; export * from "./types/values"; diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index e040d827678..5979d2bd61c 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -5,6 +5,8 @@ import * as Pointer from "../types/pointer"; import * as Allocations from "../types/allocation"; import { AstDefinition, AstReferences, AbiUtils } from "truffle-codec-utils"; import * as CodecUtils from "truffle-codec-utils"; +import { UnknownUserDefinedTypeError } from "truffle-codec-utils"; +import { UnknownBaseContractIdError, NoDefinitionFoundForABIEntryError } from "../types/errors"; import partition from "lodash.partition"; export function getAbiAllocations(referenceDeclarations: AstReferences): Allocations.AbiAllocations { @@ -86,7 +88,6 @@ export function abiSize(definition: AstDefinition, referenceDeclarations?: AstRe //second return value is whether the type is dynamic //both will be undefined if type is a mapping or internal function //third return value is resulting allocations, INCLUDING the ones passed in -//TODO: add error handling function abiSizeAndAllocate(definition: AstDefinition, referenceDeclarations?: AstReferences, existingAllocations?: Allocations.AbiAllocations): [number | undefined, boolean | undefined, Allocations.AbiAllocations] { switch (CodecUtils.Definition.typeClass(definition)) { case "bool": @@ -141,6 +142,10 @@ function abiSizeAndAllocate(definition: AstDefinition, referenceDeclarations?: A if(allocation === undefined) { //if we don't find an allocation, we'll have to do the allocation ourselves const referenceDeclaration: AstDefinition = referenceDeclarations[referenceId]; + if(referenceDeclaration === undefined) { + let typeString = CodecUtils.Definition.typeString(definition); + throw new UnknownUserDefinedTypeError(referenceId, typeString); + } allocations = allocateStruct(referenceDeclaration, referenceDeclarations, existingAllocations); allocation = allocations[referenceId]; } @@ -207,8 +212,6 @@ export function isTypeDynamic(dataType: CodecUtils.Types.Type, allocations?: All //allocates an external call //NOTE: returns just a single allocation; assumes primary allocation is already complete! -//TODO add error-handling -//TODO: check accesses to abi & node members function allocateCalldata( abiEntry: AbiUtils.FunctionAbiEntry | AbiUtils.ConstructorAbiEntry, contractId: number, @@ -216,7 +219,8 @@ function allocateCalldata( abiAllocations: Allocations.AbiAllocations, constructorContext?: CodecUtils.Contexts.DecoderContext ): Allocations.CalldataAllocation { - const linearizedBaseContracts = referenceDeclarations[contractId].linearizedBaseContracts; + const contractNode = referenceDeclarations[contractId]; + const linearizedBaseContracts = contractNode.linearizedBaseContracts; //first: determine the corresponding function node //(simultaneously: determine the offset) let node: AstDefinition; @@ -226,26 +230,41 @@ function allocateCalldata( let rawLength = constructorContext.binary.length; offset = (rawLength - 2)/2; //number of bytes in 0x-prefixed bytestring //for a constructor, we only want to search the particular contract, which - let contractNode = referenceDeclarations[contractId]; node = contractNode.nodes.find( functionNode => AbiUtils.matchesAbi( abiEntry, functionNode, referenceDeclarations ) ); - //TODO: handle case if node undefined + if(node === undefined) { + //if we can't find it, just throw + throw new NoDefinitionFoundForABIEntryError(abiEntry, [contractId]); + } break; case "function": offset = CodecUtils.EVM.SELECTOR_SIZE; //search through base contracts, from most derived (right) to most base (left) node = linearizedBaseContracts.reduceRight( - (foundNode, baseContractId) => foundNode || referenceDeclarations[baseContractId].nodes.find( - functionNode => AbiUtils.matchesAbi( - abiEntry, functionNode, referenceDeclarations - ) - ), - undefined + (foundNode, baseContractId) => { + if(foundNode) { + return foundNode //once we've found something, we don't need to keep looking + }; + let baseContractNode = referenceDeclarations[baseContractId]; + if(baseContractNode === undefined) { + throw new UnknownBaseContractIdError(contractNode.id, contractNode.name, contractNode.contractKind, baseContractId); + } + return baseContractNode.nodes.find( //may be undefined! that's OK! + functionNode => AbiUtils.matchesAbi( + abiEntry, functionNode, referenceDeclarations + ) + ); + }, + undefined //start with no node found ); - //TODO: handle case if node undefined + if(node === undefined) { + //if we can't find it, just throw + //reverse the list (cloning first with slice) so that they're actually in the order searched + throw new NoDefinitionFoundForABIEntryError(abiEntry, linearizedBaseContracts.slice().reverse()); + } break; } //now: perform the allocation! @@ -274,25 +293,39 @@ function allocateCalldata( //allocates an event //NOTE: returns just a single allocation; assumes primary allocation is already complete! -//TODO add error-handling -//TODO: check accesses to abi & node members function allocateEvent( abiEntry: AbiUtils.EventAbiEntry, contractId: number, referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations ): Allocations.EventAllocation { - const linearizedBaseContracts = referenceDeclarations[contractId].linearizedBaseContracts; + const contractNode = referenceDeclarations[contractId]; + const linearizedBaseContracts = contractNode.linearizedBaseContracts; //first: determine the corresponding event node //search through base contracts, from most derived (right) to most base (left) - const node: AstDefinition = linearizedBaseContracts.reduceRight( - (foundNode, baseContractId) => foundNode || referenceDeclarations[baseContractId].nodes.find( - eventNode => AbiUtils.matchesAbi( - abiEntry, eventNode, referenceDeclarations - ) - ), - undefined + let node: AstDefinition; + node = linearizedBaseContracts.reduceRight( + (foundNode, baseContractId) => { + if(foundNode) { + return foundNode //once we've found something, we don't need to keep looking + }; + let baseContractNode = referenceDeclarations[baseContractId]; + if(baseContractNode === undefined) { + throw new UnknownBaseContractIdError(contractNode.id, contractNode.name, contractNode.contractKind, baseContractId); + } + return baseContractNode.nodes.find( //may be undefined! that's OK! + functionNode => AbiUtils.matchesAbi( + abiEntry, functionNode, referenceDeclarations + ) + ); + }, + undefined //start with no node found ); + if(node === undefined) { + //if we can't find it, just throw + //reverse the list (cloning first with slice) so that they're actually in the order searched + throw new NoDefinitionFoundForABIEntryError(abiEntry, linearizedBaseContracts.slice().reverse()); + } //now: split the list of parameters into indexed and non-indexed //but first attach positions so we can reconstruct the list later const rawParameters = node.parameters.parameters; diff --git a/packages/truffle-codec/lib/allocate/storage.ts b/packages/truffle-codec/lib/allocate/storage.ts index 457a79c9ecc..99bc4296798 100644 --- a/packages/truffle-codec/lib/allocate/storage.ts +++ b/packages/truffle-codec/lib/allocate/storage.ts @@ -4,7 +4,8 @@ const debug = debugModule("codec:allocate:storage"); import { StoragePointer } from "../types/pointer"; import { StorageAllocations, StorageAllocation, StorageMemberAllocation } from "../types/allocation"; import { StorageLength, isWordsLength, Range } from "../types/storage"; -import { UnknownBaseContractIdError, UnknownUserDefinedTypeError } from "../types/errors"; +import { UnknownBaseContractIdError } from "../types/errors"; +import { UnknownUserDefinedTypeError } from "truffle-codec-utils"; import { AstDefinition, AstReferences } from "truffle-codec-utils"; import { readDefinition } from "../read/constant" import * as CodecUtils from "truffle-codec-utils"; diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 2034593487b..300ff90cdb8 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -38,7 +38,7 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator read( { location: "calldata", start: 0, @@ -91,12 +91,19 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator { const compiler = info.currentContext.compiler; const allocations = info.allocations.event; - const rawSelector = read( - { location: "eventtopic", - topic: 0 - }, - info.state - ).next().value; //no requests should occur, we can just get the first value + let rawSelector: Uint8Array; + try { + rawSelector = read( + { location: "eventtopic", + topic: 0 + }, + info.state + ).next().value; //no requests should occur, we can just get the first value + } + catch(error) { + //if we can't read the selector, return an empty set of decodings + return []; + } const selector = CodecUtils.Conversion.toHexString(rawSelector); const topicsCount = info.state.eventtopics.length; //yeah, it's not great to read directly from the state like this (bypassing read), but what are you gonna do? @@ -153,4 +160,5 @@ export function* decodeEvent(info: EvmInfo): IterableIterator Date: Thu, 20 Jun 2019 17:38:09 -0400 Subject: [PATCH 27/89] Further fixes to package.json --- packages/truffle-codec-utils/package.json | 2 +- packages/truffle-decoder/package.json | 6 ++--- yarn.lock | 27 ----------------------- 3 files changed, 3 insertions(+), 32 deletions(-) diff --git a/packages/truffle-codec-utils/package.json b/packages/truffle-codec-utils/package.json index f51f674a237..3bf1e1e0fc5 100644 --- a/packages/truffle-codec-utils/package.json +++ b/packages/truffle-codec-utils/package.json @@ -7,7 +7,6 @@ "lodash.clonedeep": "^4.5.0", "lodash.escaperegexp": "^4.1.2", "semver": "^6.1.1", - "truffle-contract-schema": "^3.0.11", "web3": "1.0.0-beta.37" }, "main": "dist/index.js", @@ -34,6 +33,7 @@ "@types/lodash.escaperegexp": "^4.1.6", "@types/semver": "^6.0.0", "@types/web3": "^1.0.19", + "truffle-contract-schema": "^3.0.11", "json-schema-to-typescript": "^6.1.3", "typescript": "^3.1.3" }, diff --git a/packages/truffle-decoder/package.json b/packages/truffle-decoder/package.json index 6a5d9ecd0d4..3dff9d9551d 100644 --- a/packages/truffle-decoder/package.json +++ b/packages/truffle-decoder/package.json @@ -28,13 +28,10 @@ }, "homepage": "https://github.com/trufflesuite/truffle#readme", "dependencies": { - "abi-decoder": "^1.2.0", "async-eventemitter": "^0.2.4", "bn.js": "^4.11.8", "debug": "^4.1.1", "lodash.isequal": "^4.5.0", - "truffle-contract-schema": "^3.0.9", - "truffle-decode-utils": "^1.0.12", "truffle-codec": "^3.0.3", "truffle-decode-utils": "^1.0.14", "web3": "1.0.0-beta.37" @@ -44,8 +41,9 @@ "@types/debug": "^4.1.4", "@types/lodash.isequal": "^4.5.5", "@types/web3": "1.0.18", - "typescript": "^3.5.1", "json-schema-to-typescript": "^6.1.3", + "truffle-contract-schema": "^3.0.9", + "typescript": "^3.5.1", "web3-core": "1.0.0-beta.37" } } diff --git a/yarn.lock b/yarn.lock index 93c7c0c1a0d..fb391003a49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1286,13 +1286,6 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -abi-decoder@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/abi-decoder/-/abi-decoder-1.2.0.tgz#c42882dbb91b444805f0cd203a87a5cc3c22f4a8" - integrity sha512-y2OKSEW4gf2838Eavc56vQY9V46zaXkf3Jl1WpTfUBbzAVrXSr4JRZAAWv55Tv9s5WNz1rVgBgz5d2aJIL1QCg== - dependencies: - web3 "^0.18.4" - abstract-leveldown@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-3.0.0.tgz#5cb89f958a44f526779d740d1440e743e0c30a57" @@ -2593,10 +2586,6 @@ bignumber.js@^7.2.1: version "2.0.7" resolved "git+https://github.com/debris/bignumber.js#c7a38de919ed75e6fb6ba38051986e294b328df9" -"bignumber.js@git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2": - version "2.0.7" - resolved "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" - "bignumber.js@git+https://github.com/frozeman/bignumber.js-nolookahead.git": version "2.0.7" resolved "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934" @@ -15115,17 +15104,6 @@ web3@^0.16.0: utf8 "^2.1.1" xmlhttprequest "*" -web3@^0.18.4: - version "0.18.4" - resolved "https://registry.yarnpkg.com/web3/-/web3-0.18.4.tgz#81ec1784145491f2eaa8955b31c06049e07c5e7d" - integrity sha1-gewXhBRUkfLqqJVbMcBgSeB8Xn0= - dependencies: - bignumber.js "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" - crypto-js "^3.1.4" - utf8 "^2.1.1" - xhr2 "*" - xmlhttprequest "*" - web3@^0.20.1: version "0.20.7" resolved "https://registry.yarnpkg.com/web3/-/web3-0.20.7.tgz#1605e6d81399ed6f85a471a4f3da0c8be57df2f7" @@ -15602,11 +15580,6 @@ xhr2-cookies@1.1.0, xhr2-cookies@^1.1.0: dependencies: cookiejar "^2.1.1" -xhr2@*: - version "0.2.0" - resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.2.0.tgz#eddeff782f3b7551061b8d75645085269396e521" - integrity sha512-BDtiD0i2iKPK/S8OAZfpk6tyzEDnKKSjxWHcMBVmh+LuqJ8A32qXTyOx+TVOg2dKvq6zGBq2sgKPkEeRs1qTRA== - xhr@^2.0.4, xhr@^2.2.0, xhr@^2.3.3: version "2.5.0" resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.5.0.tgz#bed8d1676d5ca36108667692b74b316c496e49dd" From ae55bb64389cc8e716e27f43c6fcc6641196bbbe Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 20 Jun 2019 20:53:58 -0400 Subject: [PATCH 28/89] Fix uses in debugger (or tests) of outdated pointer and allocation formats --- packages/truffle-codec/lib/decode/constant.ts | 1 + packages/truffle-debugger/lib/data/sagas/index.js | 12 +++++++++++- packages/truffle-debugger/test/data/codex.js | 2 ++ packages/truffle-debugger/test/data/more-decoding.js | 8 ++++---- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/truffle-codec/lib/decode/constant.ts b/packages/truffle-codec/lib/decode/constant.ts index 185a8ea837e..fd547a27346 100644 --- a/packages/truffle-codec/lib/decode/constant.ts +++ b/packages/truffle-codec/lib/decode/constant.ts @@ -38,5 +38,6 @@ export default function* decodeConstant(dataType: Types.Type, pointer: ConstantD } //otherwise, as mentioned, just dispatch to decodeValue + debug("not a static bytes"); return yield* decodeValue(dataType, pointer, info); } diff --git a/packages/truffle-debugger/lib/data/sagas/index.js b/packages/truffle-debugger/lib/data/sagas/index.js index f38a772e78a..277682ec57d 100644 --- a/packages/truffle-debugger/lib/data/sagas/index.js +++ b/packages/truffle-debugger/lib/data/sagas/index.js @@ -79,8 +79,10 @@ export function* decode(definition, ref, forceNonPayable = false) { internalFunctionsTable }); + debug("beginning decoding"); let result = decoder.next(); while (!result.done) { + debug("request received"); let request = result.value; let response; switch (request.type) { @@ -112,9 +114,11 @@ export function* decode(definition, ref, forceNonPayable = false) { default: debug("unrecognized request type!"); } + debug("sending response"); result = decoder.next(response); } //at this point, result.value holds the final value + debug("done decoding"); return result.value; } @@ -381,10 +385,12 @@ function* variablesAndMappingsSaga() { indexValue = yield* decode( keyDefinition, { + location: "definition", definition: indexDefinition }, true ); + debug("simple literal decoded: %O", indexValue); } else if (indexReference) { //if a prior assignment is found let splicedDefinition; @@ -403,6 +409,8 @@ function* variablesAndMappingsSaga() { splicedDefinition = keyDefinition; } debug("about to decode"); + debug("splicedDefinition: %o", splicedDefinition); + debug("indexReference: %o", indexReference); indexValue = yield* decode(splicedDefinition, indexReference, true); } else if ( indexDefinition.referencedDeclaration && @@ -429,6 +437,7 @@ function* variablesAndMappingsSaga() { indexValue = yield* decode( keyDefinition, { + location: "definition", definition: indexConstantDeclaration.value }, true @@ -465,6 +474,7 @@ function* variablesAndMappingsSaga() { indexValue = null; } //now, as mentioned, retry in the typeConversion case + debug("retrying decoding"); } //end subsection: key decoding @@ -642,7 +652,7 @@ function literalAssignments(node, stack, currentDepth) { let assignment = makeAssignment( { astId: node.id, stackframe: currentDepth }, - { literal } + { location: "stackliteral", literal } ); return { [assignment.id]: assignment }; diff --git a/packages/truffle-debugger/test/data/codex.js b/packages/truffle-debugger/test/data/codex.js index 33e0528318d..533e3e7e270 100644 --- a/packages/truffle-debugger/test/data/codex.js +++ b/packages/truffle-debugger/test/data/codex.js @@ -122,7 +122,9 @@ describe("Codex", function() { let session = bugger.connect(); + debug("starting stepping"); await session.continueUntilBreakpoint(); //run till end + debug("made it to end of transaction"); const surface = (await session.variable("surface")).nativize(); diff --git a/packages/truffle-debugger/test/data/more-decoding.js b/packages/truffle-debugger/test/data/more-decoding.js index 6aa163dc025..3afd74cfc5f 100644 --- a/packages/truffle-debugger/test/data/more-decoding.js +++ b/packages/truffle-debugger/test/data/more-decoding.js @@ -421,10 +421,10 @@ describe("Further Decoding", function() { //get offsets of top-level variables for this contract //converting to numbers for convenience const startingOffsets = Object.values( - Object.values(session.view(data.info.allocations.storage)).filter( - ({ definition }) => definition.name === "ComplexMappingTest" - )[0].members - ).map(({ pointer }) => pointer.storage.from.slot.offset); + session.view(data.info.allocations.storage) + ) + .find(({ definition }) => definition.name === "ComplexMappingTest") + .members.map(({ pointer }) => pointer.range.from.slot.offset); const mappingKeys = session.view(data.views.mappingKeys); for (let slot of mappingKeys) { From cb354279971722fe2e2e3f371b644f55198d62b2 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Fri, 21 Jun 2019 01:24:48 -0400 Subject: [PATCH 29/89] Fix various other errors --- packages/truffle-codec-utils/src/contexts.ts | 1 + packages/truffle-codec/lib/allocate/abi.ts | 12 +++++----- packages/truffle-codec/lib/interface/index.ts | 2 +- packages/truffle-codec/lib/types/evm.ts | 16 ++++++++------ packages/truffle-decoder/lib/contract.ts | 22 +++++++++++-------- packages/truffle-decoder/lib/wire.ts | 10 +++------ 6 files changed, 33 insertions(+), 30 deletions(-) diff --git a/packages/truffle-codec-utils/src/contexts.ts b/packages/truffle-codec-utils/src/contexts.ts index 3f6f44fe417..d0c56498a1a 100644 --- a/packages/truffle-codec-utils/src/contexts.ts +++ b/packages/truffle-codec-utils/src/contexts.ts @@ -119,6 +119,7 @@ export namespace Contexts { let newContexts: Contexts = {...contexts}; debug("contexts cloned"); + debug("cloned contexts: %O", newContexts); //next, we get all the library names and sort them descending by length. //We're going to want to go in descending order of length so that we diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index 5979d2bd61c..2566c7a44d2 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -329,7 +329,7 @@ function allocateEvent( //now: split the list of parameters into indexed and non-indexed //but first attach positions so we can reconstruct the list later const rawParameters = node.parameters.parameters; - const [indexed, nonIndexed] = rawParameters.partition((parameter: AstDefinition) => parameter.indexed); + const [indexed, nonIndexed] = partition(rawParameters, (parameter: AstDefinition) => parameter.indexed); //now: perform the allocation for the non-indexed parameters! const abiAllocation = allocateMembers(node, nonIndexed, referenceDeclarations, abiAllocations)[node.id]; //now: transform it appropriately @@ -376,7 +376,11 @@ function getCalldataAllocationsForContract( referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations ): Allocations.CalldataContractAllocation { - let allocations: Allocations.CalldataContractAllocation; + let allocations: Allocations.CalldataContractAllocation = { + constructorAllocation: defaultConstructorAllocation(constructorContext), //will be overridden if abi has a constructor + //(if it doesn't then it will remain as default) + functionAllocations: {} + } for(let abiEntry of abi) { if(abiEntry.type === "constructor") { allocations.constructorAllocation = allocateCalldata( @@ -399,10 +403,6 @@ function getCalldataAllocationsForContract( } //skip over fallback and event } - //now: did we allocate a constructor? if not, allocate a default one - if(allocations.constructorAllocation === undefined) { - allocations.constructorAllocation = defaultConstructorAllocation(constructorContext); - } return allocations; } diff --git a/packages/truffle-codec/lib/interface/index.ts b/packages/truffle-codec/lib/interface/index.ts index c2645b11de2..416d20810af 100644 --- a/packages/truffle-codec/lib/interface/index.ts +++ b/packages/truffle-codec/lib/interface/index.ts @@ -7,7 +7,7 @@ export { StoragePointer } from "../types/pointer"; export { ContractAllocationInfo, StorageAllocations, StorageMemberAllocation, AbiAllocations, CalldataAllocations, EventAllocations } from "../types/allocation"; export { Slot, isWordsLength } from "../types/storage"; export { DecoderRequest, isStorageRequest, isCodeRequest } from "../types/request"; -export { EvmInfo } from "../types/evm"; +export { EvmInfo, AllocationInfo } from "../types/evm"; export { CalldataDecoding, EventDecoding } from "../types/wire"; export { decodeVariable, decodeEvent, decodeCalldata } from "./decoding"; diff --git a/packages/truffle-codec/lib/types/evm.ts b/packages/truffle-codec/lib/types/evm.ts index d405c7f714a..789aa91ecf7 100644 --- a/packages/truffle-codec/lib/types/evm.ts +++ b/packages/truffle-codec/lib/types/evm.ts @@ -22,18 +22,20 @@ export interface EvmInfo { state: EvmState; mappingKeys?: Slot[]; userDefinedTypes?: Types.TypesById; - allocations: { - storage?: Allocations.StorageAllocations; - memory?: Allocations.MemoryAllocations; - abi?: Allocations.AbiAllocations; - calldata?: Allocations.CalldataAllocations; - event?: Allocations.EventAllocations; - } + allocations: AllocationInfo; contexts?: Contexts.DecoderContextsById; currentContext?: Contexts.DecoderContext; internalFunctionsTable?: InternalFunctions; } +export interface AllocationInfo { + storage?: Allocations.StorageAllocations; + memory?: Allocations.MemoryAllocations; + abi?: Allocations.AbiAllocations; + calldata?: Allocations.CalldataAllocations; + event?: Allocations.EventAllocations; +} + export interface InternalFunctions { [pc: number]: InternalFunction } diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 2c55a485c6e..93e8c9b5bbe 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder:decoder"); +const debug = debugModule("decoder:contract"); import * as CodecUtils from "truffle-codec-utils"; import { Types, Values } from "truffle-codec-utils"; @@ -37,12 +37,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; - private allocations: { - storage: Codec.StorageAllocations; - abi: Codec.AbiAllocations; - calldata: Codec.CalldataAllocations; - event: Codec.EventAllocations; - }; + private allocations: Codec.AllocationInfo; private stateVariableReferences: Codec.StorageMemberAllocation[]; @@ -71,23 +66,29 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.contractNodes[this.contractNode.id] = this.contractNode; if(this.contract.deployedBytecode) { //just to be safe const context = Utils.makeContext(this.contract, this.contractNode); + debug("adding context: %O", context); const hash = CodecUtils.Conversion.toHexString( CodecUtils.EVM.keccak256({type: "string", value: context.binary }) ); + debug("with hash: %s", hash); + this.context = context; this.contextHash = hash; - this.contexts[hash] = this.context; + this.contexts[hash] = context; } if(this.contract.bytecode) { //now the constructor version const constructorContext = Utils.makeContext(this.contract, this.contractNode, true); + debug("adding context: %O", constructorContext); const hash = CodecUtils.Conversion.toHexString( CodecUtils.EVM.keccak256({type: "string", value: constructorContext.binary }) ); + debug("with hash: %s", hash); + this.constructorContext = constructorContext; this.constructorContextHash = hash; - this.contexts[hash] = this.constructorContext; + this.contexts[hash] = constructorContext; } for(let relevantContract of this.relevantContracts) { @@ -97,6 +98,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.contractNodes[node.id] = node; if(relevantContract.deployedBytecode) { const context = Utils.makeContext(relevantContract, node); + debug("adding context: %O", context); const hash = CodecUtils.Conversion.toHexString( CodecUtils.EVM.keccak256({type: "string", value: context.binary @@ -107,6 +109,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } } + debug("contexts: %o", this.contexts); this.contexts = CodecUtils.Contexts.normalizeContexts(this.contexts); this.context = this.contexts[this.contextHash]; this.constructorContext = this.contexts[this.constructorContextHash]; @@ -126,6 +129,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { debug("init called"); [this.referenceDeclarations, this.userDefinedTypes] = this.getUserDefinedTypes(); + this.allocations = {}; this.allocations.storage = Codec.getStorageAllocations( this.referenceDeclarations, {[this.contractNode.id]: this.contractNode} diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index dc2c57933d0..f7d55b8759d 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("decoder:decoder"); +const debug = debugModule("decoder:wire"); import * as CodecUtils from "truffle-codec-utils"; import { Types, Values } from "truffle-codec-utils"; @@ -28,12 +28,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; - private allocations: { - storage: Codec.StorageAllocations; - abi: Codec.AbiAllocations; - calldata: Codec.CalldataAllocations; - event: Codec.EventAllocations; - }; + private allocations: Codec.AllocationInfo; private codeCache: DecoderTypes.CodeCache = {}; @@ -95,6 +90,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }) ); + this.allocations = {}; this.allocations.storage = Codec.getStorageAllocations(this.referenceDeclarations, this.contractNodes); this.allocations.abi = Codec.getAbiAllocations(this.referenceDeclarations); this.allocations.calldata = Codec.getCalldataAllocations(allocationInfo, this.referenceDeclarations, this.allocations.abi); From 45c87f89f9b6ccdec031f0e1fdfed6f687b5cb2c Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Fri, 21 Jun 2019 15:42:30 -0400 Subject: [PATCH 30/89] Clean up some typing --- .../truffle-codec/lib/interface/decoding.ts | 1 + packages/truffle-codec/lib/types/wire.ts | 30 +++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 300ff90cdb8..3aef148acba 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -149,6 +149,7 @@ export function* decodeEvent(info: EvmInfo): IterableIterator Date: Fri, 21 Jun 2019 15:55:30 -0400 Subject: [PATCH 31/89] Account for libraries in event allocation in contract.ts --- packages/truffle-decoder/lib/contract.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 93e8c9b5bbe..91bbb418b2c 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -129,6 +129,17 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { debug("init called"); [this.referenceDeclarations, this.userDefinedTypes] = this.getUserDefinedTypes(); + let libraryAllocationInfo: Codec.ContractAllocationInfo[] = + Object.entries(this.contracts).filter( + ([id, _]) => this.contractNodes[parseInt(id)].contractKind === "library" + ). + map( + ([id, { abi }]) => ({ + abi: abi, + id: parseInt(id) + }) + ); + this.allocations = {}; this.allocations.storage = Codec.getStorageAllocations( this.referenceDeclarations, @@ -145,10 +156,13 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.allocations.abi ); this.allocations.event = Codec.getEventAllocations( - [{ - abi: this.contract.abi, - id: this.contractNode.id - }], + [ + { + abi: this.contract.abi, + id: this.contractNode.id + }, + ...libraryAllocationInfo + ], this.referenceDeclarations, this.allocations.abi ); From 1297710f22991dd38da14d0546883e46e44dc029 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Fri, 21 Jun 2019 17:30:24 -0400 Subject: [PATCH 32/89] Make mode parameter consistent --- packages/truffle-codec/lib/decode/abi.ts | 43 +++++++++---------- packages/truffle-codec/lib/decode/event.ts | 8 ++-- packages/truffle-codec/lib/decode/index.ts | 8 ++-- packages/truffle-codec/lib/decode/value.ts | 13 +----- .../truffle-codec/lib/interface/decoding.ts | 2 +- packages/truffle-codec/lib/types/evm.ts | 12 ++++++ 6 files changed, 43 insertions(+), 43 deletions(-) diff --git a/packages/truffle-codec/lib/decode/abi.ts b/packages/truffle-codec/lib/decode/abi.ts index 33a2dbcdc93..944459c8f88 100644 --- a/packages/truffle-codec/lib/decode/abi.ts +++ b/packages/truffle-codec/lib/decode/abi.ts @@ -8,40 +8,37 @@ import decodeValue from "./value"; import { AbiPointer, DataPointer } from "../types/pointer"; import { AbiMemberAllocation } from "../types/allocation"; import { abiSizeForType, isTypeDynamic } from "../allocate/abi"; -import { EvmInfo } from "../types/evm"; +import { EvmInfo, DecoderMode } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; import { StopDecodingError } from "../types/errors"; -//what is strict mode? -//in strict mode, we don't return errors, we *throw* them! -//it also turns on error checking for overlong arrays or strings - -export default function* decodeAbi(dataType: Types.Type, pointer: AbiPointer, info: EvmInfo, base: number = 0, strict: boolean = false): IterableIterator { +export default function* decodeAbi(dataType: Types.Type, pointer: AbiPointer, info: EvmInfo, base: number = 0, mode: DecoderMode = "normal"): IterableIterator { if(Types.isReferenceType(dataType)) { let dynamic: boolean; try { dynamic = isTypeDynamic(dataType, info.allocations.abi); } catch(error) { //error: Values.DecodingError - if(strict) { + if(mode === "strict") { throw new StopDecodingError(); } return Values.makeGenericErrorResult(dataType, error.error); } if(dynamic) { - return yield* decodeAbiReferenceByAddress(dataType, pointer, info, base, strict); + return yield* decodeAbiReferenceByAddress(dataType, pointer, info, base, mode); } else { - return yield* decodeAbiReferenceStatic(dataType, pointer, info, strict); + return yield* decodeAbiReferenceStatic(dataType, pointer, info, mode); } } else { debug("pointer %o", pointer); - return yield* decodeValue(dataType, pointer, info, strict ? "strict" : "normal"); + return yield* decodeValue(dataType, pointer, info, mode); } } -export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, pointer: DataPointer, info: EvmInfo, base: number = 0, strict: boolean = false): IterableIterator { +export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, pointer: DataPointer, info: EvmInfo, base: number = 0, mode: DecoderMode = "normal"): IterableIterator { + const strict = mode === "strict"; const { allocations: { abi: allocations }, state } = info; debug("pointer %o", pointer); //this variable holds the location we should look to *next* @@ -89,7 +86,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin start: startPosition, length: size } - return yield* decodeAbiReferenceStatic(dataType, staticPointer, info, strict); + return yield* decodeAbiReferenceStatic(dataType, staticPointer, info, mode); } let length: number; let rawLength: Uint8Array; @@ -128,7 +125,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin length } - return yield* decodeValue(dataType, childPointer, info, strict ? "strict" : "normal"); + return yield* decodeValue(dataType, childPointer, info, mode); case "array": @@ -191,18 +188,18 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin start: startPosition + index * baseSize, length: baseSize }, - info, startPosition, strict + info, startPosition, mode )) ); //pointer base is always start of list, never the length } return new Values.ArrayValue(dataType, decodedChildren); case "struct": - return yield* decodeAbiStructByPosition(dataType, location, startPosition, info, strict); + return yield* decodeAbiStructByPosition(dataType, location, startPosition, info, mode); } } -export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer: AbiPointer, info: EvmInfo, strict: boolean = false): IterableIterator { +export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer: AbiPointer, info: EvmInfo, mode: DecoderMode = "normal"): IterableIterator { debug("static"); debug("pointer %o", pointer); const location = pointer.location; @@ -217,7 +214,7 @@ export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer baseSize = abiSizeForType(dataType.baseType, info.allocations.abi); } catch(error) { //error: Values.DecodingError - if(strict) { + if(mode === "strict") { throw new StopDecodingError(); } return Values.makeGenericErrorResult(dataType, error.error); @@ -233,21 +230,21 @@ export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer start: pointer.start + index * baseSize, length: baseSize }, - info, 0, strict //the 0 is meaningless, just there as default + info, 0, mode //the 0 is meaningless, just there as default )) ); } return new Values.ArrayValue(dataType, decodedChildren); case "struct": - return yield* decodeAbiStructByPosition(dataType, location, pointer.start, info, strict); + return yield* decodeAbiStructByPosition(dataType, location, pointer.start, info, mode); } } type AbiLocation = "calldata" | "eventdata" | "abi"; //note that this function takes the start position as a *number*; it does not take a pointer -function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLocation, startPosition: number, info: EvmInfo, strict: boolean = false): IterableIterator { +function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLocation, startPosition: number, info: EvmInfo, mode: DecoderMode = "normal"): IterableIterator { const { userDefinedTypes, allocations: { abi: allocations } } = info; const typeLocation = location === "eventdata" @@ -257,7 +254,7 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc const typeId = dataType.id; const structAllocation = allocations[typeId]; if(!structAllocation) { - if(strict) { + if(mode === "strict") { throw new StopDecodingError(); } return new Values.StructErrorResult( @@ -279,7 +276,7 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc let memberName = memberAllocation.definition.name; let storedType = userDefinedTypes[typeId]; if(!storedType) { - if(strict) { + if(mode === "strict") { throw new StopDecodingError(); } return new Values.StructErrorResult( @@ -292,7 +289,7 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc decodedMembers.push([ memberName, - (yield* decodeAbi(memberType, childPointer, info, 0, strict)) + (yield* decodeAbi(memberType, childPointer, info, 0, mode)) //the 0 is meaningless, just there by default ]); } diff --git a/packages/truffle-codec/lib/decode/event.ts b/packages/truffle-codec/lib/decode/event.ts index 6cdabe4f467..9d3ae3981f9 100644 --- a/packages/truffle-codec/lib/decode/event.ts +++ b/packages/truffle-codec/lib/decode/event.ts @@ -5,11 +5,11 @@ import decodeValue from "./value"; import read from "../read"; import { Types, Values, Conversion as ConversionUtils } from "truffle-codec-utils"; import { EventTopicPointer } from "../types/pointer"; -import { EvmInfo } from "../types/evm"; +import { EvmInfo, DecoderMode } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; import { StopDecodingError } from "../types/errors"; -export default function* decodeTopic(dataType: Types.Type, pointer: EventTopicPointer, info: EvmInfo, strict: boolean = false): IterableIterator { +export default function* decodeTopic(dataType: Types.Type, pointer: EventTopicPointer, info: EvmInfo, mode: DecoderMode = "normal"): IterableIterator { if(Types.isReferenceType(dataType)) { //we cannot decode reference types "stored" in topics; we have to just return an error let bytes: Uint8Array; @@ -17,7 +17,7 @@ export default function* decodeTopic(dataType: Types.Type, pointer: EventTopicPo bytes = yield* read(pointer, info.state); } catch(error) { //error: Values.DecodingError - if(strict) { + if(mode === "strict") { throw new StopDecodingError(); } return Values.makeGenericErrorResult(dataType, error.error); @@ -33,5 +33,5 @@ export default function* decodeTopic(dataType: Types.Type, pointer: EventTopicPo ); } //otherwise, dispatch to decodeValue - return yield* decodeValue(dataType, pointer, info, strict ? "strict" : "normal"); + return yield* decodeValue(dataType, pointer, info, mode); } diff --git a/packages/truffle-codec/lib/decode/index.ts b/packages/truffle-codec/lib/decode/index.ts index deddfdd73bd..b0f0845fd85 100644 --- a/packages/truffle-codec/lib/decode/index.ts +++ b/packages/truffle-codec/lib/decode/index.ts @@ -12,10 +12,10 @@ import decodeSpecial from "./special"; import decodeTopic from "./event"; import { Types, Values } from "truffle-codec-utils"; import * as Pointer from "../types/pointer"; -import { EvmInfo } from "../types/evm"; +import { EvmInfo, DecoderMode } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; -export default function* decode(dataType: Types.Type, pointer: Pointer.DataPointer, info: EvmInfo, base: number = 0, strict: boolean = false): IterableIterator { +export default function* decode(dataType: Types.Type, pointer: Pointer.DataPointer, info: EvmInfo, base: number = 0, mode: DecoderMode = "normal"): IterableIterator { debug("type %O", dataType); debug("pointer %O", pointer); @@ -38,10 +38,10 @@ export default function* decode(dataType: Types.Type, pointer: Pointer.DataPoint case "calldata": case "eventdata": - return yield* decodeAbi(dataType, pointer, info, base, strict); + return yield* decodeAbi(dataType, pointer, info, base, mode); case "eventtopic": - return yield* decodeTopic(dataType, pointer, info, strict); + return yield* decodeTopic(dataType, pointer, info, mode); case "memory": //NOTE: this case should never actually occur, but I'm including it diff --git a/packages/truffle-codec/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts index d58e2b09ff0..689b7434441 100644 --- a/packages/truffle-codec/lib/decode/value.ts +++ b/packages/truffle-codec/lib/decode/value.ts @@ -6,20 +6,11 @@ import * as CodecUtils from "truffle-codec-utils"; import { Types, Values } from "truffle-codec-utils"; import BN from "bn.js"; import { DataPointer } from "../types/pointer"; -import { EvmInfo } from "../types/evm"; +import { EvmInfo, DecoderMode } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; import { StopDecodingError } from "../types/errors"; - -//EXPLANATION OF MODES: -//1. normal mode -- the default -//bad padding causes an error to be returned. -//2. permissive mode -- used for stack decoding -//no error on bad padding for certain types. other things may still cause errors to be returned. -//3. strict mode -- used for event decoding -//bad padding is an error, yes, but in this mode we don't return errors, we THROW them! -//(except for internal functions; strict mode doesn't affect those) -export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, info: EvmInfo, mode: "normal" | "permissive" | "strict" = "normal"): IterableIterator { +export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, info: EvmInfo, mode: DecoderMode = "normal"): IterableIterator { //NOTE: this does not actually return a Uint8Aarray, but due to the use of yield* read, //we have to include it in the type :-/ const { state } = info; diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 3aef148acba..55544064c91 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -127,7 +127,7 @@ export function* decodeEvent(info: EvmInfo): IterableIterator Date: Fri, 21 Jun 2019 17:45:07 -0400 Subject: [PATCH 33/89] Allow decodeEvent to take a target name --- .../truffle-codec/lib/interface/decoding.ts | 6 ++++- packages/truffle-decoder/lib/contract.ts | 22 ++++++++----------- packages/truffle-decoder/lib/wire.ts | 22 ++++++++----------- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 55544064c91..11d61dca2a7 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -88,7 +88,7 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator { +export function* decodeEvent(info: EvmInfo, targetName: string | null = null): IterableIterator { const compiler = info.currentContext.compiler; const allocations = info.allocations.event; let rawSelector: Uint8Array; @@ -116,6 +116,10 @@ export function* decodeEvent(info: EvmInfo): IterableIterator { + public async decodeLog(log: Log, name: string | null = null): Promise { if(log.address !== this.contractAddress) { throw new DecoderTypes.EventOrTransactionIsNotForThisContractError(log.address, this.contractAddress); } @@ -426,7 +426,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contextsById }; - const decoder = Codec.decodeEvent(info); + const decoder = Codec.decodeEvent(info, name); let result = decoder.next(); while(!result.done) { @@ -448,8 +448,8 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } //NOTE: will only work with logs for this address! - public async decodeLogs(logs: Log[]): Promise { - return await Promise.all(logs.map(this.decodeLog)); + public async decodeLogs(logs: Log[], name: string | null = null): Promise { + return await Promise.all(logs.map(log => this.decodeLog(log, name))); } public async events(name: string | null = null, fromBlock: BlockType = "latest", toBlock: BlockType = "latest"): Promise { @@ -459,17 +459,13 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { toBlock, }); - let events = await this.decodeLogs(logs); + let events = await this.decodeLogs(logs, name); + //if a target name was specified, we'll restrict to events that decoded + //to something with that name. (note that only decodings with that name + //will have been returned from decodeLogs in the first place) if(name !== null) { - events = events.map( - event => ({ - ...event, - decodings: event.decodings.filter( - decoding => decoding.name === name - ) - }) - ).filter( + events = events.filter( event => event.decodings.length > 0 ); } diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index f7d55b8759d..6b8c2ef3ccf 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -174,7 +174,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - public async decodeLog(log: Log): Promise { + public async decodeLog(log: Log, name: string | null = null): Promise { const block = log.blockNumber; const data = CodecUtils.Conversion.toBytes(log.data); const topics = log.topics.map(CodecUtils.Conversion.toBytes); @@ -188,7 +188,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contextsById }; - const decoder = Codec.decodeEvent(info); + const decoder = Codec.decodeEvent(info, name); let result = decoder.next(); while(!result.done) { @@ -209,8 +209,8 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - public async decodeLogs(logs: Log[]): Promise { - return await Promise.all(logs.map(this.decodeLog)); + public async decodeLogs(logs: Log[], name: string | null = null): Promise { + return await Promise.all(logs.map(log => this.decodeLog(log, name))); } public async events(name: string | null = null, fromBlock: BlockType = "latest", toBlock: BlockType = "latest"): Promise { @@ -219,17 +219,13 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { toBlock, }); - let events = await this.decodeLogs(logs); + let events = await this.decodeLogs(logs, name); + //if a target name was specified, we'll restrict to events that decoded + //to something with that name. (note that only decodings with that name + //will have been returned from decodeLogs in the first place) if(name !== null) { - events = events.map( - event => ({ - ...event, - decodings: event.decodings.filter( - decoding => decoding.name === name - ) - }) - ).filter( + events = events.filter( event => event.decodings.length > 0 ); } From 7b828114b9a4335f4902cfea1f1377ce0d58d47b Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Mon, 24 Jun 2019 18:43:18 -0400 Subject: [PATCH 34/89] Some minor fixes --- packages/truffle-codec-utils/src/abi.ts | 2 ++ packages/truffle-codec-utils/src/evm.ts | 7 ++++- packages/truffle-codec/lib/decode/abi.ts | 34 +++++++++------------ packages/truffle-codec/lib/types/pointer.ts | 3 +- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/packages/truffle-codec-utils/src/abi.ts b/packages/truffle-codec-utils/src/abi.ts index 67807f1092a..9379f9c1959 100644 --- a/packages/truffle-codec-utils/src/abi.ts +++ b/packages/truffle-codec-utils/src/abi.ts @@ -122,6 +122,8 @@ export namespace AbiUtils { return "nonpayable"; } + //note: in future, this will be replaced by a toABI function, + //which will also work for variable declarations export function matchesAbi(abiEntry: AbiEntry, node: AstDefinition, referenceDeclarations: AstReferences): boolean { //first: does the basic name and type match? switch(node.nodeType) { diff --git a/packages/truffle-codec-utils/src/evm.ts b/packages/truffle-codec-utils/src/evm.ts index fc0d23ce24e..182c095f794 100644 --- a/packages/truffle-codec-utils/src/evm.ts +++ b/packages/truffle-codec-utils/src/evm.ts @@ -37,7 +37,12 @@ export namespace EVM { return ConversionUtils.toBN(sha); } - export function equalData(bytes1: Uint8Array, bytes2: Uint8Array): boolean { + //checks if two bytearrays (which may be undefined) are equal. + //does not consider undefined to be equal to itself. + export function equalData(bytes1: Uint8Array | undefined, bytes2: Uint8Array | undefined): boolean { + if(!bytes1 || !bytes2) { + return false; + } if(bytes1.length !== bytes2.length) { return false; } diff --git a/packages/truffle-codec/lib/decode/abi.ts b/packages/truffle-codec/lib/decode/abi.ts index 944459c8f88..b4c282ef74b 100644 --- a/packages/truffle-codec/lib/decode/abi.ts +++ b/packages/truffle-codec/lib/decode/abi.ts @@ -5,14 +5,16 @@ import read from "../read"; import * as CodecUtils from "truffle-codec-utils"; import { Types, Values } from "truffle-codec-utils"; import decodeValue from "./value"; -import { AbiPointer, DataPointer } from "../types/pointer"; +import { AbiDataPointer, DataPointer } from "../types/pointer"; import { AbiMemberAllocation } from "../types/allocation"; import { abiSizeForType, isTypeDynamic } from "../allocate/abi"; import { EvmInfo, DecoderMode } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; import { StopDecodingError } from "../types/errors"; -export default function* decodeAbi(dataType: Types.Type, pointer: AbiPointer, info: EvmInfo, base: number = 0, mode: DecoderMode = "normal"): IterableIterator { +type AbiLocation = "calldata" | "eventdata"; //leaving out "abi" as it shouldn't occur here + +export default function* decodeAbi(dataType: Types.Type, pointer: AbiDataPointer, info: EvmInfo, base: number = 0, mode: DecoderMode = "normal"): IterableIterator { if(Types.isReferenceType(dataType)) { let dynamic: boolean; try { @@ -109,17 +111,15 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin return Values.makeGenericErrorResult(dataType, error.error); } let lengthAsBN = CodecUtils.Conversion.toBN(rawLength); - if(strict && lengthAsBN.gtn(info.state.eventdata.length)) { - //HACK: yes, we always compare to eventdata, not the actual location! - //this is OK for now as strict mode is only used when decoding events. - //also, you may notice that the comparison is a bit crude; that's OK, - //this is just to prevent huge numbers from DOSing us, other errors will - //still be caught regardless + if(strict && lengthAsBN.gtn(info.state[location].length)) { + //you may notice that the comparison is a bit crude; that's OK, this is + //just to prevent huge numbers from DOSing us, other errors will still + //be caught regardless throw new StopDecodingError(); } length = lengthAsBN.toNumber(); - let childPointer: AbiPointer = { + let childPointer: AbiDataPointer = { location, start: startPosition + CodecUtils.EVM.WORD_SIZE, length @@ -146,12 +146,10 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin return Values.makeGenericErrorResult(dataType, error.error); } let lengthAsBN = CodecUtils.Conversion.toBN(rawLength); - if(strict && lengthAsBN.gtn(info.state.eventdata.length)) { - //HACK: yes, we always compare to eventdata, not the actual location! - //this is OK for now as strict mode is only used when decoding events. - //also, you may notice that the comparison is a bit crude; that's OK, - //this is just to prevent huge numbers from DOSing us, other errors will - //still be caught regardless + if(strict && lengthAsBN.gtn(info.state[location].length)) { + //you may notice that the comparison is a bit crude; that's OK, this is + //just to prevent huge numbers from DOSing us, other errors will still + //be caught regardless throw new StopDecodingError(); } length = lengthAsBN.toNumber(); @@ -199,7 +197,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin } } -export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer: AbiPointer, info: EvmInfo, mode: DecoderMode = "normal"): IterableIterator { +export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer: AbiDataPointer, info: EvmInfo, mode: DecoderMode = "normal"): IterableIterator { debug("static"); debug("pointer %o", pointer); const location = pointer.location; @@ -241,8 +239,6 @@ export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer } } -type AbiLocation = "calldata" | "eventdata" | "abi"; - //note that this function takes the start position as a *number*; it does not take a pointer function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLocation, startPosition: number, info: EvmInfo, mode: DecoderMode = "normal"): IterableIterator { const { userDefinedTypes, allocations: { abi: allocations } } = info; @@ -267,7 +263,7 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc for(let index = 0; index < structAllocation.members.length; index++) { const memberAllocation = structAllocation.members[index]; const memberPointer = memberAllocation.pointer; - const childPointer: AbiPointer = { + const childPointer: AbiDataPointer = { location, start: startPosition + memberPointer.start, length: memberPointer.length diff --git a/packages/truffle-codec/lib/types/pointer.ts b/packages/truffle-codec/lib/types/pointer.ts index c17d00c8dcb..9cb2a3dee38 100644 --- a/packages/truffle-codec/lib/types/pointer.ts +++ b/packages/truffle-codec/lib/types/pointer.ts @@ -3,9 +3,10 @@ import { Range } from "./storage"; export type DataPointer = StackPointer | MemoryPointer | StoragePointer | CalldataPointer | StackLiteralPointer | ConstantDefinitionPointer - | SpecialPointer | EventDataPointer | EventTopicPointer | GenericAbiPointer; + | SpecialPointer | EventDataPointer | EventTopicPointer; export type AbiPointer = CalldataPointer | EventDataPointer | GenericAbiPointer; +export type AbiDataPointer = CalldataPointer | EventDataPointer; export interface StackPointer { location: "stack", From a34dab9c6cf492a3fbed8b4a122b1214d28c8ed1 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 25 Jun 2019 02:23:12 -0400 Subject: [PATCH 35/89] Update encoder for revised output format, and fix error in address encoding --- .../truffle-codec-utils/src/types/types.ts | 1 - packages/truffle-codec/lib/allocate/abi.ts | 4 +- packages/truffle-codec/lib/encode/abi.ts | 48 ++++++++++--------- packages/truffle-decoder/lib/contract.ts | 8 ++-- packages/truffle-decoder/lib/wire.ts | 10 ++-- 5 files changed, 37 insertions(+), 34 deletions(-) diff --git a/packages/truffle-codec-utils/src/types/types.ts b/packages/truffle-codec-utils/src/types/types.ts index 25329ba6b1a..d555a20876a 100644 --- a/packages/truffle-codec-utils/src/types/types.ts +++ b/packages/truffle-codec-utils/src/types/types.ts @@ -21,7 +21,6 @@ const debug = debugModule("codec-utils:types"); import BN from "bn.js"; import { AstDefinition, AstReferences } from "../ast"; import { Definition as DefinitionUtils } from "../definition"; -import { Contexts } from "../contexts"; import { CompilerVersion } from "../compiler"; export namespace Types { diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index 2566c7a44d2..80d65c728f7 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -244,7 +244,7 @@ function allocateCalldata( offset = CodecUtils.EVM.SELECTOR_SIZE; //search through base contracts, from most derived (right) to most base (left) node = linearizedBaseContracts.reduceRight( - (foundNode, baseContractId) => { + (foundNode: AstDefinition, baseContractId: number) => { if(foundNode) { return foundNode //once we've found something, we don't need to keep looking }; @@ -305,7 +305,7 @@ function allocateEvent( //search through base contracts, from most derived (right) to most base (left) let node: AstDefinition; node = linearizedBaseContracts.reduceRight( - (foundNode, baseContractId) => { + (foundNode: AstDefinition, baseContractId: number) => { if(foundNode) { return foundNode //once we've found something, we don't need to keep looking }; diff --git a/packages/truffle-codec/lib/encode/abi.ts b/packages/truffle-codec/lib/encode/abi.ts index 539ee707151..ea6121e42f3 100644 --- a/packages/truffle-codec/lib/encode/abi.ts +++ b/packages/truffle-codec/lib/encode/abi.ts @@ -2,6 +2,7 @@ import { Types, Values, Conversion as ConversionUtils, EVM as EVMUtils } from "t import { AbiAllocations } from "../types/allocation"; import { isTypeDynamic, abiSizeForType } from "../allocate/abi"; import sum from "lodash.sum"; +import utf8 from "utf8"; export function encodeAbi(input: Values.Result, allocations?: AbiAllocations): Uint8Array | undefined { if(input instanceof Values.ErrorResult) { @@ -19,43 +20,45 @@ export function encodeAbi(input: Values.Result, allocations?: AbiAllocations): U } //now, the types that actually work! if(input instanceof Values.UintValue) { - return ConversionUtils.toBytes(input.value, EVMUtils.WORD_SIZE); + return ConversionUtils.toBytes(input.value.asBN, EVMUtils.WORD_SIZE); } if(input instanceof Values.IntValue) { - return ConversionUtils.toBytes(input.value, EVMUtils.WORD_SIZE); + return ConversionUtils.toBytes(input.value.asBN, EVMUtils.WORD_SIZE); } if(input instanceof Values.EnumValue) { return ConversionUtils.toBytes(input.value.numeric, EVMUtils.WORD_SIZE); } if(input instanceof Values.BoolValue) { let bytes = new Uint8Array(EVMUtils.WORD_SIZE); //is initialized to zeroes - if(input.value) { + if(input.value.asBool) { bytes[EVMUtils.WORD_SIZE - 1] = 1; } return bytes; } - if(input instanceof Values.BytesValue) { - switch(input.type.kind) { - case "static": { - let bytes = ConversionUtils.toBytes(input.value); - return rightPad(bytes, EVMUtils.WORD_SIZE); - } - case "dynamic": { - let bytes = ConversionUtils.toBytes(input.value); - return padAndPrependLength(bytes); - } - } + if(input instanceof Values.BytesStaticValue) { + let bytes = ConversionUtils.toBytes(input.value.asHex); + return rightPad(bytes, EVMUtils.WORD_SIZE); + } + if(input instanceof Values.BytesDynamicValue) { + let bytes = ConversionUtils.toBytes(input.value.asHex); + return padAndPrependLength(bytes); } if(input instanceof Values.AddressValue) { - let bytes = ConversionUtils.toBytes(input.value); - return rightPad(bytes, EVMUtils.WORD_SIZE); + return ConversionUtils.toBytes(input.value.asAddress, EVMUtils.WORD_SIZE); } if(input instanceof Values.ContractValue) { - let bytes = ConversionUtils.toBytes(input.value.address); - return rightPad(bytes, EVMUtils.WORD_SIZE); + return ConversionUtils.toBytes(input.value.address, EVMUtils.WORD_SIZE); } if(input instanceof Values.StringValue) { - let bytes = stringToBytes(input.value); + let bytes: Uint8Array; + switch(input.value.kind) { + case "valid": + bytes = stringToBytes(input.value.asString); + break; + case "malformed": + bytes = ConversionUtils.toBytes(input.value.asHex); + break; + } return padAndPrependLength(bytes); } if(input instanceof Values.FunctionExternalValue) { @@ -91,15 +94,14 @@ export function encodeAbi(input: Values.Result, allocations?: AbiAllocations): U } function stringToBytes(input: string): Uint8Array { - //HACK WARNING: does not properly handle the UTF-16 to UTF-8 conversion - //(i.e. we ignore this problem) - //HOWEVER, since we also ignore this in the decoder, this should work - //fine for the purposes we're using it for now >_> + input = utf8.encode(input); let bytes = new Uint8Array(input.length); for(let i = 0; i < input.length; i++) { bytes[i] = input.charCodeAt(i); } return bytes; + //NOTE: this will throw an error if the string contained malformed UTF-16! + //but, well, it shouldn't contain that... } function padAndPrependLength(bytes: Uint8Array): Uint8Array { diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 10934e341aa..a85eb28df33 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -184,12 +184,12 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { types[id] = Types.definitionToStoredType(contractNode, compiler); //now, add its struct and enum definitions for(const node of contractNode.nodes) { - if(node.nodeType === "StructDefinition" || node.nodeType === "EnumDefinition") { - references[node.id] = node; + if(node.nodeType === "StructDefinition" || node.nodeType === "EnumDefinition") { + references[node.id] = node; //HACK even though we don't have all the references, we only need one: //the reference to the contract itself, which we just added, so we're good - types[node.id] = Types.definitionToStoredType(node, compiler, references); - } + types[node.id] = Types.definitionToStoredType(node, compiler, references); + } } } return [references, types]; diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 6b8c2ef3ccf..cb507c58397 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -109,10 +109,12 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { types[id] = Types.definitionToStoredType(contractNode, compiler); //now, add its struct and enum definitions for(const node of contractNode.nodes) { - if(node.nodeType === "StructDefinition" || node.nodeType === "EnumDefinition") { - references[node.id] = node; - types[node.id] = Types.definitionToStoredType(node, compiler); - } + if(node.nodeType === "StructDefinition" || node.nodeType === "EnumDefinition") { + references[node.id] = node; + //HACK even though we don't have all the references, we only need one: + //the reference to the contract itself, which we just added, so we're good + types[node.id] = Types.definitionToStoredType(node, compiler, references); + } } } return [references, types]; From fee9356c9cc5981ecc19bab8df609820be160cda Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 25 Jun 2019 17:27:18 -0400 Subject: [PATCH 36/89] Update Solidity version in truffle-decoder tests --- .../test/contracts/DecodingSample.sol | 14 +++++++------- .../truffle-decoder/test/contracts/Migrations.sol | 2 +- packages/truffle-decoder/test/truffle-config.js | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/truffle-decoder/test/contracts/DecodingSample.sol b/packages/truffle-decoder/test/contracts/DecodingSample.sol index 237147a6165..7627ac64b9d 100644 --- a/packages/truffle-decoder/test/contracts/DecodingSample.sol +++ b/packages/truffle-decoder/test/contracts/DecodingSample.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.23; +pragma solidity ^0.5.10; contract DecodingSample { enum E { @@ -68,7 +68,7 @@ contract DecodingSample { varUint = 1; varString = "two"; varBool = true; - varAddress = 0x0012345567890abcdeffedcba09876543211337121; + varAddress = 0x12345567890abcDEffEDcBa09876543211337121; varBytes7 = hex"78554477331122"; varBytes = new bytes(4); varBytes[0] = 0x01; @@ -79,7 +79,7 @@ contract DecodingSample { varStructS.structInt = -2; varStructS.structString = "three"; varStructS.structBool = false; - varStructS.structAddress = 0x0054321567890abcdeffedcba09876543211337121; + varStructS.structAddress = 0x54321567890abcdeFfEDcBA09876543211337121; varStructS.structS2.structTwoFixedArrayUint[0] = 4; varStructS.structS2.structTwoFixedArrayUint[1] = 2; varStructS.structS2.structTwoDynamicArrayUint = new uint[](3); @@ -93,8 +93,8 @@ contract DecodingSample { fixedArrayString[1] = "world"; fixedArrayBool[0] = true; fixedArrayBool[1] = false; - fixedArrayAddress[0] = 0x0098761567890abcdeffedcba09876543211337121; - fixedArrayAddress[1] = 0x00fedc1567890abcdeffedcba09876543211337121; + fixedArrayAddress[0] = 0x98761567890ABCdeffEdCba09876543211337121; + fixedArrayAddress[1] = 0xfEDc1567890aBcDeFfEdcba09876543211337121; fixedArrayBytes7[0] = hex"75754477331122"; fixedArrayBytes7[1] = hex"e7d14477331122"; fixedArrayByte[0] = 0x37; @@ -112,8 +112,8 @@ contract DecodingSample { dynamicArrayBool[0] = true; dynamicArrayBool[1] = false; dynamicArrayAddress = new address[](2); - dynamicArrayAddress[0] = 0x0098761567890abcdeffedcba09876543211337121; - dynamicArrayAddress[1] = 0x00fedc1567890abcdeffedcba09876543211337121; + dynamicArrayAddress[0] = 0x98761567890ABCdeffEdCba09876543211337121; + dynamicArrayAddress[1] = 0xfEDc1567890aBcDeFfEdcba09876543211337121; dynamicArrayBytes7 = new bytes7[](2); dynamicArrayBytes7[0] = hex"75754477331122"; dynamicArrayBytes7[1] = hex"e7d14477331122"; diff --git a/packages/truffle-decoder/test/contracts/Migrations.sol b/packages/truffle-decoder/test/contracts/Migrations.sol index c4efb65e216..073de9cce7e 100644 --- a/packages/truffle-decoder/test/contracts/Migrations.sol +++ b/packages/truffle-decoder/test/contracts/Migrations.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.23; +pragma solidity ^0.5.10; contract Migrations { address public owner; diff --git a/packages/truffle-decoder/test/truffle-config.js b/packages/truffle-decoder/test/truffle-config.js index 3af7faaa4f7..54cd42d45d8 100644 --- a/packages/truffle-decoder/test/truffle-config.js +++ b/packages/truffle-decoder/test/truffle-config.js @@ -57,7 +57,7 @@ module.exports = { // Configure your compilers compilers: { solc: { - version: "0.4.23" // Fetch exact version from solc-bin (default: truffle's version) + version: "0.5.10" // Fetch exact version from solc-bin (default: truffle's version) // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) // settings: { // See the solidity docs for advice about optimization and evmVersion // optimizer: { From 509638df3b3679f1eca1ca34a333324d58a0b74f Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 25 Jun 2019 18:48:34 -0400 Subject: [PATCH 37/89] Add some initial tests of the wire decoder --- packages/truffle-decoder/package.json | 1 + .../test/contracts/WireTest.sol | 40 ++++++++ .../test/migrations/2_decoding_sample.js | 5 - .../test/migrations/2_deploy_contracts.js | 6 ++ .../test/test/{test.js => decoding-test.js} | 4 +- .../truffle-decoder/test/test/wire-test.js | 96 +++++++++++++++++++ 6 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 packages/truffle-decoder/test/contracts/WireTest.sol delete mode 100644 packages/truffle-decoder/test/migrations/2_decoding_sample.js create mode 100644 packages/truffle-decoder/test/migrations/2_deploy_contracts.js rename packages/truffle-decoder/test/test/{test.js => decoding-test.js} (97%) create mode 100644 packages/truffle-decoder/test/test/wire-test.js diff --git a/packages/truffle-decoder/package.json b/packages/truffle-decoder/package.json index fcbdbad8d33..222c6746ec4 100644 --- a/packages/truffle-decoder/package.json +++ b/packages/truffle-decoder/package.json @@ -40,6 +40,7 @@ "@types/bn.js": "^4.11.5", "@types/debug": "^4.1.4", "@types/web3": "1.0.18", + "chai": "^4.2.0", "json-schema-to-typescript": "^6.1.3", "truffle-contract-schema": "^3.0.9", "typescript": "^3.5.1", diff --git a/packages/truffle-decoder/test/contracts/WireTest.sol b/packages/truffle-decoder/test/contracts/WireTest.sol new file mode 100644 index 00000000000..71c94aa9028 --- /dev/null +++ b/packages/truffle-decoder/test/contracts/WireTest.sol @@ -0,0 +1,40 @@ +pragma solidity ^0.5.10; +pragma experimental ABIEncoderV2; + +contract WireTest { + constructor(bool status, bytes memory info, Ternary whoknows) public { + emit ConstructorEvent(status, info, whoknows); + } + + event ConstructorEvent(bool, bytes, Ternary); + + struct Triple { + int x; + bytes32 y; + bytes z; + } + + enum Ternary { + Yes, No, MaybeSo + } + + event Danger(function() external); + + function danger() public { + emit Danger(this.danger); + } + + event EmitStuff(Triple, address[2], string[]); + + function emitStuff(Triple memory p, address[2] memory precompiles, string[] memory strings) public { + emit EmitStuff(p, precompiles, strings); + } + + event MoreStuff(WireTest, uint[]); + + function moreStuff(WireTest notThis, uint[] memory bunchOfInts) public { + emit MoreStuff(notThis, bunchOfInts); + } +} + +//TODO: add tests of library events & ambiguity diff --git a/packages/truffle-decoder/test/migrations/2_decoding_sample.js b/packages/truffle-decoder/test/migrations/2_decoding_sample.js deleted file mode 100644 index c0bac8f0827..00000000000 --- a/packages/truffle-decoder/test/migrations/2_decoding_sample.js +++ /dev/null @@ -1,5 +0,0 @@ -const DecodingSample = artifacts.require("./DecodingSample.sol"); - -module.exports = function(deployer) { - deployer.deploy(DecodingSample); -}; diff --git a/packages/truffle-decoder/test/migrations/2_deploy_contracts.js b/packages/truffle-decoder/test/migrations/2_deploy_contracts.js new file mode 100644 index 00000000000..cbf777482f6 --- /dev/null +++ b/packages/truffle-decoder/test/migrations/2_deploy_contracts.js @@ -0,0 +1,6 @@ +const DecodingSample = artifacts.require("DecodingSample.sol"); +//we don't deploy WireTest because we're going to use WireTest.new for that + +module.exports = function(deployer) { + deployer.deploy(DecodingSample); +}; diff --git a/packages/truffle-decoder/test/test/test.js b/packages/truffle-decoder/test/test/decoding-test.js similarity index 97% rename from packages/truffle-decoder/test/test/test.js rename to packages/truffle-decoder/test/test/decoding-test.js index 25035d6e11e..c94fb965fa8 100644 --- a/packages/truffle-decoder/test/test/test.js +++ b/packages/truffle-decoder/test/test/decoding-test.js @@ -1,7 +1,7 @@ const assert = require("assert"); const util = require("util"); // eslint-disable-line no-unused-vars -const TruffleContractDecoder = require("../../../truffle-decoder"); +const TruffleDecoder = require("../../../truffle-decoder"); const TruffleCodecUtils = require("../../../truffle-codec-utils"); const DecodingSample = artifacts.require("DecodingSample"); @@ -31,7 +31,7 @@ contract("DecodingSample", _accounts => { it("should get the initial state properly", async () => { let deployedContract = await DecodingSample.deployed(); let address = deployedContract.address; - const decoder = TruffleContractDecoder.forContract( + const decoder = TruffleDecoder.forContract( DecodingSample, [], web3.currentProvider diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js new file mode 100644 index 00000000000..7dcf4e2088d --- /dev/null +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -0,0 +1,96 @@ +const assert = require("chai").assert; + +const TruffleDecoder = require("../../../truffle-decoder"); + +const WireTest = artifacts.require("WireTest"); + +contract("WireTest", _accounts => { + it("should correctly decode transactions and events", async () => { + let deployedContract = await WireTest.new(true, "0xdeadbeef", 2); + let address = deployedContract.address; + let constructorHash = deployedContract.transactionHash; + + let emitStuffArgs = [ + { + x: -1, + y: "0xdeadbeef00000000deadbeef00000000deadbeef00000000deadbeef00000000", + z: "0xbababa" + }, + [ + "0x0000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000002" + ], + ["hello", "hi", "hooblypoob"] + ]; + + let emitStuff = await deployedContract.emitStuff(...emitStuffArgs); + let emitStuffHash = emitStuff.tx; + + let moreStuffArgs = [address, [8, 8, 8, 7, 8, 8, 8]]; + + let moreStuff = await deployedContract.moreStuff(...moreStuffArgs); + let moreStuffHash = moreStuff.tx; + + let constructorTx = await web3.eth.getTransaction(constructorHash); + let emitStuffTx = await web3.eth.getTransaction(emitStuffHash); + let moreStuffTx = await web3.eth.getTransaction(moreStuffHash); + + const decoder = TruffleDecoder.forProject([WireTest], web3.currentProvider); + await decoder.init(); + + let constructorDecoding = (await decoder.decodeTransaction(constructorTx)) + .decoding; + let emitStuffDecoding = (await decoder.decodeTransaction(emitStuffTx)) + .decoding; + let moreStuffDecoding = (await decoder.decodeTransaction(moreStuffTx)) + .decoding; + + assert.strictEqual(constructorDecoding.kind, "constructor"); + assert.strictEqual(constructorDecoding.class.typeName, "WireTest"); + assert.strictEqual(constructorDecoding.arguments.length, 3); + assert.strictEqual(constructorDecoding.arguments[0].name, "status"); + assert.strictEqual(constructorDecoding.arguments[0].value.nativize(), true); + assert.strictEqual(constructorDecoding.arguments[1].name, "info"); + assert.strictEqual( + constructorDecoding.arguments[1].value.nativize(), + "0xdeadbeef" + ); + assert.strictEqual(constructorDecoding.arguments[2].name, "whoknows"); + assert.strictEqual(constructorDecoding.arguments[2].value.nativize(), 2); + + assert.strictEqual(emitStuffDecoding.kind, "function"); + assert.strictEqual(emitStuffDecoding.name, "emitStuff"); + assert.strictEqual(emitStuffDecoding.class.typeName, "WireTest"); + assert.strictEqual(emitStuffDecoding.arguments.length, 3); + assert.strictEqual(emitStuffDecoding.arguments[0].name, "p"); + assert.deepEqual( + emitStuffDecoding.arguments[0].value.nativize(), + emitStuffArgs[0] + ); + assert.strictEqual(emitStuffDecoding.arguments[1].name, "precompiles"); + assert.deepEqual( + emitStuffDecoding.arguments[1].value.nativize(), + emitStuffArgs[1] + ); + assert.strictEqual(emitStuffDecoding.arguments[2].name, "strings"); + assert.deepEqual( + emitStuffDecoding.arguments[2].value.nativize(), + emitStuffArgs[2] + ); + + assert.strictEqual(moreStuffDecoding.kind, "function"); + assert.strictEqual(moreStuffDecoding.name, "moreStuff"); + assert.strictEqual(moreStuffDecoding.class.typeName, "WireTest"); + assert.strictEqual(moreStuffDecoding.arguments.length, 2); + assert.strictEqual(moreStuffDecoding.arguments[0].name, "notThis"); + assert.strictEqual( + moreStuffDecoding.arguments[0].value.nativize(), + `WireTest(${moreStuffArgs[0]})` + ); + assert.strictEqual(moreStuffDecoding.arguments[1].name, "bunchOfInts"); + assert.deepEqual( + moreStuffDecoding.arguments[1].value.nativize(), + moreStuffArgs[1] + ); + }); +}); From b8a80ac31834d3560d7bb078bb6fb6e40a520b9b Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 25 Jun 2019 19:41:31 -0400 Subject: [PATCH 38/89] Fix typo preventing constructor contexts from being added --- packages/truffle-decoder/lib/wire.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index cb507c58397..27880c32a85 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -9,7 +9,7 @@ import { ContractObject } from "truffle-contract-schema/spec"; import BN from "bn.js"; import { Definition as DefinitionUtils, AbiUtils, EVM, AstDefinition, AstReferences } from "truffle-codec-utils"; import { BlockType, Transaction } from "web3/eth/types"; -import { EventLog, Log } from "web3/types"; +import { Log } from "web3/types"; import { Provider } from "web3/providers"; import * as Codec from "truffle-codec"; import * as DecoderTypes from "./types"; @@ -44,6 +44,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { this.contractNodes[node.id] = node; if(contract.deployedBytecode) { const context = Utils.makeContext(contract, node); + debug("context: %O", context); const hash = CodecUtils.Conversion.toHexString( CodecUtils.EVM.keccak256({type: "string", value: context.binary @@ -51,8 +52,9 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { ); this.contexts[hash] = context; } - if(contract.byteCode) { + if(contract.bytecode) { const constructorContext = Utils.makeContext(contract, node, true); + debug("constructorContext: %O", constructorContext); const hash = CodecUtils.Conversion.toHexString( CodecUtils.EVM.keccak256({type: "string", value: constructorContext.binary @@ -64,6 +66,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { } this.contexts = CodecUtils.Contexts.normalizeContexts(this.contexts); + debug("contexts: %O", this.contexts); this.contextsById = Object.assign({}, ...Object.values(this.contexts).filter( ({isConstructor}) => !isConstructor ).map(context => @@ -82,6 +85,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { debug("init called"); [this.referenceDeclarations, this.userDefinedTypes] = this.getUserDefinedTypes(); + debug("ccbyId: %O", this.constructorContextsById); let allocationInfo: Codec.ContractAllocationInfo[] = Object.entries(this.contracts).map( ([id, { abi }]) => ({ abi: abi, @@ -89,6 +93,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { constructorContext: this.constructorContextsById[parseInt(id)] }) ); + debug("allocationInfo: %O", allocationInfo); this.allocations = {}; this.allocations.storage = Codec.getStorageAllocations(this.referenceDeclarations, this.contractNodes); From 8bb910f6db356bdd48c2bc0cbc80b616444c25f8 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 25 Jun 2019 21:34:38 -0400 Subject: [PATCH 39/89] Fix a wire decoder test --- packages/truffle-decoder/test/test/wire-test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index 7dcf4e2088d..0af7412f941 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -56,7 +56,10 @@ contract("WireTest", _accounts => { "0xdeadbeef" ); assert.strictEqual(constructorDecoding.arguments[2].name, "whoknows"); - assert.strictEqual(constructorDecoding.arguments[2].value.nativize(), 2); + assert.strictEqual( + constructorDecoding.arguments[2].value.nativize(), + "WireTest.Ternary.MaybeSo" + ); assert.strictEqual(emitStuffDecoding.kind, "function"); assert.strictEqual(emitStuffDecoding.name, "emitStuff"); From 252b2bda02ee2e471b3d9c654754915dc3d1bd9e Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 26 Jun 2019 16:54:51 -0400 Subject: [PATCH 40/89] Add further tests, fix minor errors --- .../truffle-codec/lib/interface/decoding.ts | 14 +++---- packages/truffle-codec/lib/types/errors.ts | 2 +- packages/truffle-decoder/lib/wire.ts | 1 + .../test/contracts/WireTest.sol | 13 +++++- .../truffle-decoder/test/test/wire-test.js | 41 ++++++++++++++++++- 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 11d61dca2a7..89ac24ba781 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -66,9 +66,9 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator { + debug("transaction: %O", transaction); const block = transaction.blockNumber; const context = await this.getContextByAddress(transaction.to, block, transaction.input); diff --git a/packages/truffle-decoder/test/contracts/WireTest.sol b/packages/truffle-decoder/test/contracts/WireTest.sol index 71c94aa9028..26d31be0e38 100644 --- a/packages/truffle-decoder/test/contracts/WireTest.sol +++ b/packages/truffle-decoder/test/contracts/WireTest.sol @@ -1,7 +1,18 @@ pragma solidity ^0.5.10; pragma experimental ABIEncoderV2; -contract WireTest { +contract WireTestParent { + + event Done(); + + function inherited(uint[2] memory) public { + emit Done(); + } + + //no constructor +} + +contract WireTest is WireTestParent { constructor(bool status, bytes memory info, Ternary whoknows) public { emit ConstructorEvent(status, info, whoknows); } diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index 0af7412f941..844633848b0 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -1,8 +1,10 @@ +const debug = require("debug")("decoder:test:wire-test"); const assert = require("chai").assert; const TruffleDecoder = require("../../../truffle-decoder"); const WireTest = artifacts.require("WireTest"); +const WireTestParent = artifacts.require("WireTestParent"); contract("WireTest", _accounts => { it("should correctly decode transactions and events", async () => { @@ -10,6 +12,9 @@ contract("WireTest", _accounts => { let address = deployedContract.address; let constructorHash = deployedContract.transactionHash; + let deployedContractNoConstructor = await WireTestParent.new(); + let defaultConstructorHash = deployedContractNoConstructor.transactionHash; + let emitStuffArgs = [ { x: -1, @@ -31,11 +36,23 @@ contract("WireTest", _accounts => { let moreStuff = await deployedContract.moreStuff(...moreStuffArgs); let moreStuffHash = moreStuff.tx; + let inheritedArg = [2, 3]; + let inherited = await deployedContract.inherited(inheritedArg); + debug("inherited: %O", inherited); + let inheritedHash = inherited.tx; + let constructorTx = await web3.eth.getTransaction(constructorHash); let emitStuffTx = await web3.eth.getTransaction(emitStuffHash); let moreStuffTx = await web3.eth.getTransaction(moreStuffHash); + let inheritedTx = await web3.eth.getTransaction(inheritedHash); + let defaultConstructorTx = await web3.eth.getTransaction( + defaultConstructorHash + ); - const decoder = TruffleDecoder.forProject([WireTest], web3.currentProvider); + const decoder = TruffleDecoder.forProject( + [WireTest, WireTestParent], + web3.currentProvider + ); await decoder.init(); let constructorDecoding = (await decoder.decodeTransaction(constructorTx)) @@ -44,6 +61,11 @@ contract("WireTest", _accounts => { .decoding; let moreStuffDecoding = (await decoder.decodeTransaction(moreStuffTx)) .decoding; + let inheritedDecoding = (await decoder.decodeTransaction(inheritedTx)) + .decoding; + let defaultConstructorDecoding = (await decoder.decodeTransaction( + defaultConstructorTx + )).decoding; assert.strictEqual(constructorDecoding.kind, "constructor"); assert.strictEqual(constructorDecoding.class.typeName, "WireTest"); @@ -95,5 +117,22 @@ contract("WireTest", _accounts => { moreStuffDecoding.arguments[1].value.nativize(), moreStuffArgs[1] ); + + assert.strictEqual(inheritedDecoding.kind, "function"); + assert.strictEqual(inheritedDecoding.name, "inherited"); + assert.strictEqual(inheritedDecoding.class.typeName, "WireTest"); //NOT WireTestParent + assert.strictEqual(inheritedDecoding.arguments.length, 1); + assert.isUndefined(inheritedDecoding.arguments[0].name); + assert.deepEqual( + inheritedDecoding.arguments[0].value.nativize(), + inheritedArg + ); + + assert.strictEqual(defaultConstructorDecoding.kind, "constructor"); + assert.strictEqual( + defaultConstructorDecoding.class.typeName, + "WireTestParent" + ); + assert.strictEqual(defaultConstructorDecoding.arguments.length, 0); }); }); From 4b8b010f19736b769f84282f129027e6eb091878 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 26 Jun 2019 17:11:45 -0400 Subject: [PATCH 41/89] Correct logic for finding possible contract allocations for events --- .../truffle-codec/lib/interface/decoding.ts | 38 ++++++++++++++----- packages/truffle-decoder/lib/contract.ts | 2 +- packages/truffle-decoder/lib/wire.ts | 2 +- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 89ac24ba781..2137c3a9a24 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -88,8 +88,7 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator { - const compiler = info.currentContext.compiler; +export function* decodeEvent(info: EvmInfo, address: string, targetName: string | null = null): IterableIterator { const allocations = info.allocations.event; let rawSelector: Uint8Array; try { @@ -108,11 +107,30 @@ export function* decodeEvent(info: EvmInfo, targetName: string | null = null): I const topicsCount = info.state.eventtopics.length; //yeah, it's not great to read directly from the state like this (bypassing read), but what are you gonna do? const { contract: contractAllocations, library: libraryAllocations } = allocations[selector][topicsCount]; - //we only want one contract from the contractAllocations -- this one - const contractId = info.currentContext.contractId; - const contractAllocation = contractAllocations[contractId]; - const possibleAllocations: [string, EventAllocation][] = [[contractId.toString(), contractAllocation], ...Object.entries(libraryAllocations)]; + //now: what contract are we (probably) dealing with? let's get its code to find out + const codeBytes: Uint8Array = yield { + type: "code", + address + }; + const codeAsHex = CodecUtils.Conversion.toHexString(codeBytes); + const contractContext = CodecUtils.Contexts.findDecoderContext(info.contexts, codeAsHex); + let possibleContractAllocations: [string, EventAllocation][]; //should be number, but we have to temporarily pass through string to get compilation to work... + //(these are ID/allocation pairs) + if(contractContext) { + //if we found the contract, maybe it's from that contract + const contractId = contractContext.contractId; + const contractAllocation = contractAllocations[contractId]; + possibleContractAllocations = contractAllocation + ? [[contractId.toString(), contractAllocation]] //array of a single pair + : []; + } + else { + //if we couldn't determine the contract, well, we have to assume it's from a library + possibleContractAllocations = []; + } + //now we add in all the library allocations! + const possibleAllocations = [...possibleContractAllocations, ...Object.entries(libraryAllocations)]; let decodings: EventDecoding[] = []; for(const [id, allocation] of possibleAllocations) { try { @@ -120,14 +138,14 @@ export function* decodeEvent(info: EvmInfo, targetName: string | null = null): I if(targetName !== null && allocation.definition.name !== targetName) { continue; } - const context = info.contexts[parseInt(id)]; - const contractType = CodecUtils.Contexts.contextToType(context); - const newInfo = { ...info, currentContext: context }; + const attemptContext = info.contexts[parseInt(id)]; + const contractType = CodecUtils.Contexts.contextToType(attemptContext); + const newInfo = { ...info, currentContext: attemptContext }; //you can't map with a generator, so we have to do this map manually let decodedArguments: AbiArgument[] = []; for(const argumentAllocation of allocation.arguments) { const value = (yield* decode( - Types.definitionToType(argumentAllocation.definition, compiler), + Types.definitionToType(argumentAllocation.definition, attemptContext.compiler), argumentAllocation.pointer, newInfo, 0, //offset is always 0 for events diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index a85eb28df33..bb3aa83e964 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -427,7 +427,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contextsById }; - const decoder = Codec.decodeEvent(info, name); + const decoder = Codec.decodeEvent(info, log.address, name); let result = decoder.next(); while(!result.done) { diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 5f88d0388b8..a8b7c2198e6 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -196,7 +196,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contextsById }; - const decoder = Codec.decodeEvent(info, name); + const decoder = Codec.decodeEvent(info, log.address, name); let result = decoder.next(); while(!result.done) { From dbe6e33f33f6c33911ba0967ed957643f4924fdc Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 26 Jun 2019 20:51:32 -0400 Subject: [PATCH 42/89] Fix incorrect encoding of dynamic arrays --- packages/truffle-codec/lib/encode/abi.ts | 1 + .../truffle-codec/lib/interface/decoding.ts | 2 + packages/truffle-decoder/lib/wire.ts | 1 + .../test/contracts/WireTest.sol | 6 +- .../truffle-decoder/test/test/wire-test.js | 141 +++++++++++++++++- 5 files changed, 142 insertions(+), 9 deletions(-) diff --git a/packages/truffle-codec/lib/encode/abi.ts b/packages/truffle-codec/lib/encode/abi.ts index ea6121e42f3..e96dd9e2cb5 100644 --- a/packages/truffle-codec/lib/encode/abi.ts +++ b/packages/truffle-codec/lib/encode/abi.ts @@ -80,6 +80,7 @@ export function encodeAbi(input: Values.Result, allocations?: AbiAllocations): U return staticEncoding; case "dynamic": let encoded = new Uint8Array(EVMUtils.WORD_SIZE + staticEncoding.length); //leave room for length + encoded.set(staticEncoding, EVMUtils.WORD_SIZE); //again, leave room for length beforehand let lengthBytes = ConversionUtils.toBytes(input.value.length, EVMUtils.WORD_SIZE); encoded.set(lengthBytes); //and now we set the length return encoded; diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 2137c3a9a24..15054ce10ce 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -158,6 +158,7 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName: string : { value } ); } + debug("decodedArguments: %O", decodedArguments); //OK, so, having decoded the result, the question is: does it reencode to the original? //first, we have to filter out the indexed arguments, and also get rid of the name information const nonIndexedValues = decodedArguments.filter( @@ -166,6 +167,7 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName: string ({value}) => value ); //now, we can encode! + debug("nonIndexedValues: %O", nonIndexedValues); const reEncodedData = encodeTupleAbi(nonIndexedValues, info.allocations.abi); //are they equal? const encodedData = info.state.eventdata; //again, not great to read this directly, but oh well diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index a8b7c2198e6..4967ad4bb27 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -228,6 +228,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }); let events = await this.decodeLogs(logs, name); + debug("events: %o", events); //if a target name was specified, we'll restrict to events that decoded //to something with that name. (note that only decodings with that name diff --git a/packages/truffle-decoder/test/contracts/WireTest.sol b/packages/truffle-decoder/test/contracts/WireTest.sol index 26d31be0e38..600fe7c4269 100644 --- a/packages/truffle-decoder/test/contracts/WireTest.sol +++ b/packages/truffle-decoder/test/contracts/WireTest.sol @@ -17,7 +17,7 @@ contract WireTest is WireTestParent { emit ConstructorEvent(status, info, whoknows); } - event ConstructorEvent(bool, bytes, Ternary); + event ConstructorEvent(bool bit, bytes, Ternary); struct Triple { int x; @@ -41,11 +41,9 @@ contract WireTest is WireTestParent { emit EmitStuff(p, precompiles, strings); } - event MoreStuff(WireTest, uint[]); + event MoreStuff(WireTest, uint[] data); function moreStuff(WireTest notThis, uint[] memory bunchOfInts) public { emit MoreStuff(notThis, bunchOfInts); } } - -//TODO: add tests of library events & ambiguity diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index 844633848b0..aa03243bb6f 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -69,7 +69,7 @@ contract("WireTest", _accounts => { assert.strictEqual(constructorDecoding.kind, "constructor"); assert.strictEqual(constructorDecoding.class.typeName, "WireTest"); - assert.strictEqual(constructorDecoding.arguments.length, 3); + assert.lengthOf(constructorDecoding.arguments, 3); assert.strictEqual(constructorDecoding.arguments[0].name, "status"); assert.strictEqual(constructorDecoding.arguments[0].value.nativize(), true); assert.strictEqual(constructorDecoding.arguments[1].name, "info"); @@ -86,7 +86,7 @@ contract("WireTest", _accounts => { assert.strictEqual(emitStuffDecoding.kind, "function"); assert.strictEqual(emitStuffDecoding.name, "emitStuff"); assert.strictEqual(emitStuffDecoding.class.typeName, "WireTest"); - assert.strictEqual(emitStuffDecoding.arguments.length, 3); + assert.lengthOf(emitStuffDecoding.arguments, 3); assert.strictEqual(emitStuffDecoding.arguments[0].name, "p"); assert.deepEqual( emitStuffDecoding.arguments[0].value.nativize(), @@ -106,7 +106,7 @@ contract("WireTest", _accounts => { assert.strictEqual(moreStuffDecoding.kind, "function"); assert.strictEqual(moreStuffDecoding.name, "moreStuff"); assert.strictEqual(moreStuffDecoding.class.typeName, "WireTest"); - assert.strictEqual(moreStuffDecoding.arguments.length, 2); + assert.lengthOf(moreStuffDecoding.arguments, 2); assert.strictEqual(moreStuffDecoding.arguments[0].name, "notThis"); assert.strictEqual( moreStuffDecoding.arguments[0].value.nativize(), @@ -121,7 +121,7 @@ contract("WireTest", _accounts => { assert.strictEqual(inheritedDecoding.kind, "function"); assert.strictEqual(inheritedDecoding.name, "inherited"); assert.strictEqual(inheritedDecoding.class.typeName, "WireTest"); //NOT WireTestParent - assert.strictEqual(inheritedDecoding.arguments.length, 1); + assert.lengthOf(inheritedDecoding.arguments, 1); assert.isUndefined(inheritedDecoding.arguments[0].name); assert.deepEqual( inheritedDecoding.arguments[0].value.nativize(), @@ -133,6 +133,137 @@ contract("WireTest", _accounts => { defaultConstructorDecoding.class.typeName, "WireTestParent" ); - assert.strictEqual(defaultConstructorDecoding.arguments.length, 0); + assert.isEmpty(defaultConstructorDecoding.arguments); + + //now for events! + let constructorBlock = constructorTx.blockNumber; + let emitStuffBlock = emitStuffTx.blockNumber; + let moreStuffBlock = moreStuffTx.blockNumber; + let inheritedBlock = inherited.blocknumber; + + /* + let danger = await deployedContract.danger(); + let dangerBlock = danger.blockNumber; + */ + + let constructorEvents = await decoder.events( + null, + constructorBlock, + constructorBlock + ); + let emitStuffEvents = await decoder.events( + null, + emitStuffBlock, + emitStuffBlock + ); + let moreStuffEvents = await decoder.events( + null, + moreStuffBlock, + moreStuffBlock + ); + let inheritedEvents = await decoder.events( + null, + inheritedBlock, + inheritedBlock + ); + //let dangerEvents = await decoder.events(null, dangerBlock, dangerBlock); + + assert.lengthOf(constructorEvents, 1); + let constructorEventDecodings = constructorEvents[0].decodings; + assert.lengthOf(constructorEventDecodings, 1); + let constructorEventDecoding = constructorEventDecodings[0]; + + assert.lengthOf(emitStuffEvents, 1); + let emitStuffEventDecodings = emitStuffEvents[0].decodings; + assert.lengthOf(emitStuffEventDecodings, 1); + let emitStuffEventDecoding = emitStuffEventDecodings[0]; + + assert.lengthOf(moreStuffEvents, 1); + let moreStuffEventDecodings = moreStuffEvents[0].decodings; + assert.lengthOf(moreStuffEventDecodings, 1); + let moreStuffEventDecoding = moreStuffEventDecodings[0]; + + assert.lengthOf(inheritedEvents, 1); + let inheritedEventDecodings = inheritedEvents[0].decodings; + assert.lengthOf(inheritedEventDecodings, 1); + let inheritedEventDecoding = inheritedEventDecodings[0]; + + /* + assert.lengthOf(dangerEvents, 1); + let dangerEventDecodings = dangerEvents[0].decodings; + assert.lengthOf(dangerEventDecodings, 1); + let dangerEventDecoding = dangerEventDecodings[0]; + */ + + assert.strictEqual(constructorEventDecoding.kind, "event"); + assert.strictEqual(constructorEventDecoding.class.typeName, "WireTest"); + assert.strictEqual(constructorEventDecoding.name, "ConstructorEvent"); + assert.lengthOf(constructorEventDecoding.arguments, 3); + assert.strictEqual(constructorEventDecoding.arguments[0].name, "bit"); + assert.strictEqual( + constructorEventDecoding.arguments[0].value.nativize(), + true + ); + assert.isUndefined(constructorEventDecoding.arguments[1].name); + assert.strictEqual( + constructorEventDecoding.arguments[1].value.nativize(), + "0xdeadbeef" + ); + assert.isUndefined(constructorEventDecoding.arguments[2].name); + assert.strictEqual( + constructorEventDecoding.arguments[2].value.nativize(), + "WireTest.Ternary.MaybeSo" + ); + + assert.strictEqual(emitStuffEventDecoding.kind, "event"); + assert.strictEqual(emitStuffEventDecoding.name, "EmitStuff"); + assert.strictEqual(emitStuffEventDecoding.class.typeName, "WireTest"); + assert.lengthOf(emitStuffEventDecoding.arguments, 3); + assert.isUndefined(emitStuffEventDecoding.arguments[0].name); + assert.deepEqual( + emitStuffEventDecoding.arguments[0].value.nativize(), + emitStuffArgs[0] + ); + assert.isUndefined(emitStuffEventDecoding.arguments[1].name); + assert.deepEqual( + emitStuffEventDecoding.arguments[1].value.nativize(), + emitStuffArgs[1] + ); + assert.isUndefined(emitStuffEventDecoding.arguments[2].name); + assert.deepEqual( + emitStuffEventDecoding.arguments[2].value.nativize(), + emitStuffArgs[2] + ); + + assert.strictEqual(moreStuffEventDecoding.kind, "event"); + assert.strictEqual(moreStuffEventDecoding.name, "MoreStuff"); + assert.strictEqual(moreStuffEventDecoding.class.typeName, "WireTest"); + assert.lengthOf(moreStuffEventDecoding.arguments, 2); + assert.isUndefined(moreStuffEventDecoding.arguments[0].name); + assert.strictEqual( + moreStuffEventDecoding.arguments[0].value.nativize(), + `WireTest(${moreStuffArgs[0]})` + ); + assert.strictEqual(moreStuffEventDecoding.arguments[1].name, "data"); + assert.deepEqual( + moreStuffEventDecoding.arguments[1].value.nativize(), + moreStuffArgs[1] + ); + + assert.strictEqual(inheritedEventDecoding.kind, "event"); + assert.strictEqual(inheritedEventDecoding.name, "Done"); + assert.strictEqual(inheritedEventDecoding.class.typeName, "WireTest"); //NOT WireTestParent + assert.isEmpty(inheritedEventDecoding.arguments); + + /* + assert.strictEqual(dangerEventDecoding.kind, "event"); + assert.strictEqual(dangerEventDecoding.name, "Danger"); + assert.lengthOf(dangerEventDecoding.arguments, 1); + assert.isUndefined(dangerEventDecoding.arguments[0].name); + assert.strictEqual( + dangerEventDecoding.arguments[0].value.nativize(), + `WireTest(${address}).danger` + ); + */ }); }); From 9b41d4eafd48bc033193c52142629475dfc9e848 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 26 Jun 2019 21:12:42 -0400 Subject: [PATCH 43/89] Remove commented-out test --- .../test/contracts/WireTest.sol | 2 ++ .../truffle-decoder/test/test/wire-test.js | 24 ------------------- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/packages/truffle-decoder/test/contracts/WireTest.sol b/packages/truffle-decoder/test/contracts/WireTest.sol index 600fe7c4269..7602a1751f5 100644 --- a/packages/truffle-decoder/test/contracts/WireTest.sol +++ b/packages/truffle-decoder/test/contracts/WireTest.sol @@ -31,6 +31,8 @@ contract WireTest is WireTestParent { event Danger(function() external); + //currently omitted from tests due to having to deal with + //ethers's crappy decoder function danger() public { emit Danger(this.danger); } diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index aa03243bb6f..15fc765aeca 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -141,11 +141,6 @@ contract("WireTest", _accounts => { let moreStuffBlock = moreStuffTx.blockNumber; let inheritedBlock = inherited.blocknumber; - /* - let danger = await deployedContract.danger(); - let dangerBlock = danger.blockNumber; - */ - let constructorEvents = await decoder.events( null, constructorBlock, @@ -166,7 +161,6 @@ contract("WireTest", _accounts => { inheritedBlock, inheritedBlock ); - //let dangerEvents = await decoder.events(null, dangerBlock, dangerBlock); assert.lengthOf(constructorEvents, 1); let constructorEventDecodings = constructorEvents[0].decodings; @@ -188,13 +182,6 @@ contract("WireTest", _accounts => { assert.lengthOf(inheritedEventDecodings, 1); let inheritedEventDecoding = inheritedEventDecodings[0]; - /* - assert.lengthOf(dangerEvents, 1); - let dangerEventDecodings = dangerEvents[0].decodings; - assert.lengthOf(dangerEventDecodings, 1); - let dangerEventDecoding = dangerEventDecodings[0]; - */ - assert.strictEqual(constructorEventDecoding.kind, "event"); assert.strictEqual(constructorEventDecoding.class.typeName, "WireTest"); assert.strictEqual(constructorEventDecoding.name, "ConstructorEvent"); @@ -254,16 +241,5 @@ contract("WireTest", _accounts => { assert.strictEqual(inheritedEventDecoding.name, "Done"); assert.strictEqual(inheritedEventDecoding.class.typeName, "WireTest"); //NOT WireTestParent assert.isEmpty(inheritedEventDecoding.arguments); - - /* - assert.strictEqual(dangerEventDecoding.kind, "event"); - assert.strictEqual(dangerEventDecoding.name, "Danger"); - assert.lengthOf(dangerEventDecoding.arguments, 1); - assert.isUndefined(dangerEventDecoding.arguments[0].name); - assert.strictEqual( - dangerEventDecoding.arguments[0].value.nativize(), - `WireTest(${address}).danger` - ); - */ }); }); From e1da2c54bc39bd127bcec5e972c5a575ad6955bd Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 26 Jun 2019 21:43:42 -0400 Subject: [PATCH 44/89] Add test of indexed parameters --- .../test/contracts/WireTest.sol | 6 +++ .../truffle-decoder/test/test/wire-test.js | 50 +++++++++++++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/truffle-decoder/test/contracts/WireTest.sol b/packages/truffle-decoder/test/contracts/WireTest.sol index 7602a1751f5..956c701a617 100644 --- a/packages/truffle-decoder/test/contracts/WireTest.sol +++ b/packages/truffle-decoder/test/contracts/WireTest.sol @@ -48,4 +48,10 @@ contract WireTest is WireTestParent { function moreStuff(WireTest notThis, uint[] memory bunchOfInts) public { emit MoreStuff(notThis, bunchOfInts); } + + event HasIndices(uint, uint indexed, string, string indexed, uint); + + function indexTest(uint a, uint b, string memory c, string memory d, uint e) public { + emit HasIndices(a, b, c, d, e); + } } diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index 15fc765aeca..9e99c57223b 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -41,6 +41,9 @@ contract("WireTest", _accounts => { debug("inherited: %O", inherited); let inheritedHash = inherited.tx; + let indexTestArgs = [7, 89, "hello", "indecipherable", 62]; + let indexTest = await deployedContract.indexTest(...indexTestArgs); + let constructorTx = await web3.eth.getTransaction(constructorHash); let emitStuffTx = await web3.eth.getTransaction(emitStuffHash); let moreStuffTx = await web3.eth.getTransaction(moreStuffHash); @@ -137,9 +140,10 @@ contract("WireTest", _accounts => { //now for events! let constructorBlock = constructorTx.blockNumber; - let emitStuffBlock = emitStuffTx.blockNumber; - let moreStuffBlock = moreStuffTx.blockNumber; - let inheritedBlock = inherited.blocknumber; + let emitStuffBlock = emitStuff.receipt.blockNumber; + let moreStuffBlock = moreStuff.receipt.blockNumber; + let inheritedBlock = inherited.receipt.blockNumber; + let indexTestBlock = indexTest.receipt.blockNumber; let constructorEvents = await decoder.events( null, @@ -161,6 +165,11 @@ contract("WireTest", _accounts => { inheritedBlock, inheritedBlock ); + let indexTestEvents = await decoder.events( + null, + indexTestBlock, + indexTestBlock + ); assert.lengthOf(constructorEvents, 1); let constructorEventDecodings = constructorEvents[0].decodings; @@ -182,6 +191,11 @@ contract("WireTest", _accounts => { assert.lengthOf(inheritedEventDecodings, 1); let inheritedEventDecoding = inheritedEventDecodings[0]; + assert.lengthOf(indexTestEvents, 1); + let indexTestEventDecodings = indexTestEvents[0].decodings; + assert.lengthOf(indexTestEventDecodings, 1); + let indexTestEventDecoding = indexTestEventDecodings[0]; + assert.strictEqual(constructorEventDecoding.kind, "event"); assert.strictEqual(constructorEventDecoding.class.typeName, "WireTest"); assert.strictEqual(constructorEventDecoding.name, "ConstructorEvent"); @@ -241,5 +255,35 @@ contract("WireTest", _accounts => { assert.strictEqual(inheritedEventDecoding.name, "Done"); assert.strictEqual(inheritedEventDecoding.class.typeName, "WireTest"); //NOT WireTestParent assert.isEmpty(inheritedEventDecoding.arguments); + + assert.strictEqual(indexTestEventDecoding.kind, "event"); + assert.strictEqual(indexTestEventDecoding.name, "HasIndices"); + assert.strictEqual(indexTestEventDecoding.class.typeName, "WireTest"); + assert.lengthOf(indexTestEventDecoding.arguments, 5); + assert.isUndefined(indexTestEventDecoding.arguments[0].name); + assert.strictEqual( + indexTestEventDecoding.arguments[0].value.nativize(), + indexTestArgs[0] + ); + assert.isUndefined(indexTestEventDecoding.arguments[1].name); + assert.deepEqual( + indexTestEventDecoding.arguments[1].value.nativize(), + indexTestArgs[1] + ); + assert.isUndefined(indexTestEventDecoding.arguments[2].name); + assert.deepEqual( + indexTestEventDecoding.arguments[2].value.nativize(), + indexTestArgs[2] + ); + assert.isUndefined(indexTestEventDecoding.arguments[3].name); + assert.isUndefined( + indexTestEventDecoding.arguments[3].value.nativize(), //can't decode indexed reference type! + indexTestArgs[3] + ); + assert.isUndefined(indexTestEventDecoding.arguments[4].name); + assert.deepEqual( + indexTestEventDecoding.arguments[4].value.nativize(), + indexTestArgs[4] + ); }); }); From e31273a361a38939aafa227fb327156996b0eba6 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 26 Jun 2019 21:50:13 -0400 Subject: [PATCH 45/89] Revert "Remove commented-out test" and uncomment the test! This reverts commit 9b41d4eafd48bc033193c52142629475dfc9e848. In addition, it uncomments the "danger" test and gets it working. --- .../test/contracts/WireTest.sol | 2 -- .../truffle-decoder/test/test/wire-test.js | 25 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/truffle-decoder/test/contracts/WireTest.sol b/packages/truffle-decoder/test/contracts/WireTest.sol index 956c701a617..74ccc67d5d2 100644 --- a/packages/truffle-decoder/test/contracts/WireTest.sol +++ b/packages/truffle-decoder/test/contracts/WireTest.sol @@ -31,8 +31,6 @@ contract WireTest is WireTestParent { event Danger(function() external); - //currently omitted from tests due to having to deal with - //ethers's crappy decoder function danger() public { emit Danger(this.danger); } diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index 9e99c57223b..69d911a1e78 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -145,6 +145,14 @@ contract("WireTest", _accounts => { let inheritedBlock = inherited.receipt.blockNumber; let indexTestBlock = indexTest.receipt.blockNumber; + try { + //due to web3's having ethers's crappy decoder built in, + //we have to put this in a try block to catch the error + await deployedContract.danger(); + } catch (_) { + //discard the error! + } + let constructorEvents = await decoder.events( null, constructorBlock, @@ -170,6 +178,9 @@ contract("WireTest", _accounts => { indexTestBlock, indexTestBlock ); + //HACK -- since danger was last, we can just ask for the + //events from the latest block + let dangerEvents = await decoder.events(); assert.lengthOf(constructorEvents, 1); let constructorEventDecodings = constructorEvents[0].decodings; @@ -196,6 +207,11 @@ contract("WireTest", _accounts => { assert.lengthOf(indexTestEventDecodings, 1); let indexTestEventDecoding = indexTestEventDecodings[0]; + assert.lengthOf(dangerEvents, 1); + let dangerEventDecodings = dangerEvents[0].decodings; + assert.lengthOf(dangerEventDecodings, 1); + let dangerEventDecoding = dangerEventDecodings[0]; + assert.strictEqual(constructorEventDecoding.kind, "event"); assert.strictEqual(constructorEventDecoding.class.typeName, "WireTest"); assert.strictEqual(constructorEventDecoding.name, "ConstructorEvent"); @@ -285,5 +301,14 @@ contract("WireTest", _accounts => { indexTestEventDecoding.arguments[4].value.nativize(), indexTestArgs[4] ); + + assert.strictEqual(dangerEventDecoding.kind, "event"); + assert.strictEqual(dangerEventDecoding.name, "Danger"); + assert.lengthOf(dangerEventDecoding.arguments, 1); + assert.isUndefined(dangerEventDecoding.arguments[0].name); + assert.strictEqual( + dangerEventDecoding.arguments[0].value.nativize(), + `WireTest(${address}).danger` + ); }); }); From 038f3a4992cf6079b249bb0b9a7b0dde8be63aa8 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 26 Jun 2019 22:20:27 -0400 Subject: [PATCH 46/89] Add test of library event --- .../truffle-codec/lib/interface/decoding.ts | 1 + .../test/contracts/WireTest.sol | 12 ++++++++ .../test/migrations/2_deploy_contracts.js | 7 +++-- .../truffle-decoder/test/test/wire-test.js | 30 ++++++++++++++++++- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 15054ce10ce..d63d8de3a7d 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -90,6 +90,7 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator { const allocations = info.allocations.event; + debug("event allocations: %O", allocations); let rawSelector: Uint8Array; try { rawSelector = read( diff --git a/packages/truffle-decoder/test/contracts/WireTest.sol b/packages/truffle-decoder/test/contracts/WireTest.sol index 74ccc67d5d2..1cbdb89599c 100644 --- a/packages/truffle-decoder/test/contracts/WireTest.sol +++ b/packages/truffle-decoder/test/contracts/WireTest.sol @@ -52,4 +52,16 @@ contract WireTest is WireTestParent { function indexTest(uint a, uint b, string memory c, string memory d, uint e) public { emit HasIndices(a, b, c, d, e); } + + function libraryTest(string memory it) public { + WireTestLibrary.emitEvent(it); + } +} + +library WireTestLibrary { + event LibraryEvent(string); + + function emitEvent(string calldata it) external { + emit LibraryEvent(it); + } } diff --git a/packages/truffle-decoder/test/migrations/2_deploy_contracts.js b/packages/truffle-decoder/test/migrations/2_deploy_contracts.js index cbf777482f6..9f0f6a5c461 100644 --- a/packages/truffle-decoder/test/migrations/2_deploy_contracts.js +++ b/packages/truffle-decoder/test/migrations/2_deploy_contracts.js @@ -1,6 +1,9 @@ -const DecodingSample = artifacts.require("DecodingSample.sol"); -//we don't deploy WireTest because we're going to use WireTest.new for that +const DecodingSample = artifacts.require("DecodingSample"); +const WireTest = artifacts.require("WireTest"); +const WireTestLibrary = artifacts.require("WireTestLibrary"); module.exports = function(deployer) { deployer.deploy(DecodingSample); + deployer.deploy(WireTestLibrary); + deployer.link(WireTestLibrary, WireTest); }; diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index 69d911a1e78..e7657e6e74b 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -5,6 +5,7 @@ const TruffleDecoder = require("../../../truffle-decoder"); const WireTest = artifacts.require("WireTest"); const WireTestParent = artifacts.require("WireTestParent"); +const WireTestLibrary = artifacts.require("WireTestLibrary"); contract("WireTest", _accounts => { it("should correctly decode transactions and events", async () => { @@ -44,6 +45,9 @@ contract("WireTest", _accounts => { let indexTestArgs = [7, 89, "hello", "indecipherable", 62]; let indexTest = await deployedContract.indexTest(...indexTestArgs); + let libraryTestArg = "zooglyzooglyzooglyzoogly"; + let libraryTest = await deployedContract.libraryTest(libraryTestArg); + let constructorTx = await web3.eth.getTransaction(constructorHash); let emitStuffTx = await web3.eth.getTransaction(emitStuffHash); let moreStuffTx = await web3.eth.getTransaction(moreStuffHash); @@ -53,7 +57,7 @@ contract("WireTest", _accounts => { ); const decoder = TruffleDecoder.forProject( - [WireTest, WireTestParent], + [WireTest, WireTestParent, WireTestLibrary], web3.currentProvider ); await decoder.init(); @@ -144,6 +148,7 @@ contract("WireTest", _accounts => { let moreStuffBlock = moreStuff.receipt.blockNumber; let inheritedBlock = inherited.receipt.blockNumber; let indexTestBlock = indexTest.receipt.blockNumber; + let libraryTestBlock = libraryTest.receipt.blockNumber; try { //due to web3's having ethers's crappy decoder built in, @@ -178,6 +183,11 @@ contract("WireTest", _accounts => { indexTestBlock, indexTestBlock ); + let libraryTestEvents = await decoder.events( + null, + libraryTestBlock, + libraryTestBlock + ); //HACK -- since danger was last, we can just ask for the //events from the latest block let dangerEvents = await decoder.events(); @@ -207,6 +217,11 @@ contract("WireTest", _accounts => { assert.lengthOf(indexTestEventDecodings, 1); let indexTestEventDecoding = indexTestEventDecodings[0]; + assert.lengthOf(libraryTestEvents, 1); + let libraryTestEventDecodings = libraryTestEvents[0].decodings; + assert.lengthOf(libraryTestEventDecodings, 1); + let libraryTestEventDecoding = libraryTestEventDecodings[0]; + assert.lengthOf(dangerEvents, 1); let dangerEventDecodings = dangerEvents[0].decodings; assert.lengthOf(dangerEventDecodings, 1); @@ -302,6 +317,19 @@ contract("WireTest", _accounts => { indexTestArgs[4] ); + assert.strictEqual(libraryTestEventDecoding.kind, "event"); + assert.strictEqual(libraryTestEventDecoding.name, "LibraryEvent"); + assert.strictEqual( + libraryTestEventDecoding.class.typeName, + "WireTestLibrary" + ); + assert.lengthOf(libraryTestEventDecoding.arguments, 1); + assert.isUndefined(libraryTestEventDecoding.arguments[0].name); + assert.strictEqual( + libraryTestEventDecoding.arguments[0].value.nativize(), + libraryTestArg + ); + assert.strictEqual(dangerEventDecoding.kind, "event"); assert.strictEqual(dangerEventDecoding.name, "Danger"); assert.lengthOf(dangerEventDecoding.arguments, 1); From 4c7936242967d0abed3cdaf43d2bde045c947acf Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 27 Jun 2019 00:02:51 -0400 Subject: [PATCH 47/89] Add tests of maybe-but-not-really unambiguous events --- .../truffle-codec/lib/interface/decoding.ts | 2 +- .../test/contracts/WireTest.sol | 66 ++++++++- .../test/migrations/2_deploy_contracts.js | 1 + .../truffle-decoder/test/test/wire-test.js | 130 +++++++++++++++++- 4 files changed, 185 insertions(+), 14 deletions(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index d63d8de3a7d..52d99528415 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -1,5 +1,5 @@ import debugModule from "debug"; -const debug = debugModule("codec:interface"); +const debug = debugModule("codec:interface:decoding"); import { AstDefinition, Types, Values } from "truffle-codec-utils"; import * as CodecUtils from "truffle-codec-utils"; diff --git a/packages/truffle-decoder/test/contracts/WireTest.sol b/packages/truffle-decoder/test/contracts/WireTest.sol index 1cbdb89599c..a2436c03017 100644 --- a/packages/truffle-decoder/test/contracts/WireTest.sol +++ b/packages/truffle-decoder/test/contracts/WireTest.sol @@ -29,12 +29,6 @@ contract WireTest is WireTestParent { Yes, No, MaybeSo } - event Danger(function() external); - - function danger() public { - emit Danger(this.danger); - } - event EmitStuff(Triple, address[2], string[]); function emitStuff(Triple memory p, address[2] memory precompiles, string[] memory strings) public { @@ -47,6 +41,12 @@ contract WireTest is WireTestParent { emit MoreStuff(notThis, bunchOfInts); } + event Danger(function() external); + + function danger() public { + emit Danger(this.danger); + } + event HasIndices(uint, uint indexed, string, string indexed, uint); function indexTest(uint a, uint b, string memory c, string memory d, uint e) public { @@ -56,6 +56,51 @@ contract WireTest is WireTestParent { function libraryTest(string memory it) public { WireTestLibrary.emitEvent(it); } + + event AmbiguousEvent(uint8[] indexed, uint[5]); + + function ambiguityTest() public { + uint8[] memory short = new uint8[](3); + uint[5] memory long; + long[0] = 32; + long[1] = 3; + long[2] = short[0] = 17; + long[3] = short[1] = 18; + long[4] = short[2] = 19; + emit AmbiguousEvent(short, long); + } + + function unambiguityTest() public { + uint8[] memory empty; + uint[5] memory tooLong; + //array length too long + tooLong[0] = 32; + tooLong[1] = 1e12; //still small enough for JS :) + tooLong[2] = 17; + tooLong[3] = 18; + tooLong[4] = 19; + emit AmbiguousEvent(empty, tooLong); + + //bad padding + uint[5] memory badPadding; + badPadding[0] = 32; + badPadding[1] = 3; + badPadding[2] = 257; + badPadding[3] = 257; + badPadding[4] = 257; + emit AmbiguousEvent(empty, badPadding); + + //decodes, but fails re-encode + uint[5] memory nonStrict; + nonStrict[0] = 64; + nonStrict[1] = 0; + nonStrict[2] = 2; + nonStrict[3] = 1; + nonStrict[4] = 1; + emit AmbiguousEvent(empty, nonStrict); + + WireTestLibrary.emitUnambiguousEvent(); + } } library WireTestLibrary { @@ -64,4 +109,13 @@ library WireTestLibrary { function emitEvent(string calldata it) external { emit LibraryEvent(it); } + + event AmbiguousEvent(uint8[], uint[5] indexed); + + function emitUnambiguousEvent() external { + uint8[] memory wrongLength = new uint8[](1); + wrongLength[0] = 107; + uint[5] memory allZeroes; + emit AmbiguousEvent(wrongLength, allZeroes); + } } diff --git a/packages/truffle-decoder/test/migrations/2_deploy_contracts.js b/packages/truffle-decoder/test/migrations/2_deploy_contracts.js index 9f0f6a5c461..f980564cdb3 100644 --- a/packages/truffle-decoder/test/migrations/2_deploy_contracts.js +++ b/packages/truffle-decoder/test/migrations/2_deploy_contracts.js @@ -6,4 +6,5 @@ module.exports = function(deployer) { deployer.deploy(DecodingSample); deployer.deploy(WireTestLibrary); deployer.link(WireTestLibrary, WireTest); + deployer.deploy(WireTest, false, "0x", 0); }; diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index e7657e6e74b..98db9c78347 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -13,6 +13,12 @@ contract("WireTest", _accounts => { let address = deployedContract.address; let constructorHash = deployedContract.transactionHash; + const decoder = TruffleDecoder.forProject( + [WireTest, WireTestParent, WireTestLibrary], + web3.currentProvider + ); + await decoder.init(); + let deployedContractNoConstructor = await WireTestParent.new(); let defaultConstructorHash = deployedContractNoConstructor.transactionHash; @@ -39,7 +45,6 @@ contract("WireTest", _accounts => { let inheritedArg = [2, 3]; let inherited = await deployedContract.inherited(inheritedArg); - debug("inherited: %O", inherited); let inheritedHash = inherited.tx; let indexTestArgs = [7, 89, "hello", "indecipherable", 62]; @@ -56,12 +61,6 @@ contract("WireTest", _accounts => { defaultConstructorHash ); - const decoder = TruffleDecoder.forProject( - [WireTest, WireTestParent, WireTestLibrary], - web3.currentProvider - ); - await decoder.init(); - let constructorDecoding = (await decoder.decodeTransaction(constructorTx)) .decoding; let emitStuffDecoding = (await decoder.decodeTransaction(emitStuffTx)) @@ -339,4 +338,121 @@ contract("WireTest", _accounts => { `WireTest(${address}).danger` ); }); + + it("disambiguates events when possible and not when impossible", async () => { + let deployedContract = await WireTest.deployed(); + + const decoder = TruffleDecoder.forProject( + [WireTest, WireTestParent, WireTestLibrary], + web3.currentProvider + ); + await decoder.init(); + + //HACK HACK -- we're going to repeatedly apply the hack from above + //because ethers also can't handle ambiguous events + try { + await deployedContract.ambiguityTest(); + } catch (_) { + //discard the error! + } + let ambiguityTestEvents = await decoder.events(); + try { + await deployedContract.unambiguityTest(); + } catch (_) { + //discard the error! + } + let unambiguityTestEvents = await decoder.events(); + + assert.lengthOf(ambiguityTestEvents, 1); + let ambiguityTestEventDecodings = ambiguityTestEvents[0].decodings; + assert.lengthOf(ambiguityTestEventDecodings, 2); //it's ambiguous! + //contract should always come before libraries + let ambiguityTestContractDecoding = ambiguityTestEventDecodings[0]; + let ambiguityTestLibraryDecoding = ambiguityTestEventDecodings[1]; + + assert.lengthOf(unambiguityTestEvents, 4); + for (let event of unambiguityTestEvents) { + assert.lengthOf(event.decodings, 1); //they're unambiguous! + } + let unambiguousDecodings = unambiguityTestEvents.map( + ({ decodings }) => decodings[0] + ); + assert.strictEqual(ambiguityTestContractDecoding.kind, "event"); + assert.strictEqual(ambiguityTestContractDecoding.name, "AmbiguousEvent"); + assert.strictEqual( + ambiguityTestContractDecoding.class.typeName, + "WireTest" + ); + assert.lengthOf(ambiguityTestContractDecoding.arguments, 2); + assert.isUndefined( + ambiguityTestContractDecoding.arguments[0].value.nativize() + ); + assert.deepEqual( + ambiguityTestContractDecoding.arguments[1].value.nativize(), + [32, 3, 17, 18, 19] + ); + + assert.strictEqual(ambiguityTestLibraryDecoding.kind, "event"); + assert.strictEqual(ambiguityTestLibraryDecoding.name, "AmbiguousEvent"); + assert.strictEqual( + ambiguityTestLibraryDecoding.class.typeName, + "WireTestLibrary" + ); + assert.lengthOf(ambiguityTestLibraryDecoding.arguments, 2); + assert.deepEqual( + ambiguityTestLibraryDecoding.arguments[0].value.nativize(), + [17, 18, 19] + ); + assert.isUndefined( + ambiguityTestLibraryDecoding.arguments[1].value.nativize() + ); + + for (let decoding of unambiguousDecodings) { + assert.strictEqual(decoding.kind, "event"); + assert.strictEqual(decoding.name, "AmbiguousEvent"); + } + + assert.strictEqual(unambiguousDecodings[0].class.typeName, "WireTest"); + assert.lengthOf(unambiguousDecodings[0].arguments, 2); + assert.isUndefined(unambiguousDecodings[0].arguments[0].value.nativize()); + assert.deepEqual(unambiguousDecodings[0].arguments[1].value.nativize(), [ + 32, + 1e12, + 17, + 18, + 19 + ]); + + assert.strictEqual(unambiguousDecodings[1].class.typeName, "WireTest"); + assert.lengthOf(unambiguousDecodings[1].arguments, 2); + assert.isUndefined(unambiguousDecodings[1].arguments[0].value.nativize()); + assert.deepEqual(unambiguousDecodings[1].arguments[1].value.nativize(), [ + 32, + 3, + 257, + 257, + 257 + ]); + + assert.strictEqual(unambiguousDecodings[2].class.typeName, "WireTest"); + assert.lengthOf(unambiguousDecodings[2].arguments, 2); + assert.isUndefined(unambiguousDecodings[2].arguments[0].value.nativize()); + assert.deepEqual(unambiguousDecodings[2].arguments[1].value.nativize(), [ + 64, + 0, + 2, + 1, + 1 + ]); + + assert.strictEqual( + unambiguousDecodings[3].class.typeName, + "WireTestLibrary" + ); + assert.lengthOf(unambiguousDecodings[3].arguments, 2); + assert.deepEqual(unambiguousDecodings[3].arguments[0].value.nativize(), [ + 107 + ]); + assert.isUndefined(unambiguousDecodings[3].arguments[1].value.nativize()); + }); }); From 24270e9a845ef9c747632f7fac480455edc78f42 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 27 Jun 2019 15:13:44 -0400 Subject: [PATCH 48/89] Remove needless variable --- packages/truffle-codec/lib/interface/decoding.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 52d99528415..7ec98977098 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -139,16 +139,15 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName: string if(targetName !== null && allocation.definition.name !== targetName) { continue; } - const attemptContext = info.contexts[parseInt(id)]; + const compiler = info.contexts[parseInt(id)].compiler; //is this the one place we get a context by ID? const contractType = CodecUtils.Contexts.contextToType(attemptContext); - const newInfo = { ...info, currentContext: attemptContext }; //you can't map with a generator, so we have to do this map manually let decodedArguments: AbiArgument[] = []; for(const argumentAllocation of allocation.arguments) { const value = (yield* decode( - Types.definitionToType(argumentAllocation.definition, attemptContext.compiler), + Types.definitionToType(argumentAllocation.definition, compiler), argumentAllocation.pointer, - newInfo, + info, 0, //offset is always 0 for events "strict" //turns on STRICT MODE to cause more errors to be thrown )); @@ -157,7 +156,7 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName: string name //deliberate general falsiness test ? { name, value } : { value } - ); + ); } debug("decodedArguments: %O", decodedArguments); //OK, so, having decoded the result, the question is: does it reencode to the original? From fb0fade1a4bfa2ccb682bddbbe2409f7b3a2ac39 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 27 Jun 2019 17:13:39 -0400 Subject: [PATCH 49/89] Add indexedness to event decoder output; remove need to explicitly call init --- .../truffle-codec/lib/interface/decoding.ts | 17 ++++++++--------- packages/truffle-codec/lib/types/wire.ts | 1 + packages/truffle-decoder/lib/index.ts | 12 ++++++++---- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 7ec98977098..063442256f8 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -139,33 +139,32 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName: string if(targetName !== null && allocation.definition.name !== targetName) { continue; } - const compiler = info.contexts[parseInt(id)].compiler; //is this the one place we get a context by ID? + const attemptContext = info.contexts[parseInt(id)]; const contractType = CodecUtils.Contexts.contextToType(attemptContext); //you can't map with a generator, so we have to do this map manually let decodedArguments: AbiArgument[] = []; for(const argumentAllocation of allocation.arguments) { const value = (yield* decode( - Types.definitionToType(argumentAllocation.definition, compiler), + Types.definitionToType(argumentAllocation.definition, attemptContext.compiler), argumentAllocation.pointer, info, 0, //offset is always 0 for events "strict" //turns on STRICT MODE to cause more errors to be thrown )); const name = argumentAllocation.definition.name; + const indexed = argumentAllocation.pointer.location === "eventtopic"; decodedArguments.push( name //deliberate general falsiness test - ? { name, value } - : { value } + ? { name, indexed, value } + : { indexed, value } ); } debug("decodedArguments: %O", decodedArguments); //OK, so, having decoded the result, the question is: does it reencode to the original? //first, we have to filter out the indexed arguments, and also get rid of the name information - const nonIndexedValues = decodedArguments.filter( - (_, index) => allocation.arguments[index].pointer.location !== "eventtopic" - ).map( - ({value}) => value - ); + const nonIndexedValues = decodedArguments + .filter(argument => !argument.indexed) + .map(argument => argument.value); //now, we can encode! debug("nonIndexedValues: %O", nonIndexedValues); const reEncodedData = encodeTupleAbi(nonIndexedValues, info.allocations.abi); diff --git a/packages/truffle-codec/lib/types/wire.ts b/packages/truffle-codec/lib/types/wire.ts index 6f1d563dd8b..3fccaa34861 100644 --- a/packages/truffle-codec/lib/types/wire.ts +++ b/packages/truffle-codec/lib/types/wire.ts @@ -33,5 +33,6 @@ export interface EventDecoding { export interface AbiArgument { name?: string; //included if parameter is named + indexed?: boolean; //included for event parameters value: CodecUtils.Values.Result; } diff --git a/packages/truffle-decoder/lib/index.ts b/packages/truffle-decoder/lib/index.ts index 2e6c325e0d6..4918e15434f 100644 --- a/packages/truffle-decoder/lib/index.ts +++ b/packages/truffle-decoder/lib/index.ts @@ -3,10 +3,14 @@ import TruffleWireDecoder from "./wire"; import { Provider } from "web3/providers"; import { ContractObject } from "truffle-contract-schema/spec"; -export function forContract(contract: ContractObject, relevantContracts: ContractObject[], provider: Provider, address?: string): TruffleContractDecoder { - return new TruffleContractDecoder(contract, relevantContracts, provider, address); +export async function forContract(contract: ContractObject, relevantContracts: ContractObject[], provider: Provider, address?: string): Promise { + let decoder = new TruffleContractDecoder(contract, relevantContracts, provider, address); + await decoder.init(); + return decoder; } -export function forProject(contracts: ContractObject[], provider: Provider): TruffleWireDecoder { - return new TruffleWireDecoder(contracts, provider); +export async function forProject(contracts: ContractObject[], provider: Provider): Promise { + let decoder = new TruffleWireDecoder(contracts, provider); + await decoder.init(); + return decoder; } From fe5be581c2a8bfd4338ace67c9e91aeaac681011 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 3 Jul 2019 19:36:23 -0400 Subject: [PATCH 50/89] Further update for type-and-value changes --- packages/truffle-codec-utils/src/abi.ts | 8 +- packages/truffle-codec-utils/src/ast.ts | 1 + packages/truffle-codec-utils/src/contexts.ts | 14 +- .../truffle-codec-utils/src/types/errors.ts | 26 +-- .../truffle-codec-utils/src/types/values.ts | 4 - packages/truffle-codec/lib/allocate/abi.ts | 10 +- packages/truffle-codec/lib/allocate/memory.ts | 7 +- packages/truffle-codec/lib/decode/abi.ts | 45 ++--- packages/truffle-codec/lib/decode/event.ts | 16 +- packages/truffle-codec/lib/decode/index.ts | 11 +- packages/truffle-codec/lib/decode/stack.ts | 10 +- packages/truffle-codec/lib/decode/value.ts | 8 +- packages/truffle-codec/lib/encode/abi.ts | 171 +++++++++--------- .../truffle-codec/lib/interface/decoding.ts | 5 +- packages/truffle-codec/lib/types/evm.ts | 6 +- packages/truffle-debugger/lib/evm/reducers.js | 2 +- .../truffle-debugger/test/data/calldata.js | 2 +- packages/truffle-decoder/lib/contract.ts | 1 - packages/truffle-decoder/lib/index.ts | 3 + .../test/test/decoding-test.js | 3 +- .../truffle-decoder/test/test/wire-test.js | 6 +- 21 files changed, 168 insertions(+), 191 deletions(-) diff --git a/packages/truffle-codec-utils/src/abi.ts b/packages/truffle-codec-utils/src/abi.ts index 9379f9c1959..d110af2d89f 100644 --- a/packages/truffle-codec-utils/src/abi.ts +++ b/packages/truffle-codec-utils/src/abi.ts @@ -3,7 +3,7 @@ const debug = debugModule("codec-utils:abi"); import { Abi as SchemaAbi } from "truffle-contract-schema/spec"; import { EVM as EVMUtils } from "./evm"; -import { AstDefinition, AstReferences } from "./ast"; +import { AstDefinition, AstReferences, Mutability } from "./ast"; import { Definition as DefinitionUtils } from "./definition"; import { Values } from "./types/values"; import { UnknownUserDefinedTypeError } from "./errors"; @@ -23,7 +23,7 @@ export namespace AbiUtils { name: string; inputs: AbiParameter[]; outputs: AbiParameter[]; - stateMutability?: "payable" | "nonpayable" | "view" | "pure"; //only in newer ones + stateMutability?: Mutability; //only in newer ones constant?: boolean; //only in older ones payable?: boolean; //only in older ones } @@ -97,7 +97,7 @@ export namespace AbiUtils { } //does this ABI have a payable fallback function? - export function isABIPayable(abiLoose: Abi | SchemaAbi | undefined): boolean | undefined { + export function abiHasPayableFallback(abiLoose: Abi | SchemaAbi | undefined): boolean | undefined { if(abiLoose === undefined) { return undefined; } @@ -109,7 +109,7 @@ export namespace AbiUtils { } //shim for old abi versions - function abiMutability(abiEntry: FunctionAbiEntry | ConstructorAbiEntry | FallbackAbiEntry): "pure" | "view" | "nonpayable" | "payable" { + function abiMutability(abiEntry: FunctionAbiEntry | ConstructorAbiEntry | FallbackAbiEntry): Mutability { if(abiEntry.stateMutability !== undefined) { return abiEntry.stateMutability; } diff --git a/packages/truffle-codec-utils/src/ast.ts b/packages/truffle-codec-utils/src/ast.ts index b930f9e5a78..dabdb9ee9ba 100644 --- a/packages/truffle-codec-utils/src/ast.ts +++ b/packages/truffle-codec-utils/src/ast.ts @@ -40,6 +40,7 @@ export interface AstDefinition { keyType?: AstDefinition; valueType?: AstDefinition; indexed?: boolean; + anonymous?: boolean; contractKind?: ContractKind; isConstructor?: boolean; [k: string]: any; diff --git a/packages/truffle-codec-utils/src/contexts.ts b/packages/truffle-codec-utils/src/contexts.ts index 74694318eef..ba54dc80336 100644 --- a/packages/truffle-codec-utils/src/contexts.ts +++ b/packages/truffle-codec-utils/src/contexts.ts @@ -9,7 +9,6 @@ import { AbiUtils } from "./abi"; import { Types } from "./types/types"; import { AstDefinition, AstReferences, ContractKind } from "./ast"; import { CompilerVersion } from "./compiler"; -import { ContractKind } from "./ast"; export namespace Contexts { @@ -59,18 +58,7 @@ export namespace Contexts { payable?: boolean; } - //does this ABI have a payable fallback function? - export function abiHasPayableFallback(abi: Abi | undefined): boolean | undefined { - if(abi === undefined) { - return undefined; - } - return abi.some( - abiEntry => abiEntry.type === "fallback" && - (abiEntry.stateMutability === "payable" || abiEntry.payable) - ); - } - - //I split these next two apart because the type system was giving me rouble + //I split these next two apart because the type system was giving me trouble export function findDecoderContext(contexts: DecoderContexts | DecoderContextsById, binary: string): DecoderContext | null { debug("binary %s", binary); let context = Object.values(contexts).find(context => diff --git a/packages/truffle-codec-utils/src/types/errors.ts b/packages/truffle-codec-utils/src/types/errors.ts index db5ef0802d8..5ddc60808af 100644 --- a/packages/truffle-codec-utils/src/types/errors.ts +++ b/packages/truffle-codec-utils/src/types/errors.ts @@ -1,24 +1,11 @@ import debugModule from "debug"; const debug = debugModule("codec-utils:types:errors"); -//objects for Solidity values - -//Note: This is NOT intended to represent every possible value that exists -//in Solidity! Only possible values of variables. (Though there may be -//some expansion in the future.) We do however count the builtin variables -//msg, block, and tx as variables (not other builtins though for now) so -//there is some support for the magic type. - -//We don't include fixed and ufixed for now. Those will be added when -//implemented. - -//NOTE: not all of these optional fields are actually implemented. Some are -//just intended for the future. More optional fields may be added in the -//future. +//error counterpart to values.ts //Note: Many of the errors defined here deliberately *don't* extend Error. -//This is because their intended use is a little different. Only the ones -//that are for throwing extend Error. +//This is because they're not for throwing. If you want to throw one, +//wrap it in a DecodingError. import BN from "bn.js"; import { Types } from "./types"; @@ -525,7 +512,8 @@ export namespace Errors { * SECTION 8: GENERIC ERRORS */ - export type GenericError = UserDefinedTypeNotFoundError | UnsupportedConstantError | ReadErrorStack; + export type GenericError = UserDefinedTypeNotFoundError | IndexedReferenceTypeError + | UnsupportedConstantError | ReadErrorStack | ReadErrorTopic; //type-location error export class UserDefinedTypeNotFoundError extends DecoderErrorBase { @@ -545,7 +533,7 @@ export namespace Errors { } //attempted to decode an indexed parameter of reference type error - export class IndexedReferenceTypeError extends GenericError { + export class IndexedReferenceTypeError extends DecoderErrorBase { type: Types.ReferenceType; raw: string; //should be hex string message() { @@ -587,7 +575,7 @@ export namespace Errors { } } - export class ReadErrorTopic extends GenericError { + export class ReadErrorTopic extends DecoderErrorBase { topic: number; message() { return `Can't read event topic ${this.topic}`; diff --git a/packages/truffle-codec-utils/src/types/values.ts b/packages/truffle-codec-utils/src/types/values.ts index 48d43b90a70..231a9669463 100644 --- a/packages/truffle-codec-utils/src/types/values.ts +++ b/packages/truffle-codec-utils/src/types/values.ts @@ -17,10 +17,6 @@ const debug = debugModule("codec-utils:types:values"); //just intended for the future. More optional fields may be added in the //future. -//Note: Many of the errors defined here deliberately *don't* extend Error. -//This is because their intended use is a little different. Only the ones -//that are for throwing extend Error. - import BN from "bn.js"; import { Types } from "./types"; import { Errors } from "./errors"; diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index 8e40baad7ad..ca13d6c186a 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -12,7 +12,7 @@ import partition from "lodash.partition"; interface AbiAllocationInfo { size?: number; //left out for types that don't go in the abi dynamic?: boolean; //similarly - allocations: AbiAllocations; + allocations: Allocations.AbiAllocations; } export function getAbiAllocations(referenceDeclarations: AstReferences): Allocations.AbiAllocations { @@ -212,8 +212,8 @@ export function abiSizeForType(dataType: CodecUtils.Types.Type, allocations?: Al case "struct": const allocation = allocations[dataType.id]; if(!allocation) { - throw new CodecUtils.Values.DecodingError( - new CodecUtils.Values.UserDefinedTypeNotFoundError(dataType) + throw new CodecUtils.Errors.DecodingError( + new CodecUtils.Errors.UserDefinedTypeNotFoundError(dataType) ); } return allocation.length; @@ -234,8 +234,8 @@ export function isTypeDynamic(dataType: CodecUtils.Types.Type, allocations?: All case "struct": const allocation = allocations[dataType.id]; if(!allocation) { - throw new CodecUtils.Values.DecodingError( - new CodecUtils.Values.UserDefinedTypeNotFoundError(dataType) + throw new CodecUtils.Errors.DecodingError( + new CodecUtils.Errors.UserDefinedTypeNotFoundError(dataType) ); } return allocation.dynamic; diff --git a/packages/truffle-codec/lib/allocate/memory.ts b/packages/truffle-codec/lib/allocate/memory.ts index 3216b97d02c..80d4b8e18ab 100644 --- a/packages/truffle-codec/lib/allocate/memory.ts +++ b/packages/truffle-codec/lib/allocate/memory.ts @@ -28,10 +28,9 @@ function allocateStruct(definition: AstDefinition): MemoryAllocation { memberAllocations.push({ definition: member, pointer: { - memory: { - start: position, - length - } + location: "memory", + start: position, + length } }); position += length; diff --git a/packages/truffle-codec/lib/decode/abi.ts b/packages/truffle-codec/lib/decode/abi.ts index 5ce4423a02f..773cf19cf06 100644 --- a/packages/truffle-codec/lib/decode/abi.ts +++ b/packages/truffle-codec/lib/decode/abi.ts @@ -8,39 +8,40 @@ import decodeValue from "./value"; import { AbiDataPointer, DataPointer } from "../types/pointer"; import { AbiMemberAllocation } from "../types/allocation"; import { abiSizeForType, isTypeDynamic } from "../allocate/abi"; -import { EvmInfo, DecoderMode } from "../types/evm"; +import { EvmInfo, DecoderOptions } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; import { StopDecodingError } from "../types/errors"; type AbiLocation = "calldata" | "eventdata"; //leaving out "abi" as it shouldn't occur here -export default function* decodeAbi(dataType: Types.Type, pointer: AbiDataPointer, info: EvmInfo, base: number = 0, mode: DecoderMode = "normal"): IterableIterator { +export default function* decodeAbi(dataType: Types.Type, pointer: AbiDataPointer, info: EvmInfo, options: DecoderOptions = {}): IterableIterator { if(Types.isReferenceType(dataType)) { let dynamic: boolean; try { dynamic = isTypeDynamic(dataType, info.allocations.abi); } catch(error) { //error: Errors.DecodingError - if(mode === "strict") { + if(options.strictAbiMode) { throw new StopDecodingError(); } return Errors.makeGenericErrorResult(dataType, error.error); } if(dynamic) { - return yield* decodeAbiReferenceByAddress(dataType, pointer, info, base, mode); + return yield* decodeAbiReferenceByAddress(dataType, pointer, info, options); } else { - return yield* decodeAbiReferenceStatic(dataType, pointer, info, mode); + return yield* decodeAbiReferenceStatic(dataType, pointer, info, options); } } else { debug("pointer %o", pointer); - return yield* decodeValue(dataType, pointer, info, mode); + return yield* decodeValue(dataType, pointer, info, options); } } -export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, pointer: DataPointer, info: EvmInfo, base: number = 0, mode: DecoderMode = "normal"): IterableIterator { - const strict = mode === "strict"; +export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, pointer: DataPointer, info: EvmInfo, options: DecoderOptions = {}): IterableIterator { + let { strictAbiMode: strict, abiPointerBase: base } = options; + base = base || 0; //in case base was undefined const { allocations: { abi: allocations }, state } = info; debug("pointer %o", pointer); //this variable holds the location we should look to *next* @@ -88,7 +89,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin start: startPosition, length: size } - return yield* decodeAbiReferenceStatic(dataType, staticPointer, info, mode); + return yield* decodeAbiReferenceStatic(dataType, staticPointer, info, options); } let length: number; let rawLength: Uint8Array; @@ -125,7 +126,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin length } - return yield* decodeValue(dataType, childPointer, info, mode); + return yield* decodeValue(dataType, childPointer, info, options); case "array": @@ -186,18 +187,18 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin start: startPosition + index * baseSize, length: baseSize }, - info, startPosition, mode + info, { ...options, abiPointerBase: startPosition } )) ); //pointer base is always start of list, never the length } return new Values.ArrayValue(dataType, decodedChildren); case "struct": - return yield* decodeAbiStructByPosition(dataType, location, startPosition, info, mode); + return yield* decodeAbiStructByPosition(dataType, location, startPosition, info, options); } } -export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer: AbiDataPointer, info: EvmInfo, mode: DecoderMode = "normal"): IterableIterator { +export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer: AbiDataPointer, info: EvmInfo, options: DecoderOptions = {}): IterableIterator { debug("static"); debug("pointer %o", pointer); const location = pointer.location; @@ -212,7 +213,7 @@ export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer baseSize = abiSizeForType(dataType.baseType, info.allocations.abi); } catch(error) { //error: Errors.DecodingError - if(mode === "strict") { + if(options.strictAbiMode) { throw new StopDecodingError(); } return Errors.makeGenericErrorResult(dataType, error.error); @@ -228,19 +229,19 @@ export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer start: pointer.start + index * baseSize, length: baseSize }, - info, 0, mode //the 0 is meaningless, just there as default + info, options )) ); } return new Values.ArrayValue(dataType, decodedChildren); case "struct": - return yield* decodeAbiStructByPosition(dataType, location, pointer.start, info, mode); + return yield* decodeAbiStructByPosition(dataType, location, pointer.start, info, options); } } //note that this function takes the start position as a *number*; it does not take a pointer -function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLocation, startPosition: number, info: EvmInfo, mode: DecoderMode = "normal"): IterableIterator { +function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLocation, startPosition: number, info: EvmInfo, options: DecoderOptions = {}): IterableIterator { const { userDefinedTypes, allocations: { abi: allocations } } = info; const typeLocation = location === "eventdata" @@ -250,7 +251,7 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc const typeId = dataType.id; const structAllocation = allocations[typeId]; if(!structAllocation) { - if(mode === "strict") { + if(options.strictAbiMode) { throw new StopDecodingError(); } return new Errors.StructErrorResult( @@ -272,7 +273,7 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc let memberName = memberAllocation.definition.name; let storedType = userDefinedTypes[typeId]; if(!storedType) { - if(mode === "strict") { + if(options.strictAbiMode) { throw new StopDecodingError(); } return new Errors.StructErrorResult( @@ -280,13 +281,13 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc new Errors.UserDefinedTypeNotFoundError(dataType) ); } - let storedMemberType = storedType.memberTypes[index][1]; + let storedMemberType = storedType.memberTypes[index].type; let memberType = Types.specifyLocation(storedMemberType, typeLocation); decodedMembers.push({ name: memberName, - value: (yield* decodeAbi(memberType, childPointer, info, startPosition, mode)) - //note that startPosition is only needed in the dynamic case, but we don't know which case we're in + value: (yield* decodeAbi(memberType, childPointer, info, {...options, abiPointerBase: startPosition})) + //note that the base option is only needed in the dynamic case, but we're being indiscriminate }); } return new Values.StructValue(dataType, decodedMembers); diff --git a/packages/truffle-codec/lib/decode/event.ts b/packages/truffle-codec/lib/decode/event.ts index 9d3ae3981f9..8ea293d274a 100644 --- a/packages/truffle-codec/lib/decode/event.ts +++ b/packages/truffle-codec/lib/decode/event.ts @@ -3,13 +3,13 @@ const debug = debugModule("codec:decode:event"); import decodeValue from "./value"; import read from "../read"; -import { Types, Values, Conversion as ConversionUtils } from "truffle-codec-utils"; +import { Types, Values, Errors, Conversion as ConversionUtils } from "truffle-codec-utils"; import { EventTopicPointer } from "../types/pointer"; -import { EvmInfo, DecoderMode } from "../types/evm"; +import { EvmInfo, DecoderOptions } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; import { StopDecodingError } from "../types/errors"; -export default function* decodeTopic(dataType: Types.Type, pointer: EventTopicPointer, info: EvmInfo, mode: DecoderMode = "normal"): IterableIterator { +export default function* decodeTopic(dataType: Types.Type, pointer: EventTopicPointer, info: EvmInfo, options: DecoderOptions = {}): IterableIterator { if(Types.isReferenceType(dataType)) { //we cannot decode reference types "stored" in topics; we have to just return an error let bytes: Uint8Array; @@ -17,21 +17,21 @@ export default function* decodeTopic(dataType: Types.Type, pointer: EventTopicPo bytes = yield* read(pointer, info.state); } catch(error) { //error: Values.DecodingError - if(mode === "strict") { + if(options.strictAbiMode) { throw new StopDecodingError(); } - return Values.makeGenericErrorResult(dataType, error.error); + return Errors.makeGenericErrorResult(dataType, error.error); } let raw: string = ConversionUtils.toHexString(bytes); //NOTE: even in strict mode we want to just return this, not throw an error here - return Values.makeGenericErrorResult( + return Errors.makeGenericErrorResult( dataType, - new Values.IndexedReferenceTypeError( + new Errors.IndexedReferenceTypeError( dataType, raw ) ); } //otherwise, dispatch to decodeValue - return yield* decodeValue(dataType, pointer, info, mode); + return yield* decodeValue(dataType, pointer, info, options); } diff --git a/packages/truffle-codec/lib/decode/index.ts b/packages/truffle-codec/lib/decode/index.ts index b0f0845fd85..e9364e56140 100644 --- a/packages/truffle-codec/lib/decode/index.ts +++ b/packages/truffle-codec/lib/decode/index.ts @@ -12,10 +12,10 @@ import decodeSpecial from "./special"; import decodeTopic from "./event"; import { Types, Values } from "truffle-codec-utils"; import * as Pointer from "../types/pointer"; -import { EvmInfo, DecoderMode } from "../types/evm"; +import { EvmInfo, DecoderOptions } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; -export default function* decode(dataType: Types.Type, pointer: Pointer.DataPointer, info: EvmInfo, base: number = 0, mode: DecoderMode = "normal"): IterableIterator { +export default function* decode(dataType: Types.Type, pointer: Pointer.DataPointer, info: EvmInfo, options: DecoderOptions = {}): IterableIterator { debug("type %O", dataType); debug("pointer %O", pointer); @@ -38,17 +38,14 @@ export default function* decode(dataType: Types.Type, pointer: Pointer.DataPoint case "calldata": case "eventdata": - return yield* decodeAbi(dataType, pointer, info, base, mode); + return yield* decodeAbi(dataType, pointer, info, options); case "eventtopic": - return yield* decodeTopic(dataType, pointer, info, mode); + return yield* decodeTopic(dataType, pointer, info, options); case "memory": //NOTE: this case should never actually occur, but I'm including it //anyway as a fallback return yield* decodeMemory(dataType, pointer, info); - - //...and in case "abi", which shouldn't happen, we'll just run off the end - //and cause a problem :P } } diff --git a/packages/truffle-codec/lib/decode/stack.ts b/packages/truffle-codec/lib/decode/stack.ts index 56d8e63196f..6f02b2492f5 100644 --- a/packages/truffle-codec/lib/decode/stack.ts +++ b/packages/truffle-codec/lib/decode/stack.ts @@ -64,12 +64,18 @@ export function* decodeLiteral(dataType: Types.Type, pointer: StackLiteralPointe //HACK -- in order to read the correct location, we need to add an offset //of -32 (since, again, we're throwing away the length info), so we pass //that in as the "base" value - return yield* decodeAbiReferenceByAddress(dataType, {location: "stackliteral", literal: locationOnly}, info, -CodecUtils.EVM.WORD_SIZE); + return yield* decodeAbiReferenceByAddress( + dataType, + {location: "stackliteral", literal: locationOnly}, + info, + { abiPointerBase: -CodecUtils.EVM.WORD_SIZE} + ); } else { //multivalue case -- this case is straightforward //pass in 0 as the base since this is an absolute pointer - return yield* decodeAbiReferenceByAddress(dataType, pointer, info, 0); + //(yeah we don't need to but let's be explicit) + return yield* decodeAbiReferenceByAddress(dataType, pointer, info, { abiPointerBase: 0 }); } } } diff --git a/packages/truffle-codec/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts index 945703df418..607d598e23e 100644 --- a/packages/truffle-codec/lib/decode/value.ts +++ b/packages/truffle-codec/lib/decode/value.ts @@ -7,13 +7,13 @@ import { Types, Values, Errors } from "truffle-codec-utils"; import BN from "bn.js"; import utf8 from "utf8"; import { DataPointer } from "../types/pointer"; -import { EvmInfo, DecoderOptions, DefaultDecoderOptions } from "../types/evm"; +import { EvmInfo, DecoderOptions } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; import { StopDecodingError } from "../types/errors"; -export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, info: EvmInfo, options: DecoderOptions = DefaultDecoderOptions): IterableIterator { +export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, info: EvmInfo, options: DecoderOptions = {}): IterableIterator { const { state } = info; - const { permissivePadding, strict } = options; + const { permissivePadding, strictAbiMode: strict } = options; //if these are undefined they'll still be falsy so OK let bytes: Uint8Array; let rawBytes: Uint8Array; @@ -263,7 +263,7 @@ export function decodeString(bytes: Uint8Array): Values.StringValueInfo { let correctlyEncodedString = utf8.decode(badlyEncodedString); return new Values.StringValueInfoValid(correctlyEncodedString); } - catch(error) { + catch(_) { //we're going to ignore the precise error and just assume it's because //the string was malformed (what else could it be?) let hexString = CodecUtils.Conversion.toHexString(bytes); diff --git a/packages/truffle-codec/lib/encode/abi.ts b/packages/truffle-codec/lib/encode/abi.ts index e96dd9e2cb5..d76c2a78281 100644 --- a/packages/truffle-codec/lib/encode/abi.ts +++ b/packages/truffle-codec/lib/encode/abi.ts @@ -4,93 +4,100 @@ import { isTypeDynamic, abiSizeForType } from "../allocate/abi"; import sum from "lodash.sum"; import utf8 from "utf8"; +//UGH -- it turns out TypeScript can't handle nested tagged unions +//see: https://github.com/microsoft/TypeScript/issues/18758 +//so, I'm just going to have to throw in a bunch of type coercions >_> + export function encodeAbi(input: Values.Result, allocations?: AbiAllocations): Uint8Array | undefined { - if(input instanceof Values.ErrorResult) { - return undefined; - } - //types that can't go in ABI - if(input instanceof Values.MappingValue) { - return undefined; - } - if(input instanceof Values.FunctionInternalValue) { + //errors can't be encoded + if(input.kind === "error") { return undefined; } - if(input instanceof Values.MagicValue) { - return undefined; - } - //now, the types that actually work! - if(input instanceof Values.UintValue) { - return ConversionUtils.toBytes(input.value.asBN, EVMUtils.WORD_SIZE); - } - if(input instanceof Values.IntValue) { - return ConversionUtils.toBytes(input.value.asBN, EVMUtils.WORD_SIZE); - } - if(input instanceof Values.EnumValue) { - return ConversionUtils.toBytes(input.value.numeric, EVMUtils.WORD_SIZE); - } - if(input instanceof Values.BoolValue) { - let bytes = new Uint8Array(EVMUtils.WORD_SIZE); //is initialized to zeroes - if(input.value.asBool) { - bytes[EVMUtils.WORD_SIZE - 1] = 1; + let bytes: Uint8Array; + //TypeScript can at least infer in the rest of this that we're looking + //at a value, not an error! But that's hardly enough... + switch(input.type.typeClass) { + case "mapping": + case "magic": + //neither of these can go in the ABI + return undefined; + case "uint": + case "int": + return ConversionUtils.toBytes((input).value.asBN, EVMUtils.WORD_SIZE); + case "enum": + return ConversionUtils.toBytes((input).value.numericAsBN, EVMUtils.WORD_SIZE); + case "bool": { + bytes = new Uint8Array(EVMUtils.WORD_SIZE); //is initialized to zeroes + if((input).value.asBool) { + bytes[EVMUtils.WORD_SIZE - 1] = 1; + } + return bytes; } - return bytes; - } - if(input instanceof Values.BytesStaticValue) { - let bytes = ConversionUtils.toBytes(input.value.asHex); - return rightPad(bytes, EVMUtils.WORD_SIZE); - } - if(input instanceof Values.BytesDynamicValue) { - let bytes = ConversionUtils.toBytes(input.value.asHex); - return padAndPrependLength(bytes); - } - if(input instanceof Values.AddressValue) { - return ConversionUtils.toBytes(input.value.asAddress, EVMUtils.WORD_SIZE); - } - if(input instanceof Values.ContractValue) { - return ConversionUtils.toBytes(input.value.address, EVMUtils.WORD_SIZE); - } - if(input instanceof Values.StringValue) { - let bytes: Uint8Array; - switch(input.value.kind) { - case "valid": - bytes = stringToBytes(input.value.asString); - break; - case "malformed": - bytes = ConversionUtils.toBytes(input.value.asHex); - break; + case "bytes": + bytes = ConversionUtils.toBytes((input).value.asHex); + switch(input.type.kind) { + case "static": + let padded = new Uint8Array(EVMUtils.WORD_SIZE); //initialized to zeroes + padded.set(bytes); + return padded; + case "dynamic": + return padAndPrependLength(bytes); + } + case "address": + return ConversionUtils.toBytes((input).value.asAddress, EVMUtils.WORD_SIZE); + case "contract": + return ConversionUtils.toBytes((input).value.address, EVMUtils.WORD_SIZE); + case "string": { + let coercedInput: Values.StringValue = input; + switch(coercedInput.value.kind) { + case "valid": + bytes = stringToBytes(coercedInput.value.asString); + break; + case "malformed": + bytes = ConversionUtils.toBytes(coercedInput.value.asHex); + break; + } + return padAndPrependLength(bytes); } - return padAndPrependLength(bytes); - } - if(input instanceof Values.FunctionExternalValue) { - let encoded = new Uint8Array(EVMUtils.WORD_SIZE); //starts filled w/0s - let addressBytes = ConversionUtils.toBytes(input.value.contract.address); //should already be correct length - let selectorBytes = ConversionUtils.toBytes(input.value.selector); //should already be correct length - encoded.set(addressBytes); - encoded.set(selectorBytes, EVMUtils.ADDRESS_SIZE); //set it after the address - return encoded; - } - //skip fixed/ufixed for now - if(input instanceof Values.ArrayValue) { - if(input.reference !== undefined) { - return undefined; //circular values can't be encoded + case "function": { + switch(input.type.visibility) { + case "internal": + return undefined; //internal functions can't go in the ABI! + case "external": + let coercedInput: Values.FunctionExternalValue = input; + let encoded = new Uint8Array(EVMUtils.WORD_SIZE); //starts filled w/0s + let addressBytes = ConversionUtils.toBytes(coercedInput.value.contract.address); //should already be correct length + let selectorBytes = ConversionUtils.toBytes(coercedInput.value.selector); //should already be correct length + encoded.set(addressBytes); + encoded.set(selectorBytes, EVMUtils.ADDRESS_SIZE); //set it after the address + return encoded; + } } - let staticEncoding = encodeTupleAbi(input.value, allocations); - switch(input.type.kind) { - case "static": - return staticEncoding; - case "dynamic": - let encoded = new Uint8Array(EVMUtils.WORD_SIZE + staticEncoding.length); //leave room for length - encoded.set(staticEncoding, EVMUtils.WORD_SIZE); //again, leave room for length beforehand - let lengthBytes = ConversionUtils.toBytes(input.value.length, EVMUtils.WORD_SIZE); - encoded.set(lengthBytes); //and now we set the length - return encoded; + //the fixed & ufixed cases will get skipped for now + case "array": { + let coercedInput: Values.ArrayValue = input; + if(coercedInput.reference !== undefined) { + return undefined; //circular values can't be encoded + } + let staticEncoding = encodeTupleAbi(coercedInput.value, allocations); + switch(input.type.kind) { + case "static": + return staticEncoding; + case "dynamic": + let encoded = new Uint8Array(EVMUtils.WORD_SIZE + staticEncoding.length); //leave room for length + encoded.set(staticEncoding, EVMUtils.WORD_SIZE); //again, leave room for length beforehand + let lengthBytes = ConversionUtils.toBytes(coercedInput.value.length, EVMUtils.WORD_SIZE); + encoded.set(lengthBytes); //and now we set the length + return encoded; + } } - } - if(input instanceof Values.StructValue) { - if(input.reference !== undefined) { - return undefined; //circular values can't be encoded + case "struct": { + let coercedInput: Values.StructValue = input; + if(coercedInput.reference !== undefined) { + return undefined; //circular values can't be encoded + } + return encodeTupleAbi(coercedInput.value.map(({value}) => value), allocations); } - return encodeTupleAbi(input.value.map(([_, value]) => value), allocations); } } @@ -115,12 +122,6 @@ function padAndPrependLength(bytes: Uint8Array): Uint8Array { return encoded; } -function rightPad(bytes: Uint8Array, length: number): Uint8Array { - let padded = new Uint8Array(length); - padded.set(bytes); - return padded; -} - export function encodeTupleAbi(tuple: Values.Result[], allocations?: AbiAllocations): Uint8Array | undefined { let elementEncodings = tuple.map(element => encodeAbi(element, allocations)); if(elementEncodings.some(element => element === undefined)) { diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 063442256f8..9206495ace1 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -62,7 +62,7 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator ({[context.contractId]: context}) )); - this.contexts = DecodeUtils.Contexts.normalizeContexts(this.contexts); } public async init(): Promise { diff --git a/packages/truffle-decoder/lib/index.ts b/packages/truffle-decoder/lib/index.ts index 4918e15434f..cd7da6c4e81 100644 --- a/packages/truffle-decoder/lib/index.ts +++ b/packages/truffle-decoder/lib/index.ts @@ -1,3 +1,6 @@ +import debugModule from "debug"; +const debug = debugModule("decoder"); + import TruffleContractDecoder from "./contract"; import TruffleWireDecoder from "./wire"; import { Provider } from "web3/providers"; diff --git a/packages/truffle-decoder/test/test/decoding-test.js b/packages/truffle-decoder/test/test/decoding-test.js index c94fb965fa8..0901ab0ea52 100644 --- a/packages/truffle-decoder/test/test/decoding-test.js +++ b/packages/truffle-decoder/test/test/decoding-test.js @@ -31,12 +31,11 @@ contract("DecodingSample", _accounts => { it("should get the initial state properly", async () => { let deployedContract = await DecodingSample.deployed(); let address = deployedContract.address; - const decoder = TruffleDecoder.forContract( + const decoder = await TruffleDecoder.forContract( DecodingSample, [], web3.currentProvider ); - await decoder.init(); decoder.watchMappingKey("varMapping", 2); decoder.watchMappingKey("varMapping", 3); diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index 98db9c78347..c5ddb57a5f8 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -13,11 +13,10 @@ contract("WireTest", _accounts => { let address = deployedContract.address; let constructorHash = deployedContract.transactionHash; - const decoder = TruffleDecoder.forProject( + const decoder = await TruffleDecoder.forProject( [WireTest, WireTestParent, WireTestLibrary], web3.currentProvider ); - await decoder.init(); let deployedContractNoConstructor = await WireTestParent.new(); let defaultConstructorHash = deployedContractNoConstructor.transactionHash; @@ -342,11 +341,10 @@ contract("WireTest", _accounts => { it("disambiguates events when possible and not when impossible", async () => { let deployedContract = await WireTest.deployed(); - const decoder = TruffleDecoder.forProject( + const decoder = await TruffleDecoder.forProject( [WireTest, WireTestParent, WireTestLibrary], web3.currentProvider ); - await decoder.init(); //HACK HACK -- we're going to repeatedly apply the hack from above //because ethers also can't handle ambiguous events From e0590488e0fdf8f7bf3afd5091d0e54fc64e4fa4 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 3 Jul 2019 23:37:49 -0400 Subject: [PATCH 51/89] Handle anonymous events --- packages/truffle-codec/lib/allocate/abi.ts | 46 +++++++----- .../truffle-codec/lib/interface/decoding.ts | 71 ++++++++++++++----- packages/truffle-codec/lib/interface/index.ts | 2 +- .../truffle-codec/lib/types/allocation.ts | 41 ++++++----- packages/truffle-codec/lib/types/wire.ts | 12 ++++ packages/truffle-decoder/lib/contract.ts | 8 +-- packages/truffle-decoder/lib/types.ts | 6 +- packages/truffle-decoder/lib/wire.ts | 8 +-- 8 files changed, 129 insertions(+), 65 deletions(-) diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index ca13d6c186a..4cf123c5608 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -382,18 +382,19 @@ function allocateEvent( }; } //finally: add in the indexed parameters... - for(let positionInIndexed = 0; positionInIndexed < indexed.length; positionInIndexed++) { - const node = indexed[positionInIndexed]; + let currentTopic = node.anonymous ? 0 : 1; //if not anonymous, selector takes up topic 0 + for(const parameterNode of indexed) { const position = rawParameters.findIndex( - (parameter: AstDefinition) => parameter.id === node.id + (parameter: AstDefinition) => parameter.id === parameterNode.id ); argumentsAllocation[position] = { - definition: node, + definition: parameterNode, pointer: { location: "eventtopic" as "eventtopic", - topic: positionInIndexed + 1 //signature takes up topic 0, so we skip that, hence +1 + topic: currentTopic } }; + currentTopic++; } //...and return return { @@ -467,31 +468,44 @@ function getEventAllocationsForContract( abiAllocations: Allocations.AbiAllocations ): Allocations.EventAllocationTemporary[] { return abi.filter( - (abiEntry: AbiUtils.AbiEntry) => abiEntry.type === "event" && !abiEntry.anonymous + (abiEntry: AbiUtils.AbiEntry) => abiEntry.type === "event" ).map( (abiEntry: AbiUtils.EventAbiEntry) => - ({ + abiEntry.anonymous + ? { + topics: AbiUtils.topicsCount(abiEntry), + allocation: allocateEvent(abiEntry, contractId, referenceDeclarations, abiAllocations) + } + : { selector: AbiUtils.abiSelector(abiEntry), topics: AbiUtils.topicsCount(abiEntry), allocation: allocateEvent(abiEntry, contractId, referenceDeclarations, abiAllocations) - }) + } ); } //note: constructor context is ignored by this function; no need to pass it in export function getEventAllocations(contracts: Allocations.ContractAllocationInfo[], referenceDeclarations: AstReferences, abiAllocations: Allocations.AbiAllocations): Allocations.EventAllocations { let allocations: Allocations.EventAllocations = {}; - for(let {abi, id} of contracts) { - let contractKind = referenceDeclarations[id].contractKind; - let contractAllocations = getEventAllocationsForContract(abi, id, referenceDeclarations, abiAllocations); + for(let {abi, id: contractId} of contracts) { + let contractKind = referenceDeclarations[contractId].contractKind; + let contractAllocations = getEventAllocationsForContract(abi, contractId, referenceDeclarations, abiAllocations); for(let {selector, topics, allocation} of contractAllocations) { - if(allocations[selector] === undefined) { - allocations[selector] = {}; + if(allocations[topics] === undefined) { + allocations[topics] = { bySelector: {}, anonymous: { contract: {}, library: {} } }; } - if(allocations[selector][topics] === undefined) { - allocations[selector][topics] = { contract: {}, library: {} }; + if(selector !== undefined) { + if(allocations[topics].bySelector[selector] === undefined) { + allocations[topics].bySelector[selector] = { contract: {}, library: {} }; + } + allocations[topics].bySelector[selector][contractKind][contractId] = allocation; + } + else { + if(allocations[topics].anonymous[contractKind][contractId] === undefined) { + allocations[topics].anonymous[contractKind][contractId] = []; + } + allocations[topics].anonymous[contractKind][contractId].push(allocation); } - allocations[selector][topics][contractKind][id] = allocation; } } return allocations; diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 9206495ace1..c83e22995a3 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -7,7 +7,7 @@ import * as Pointer from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; import { CalldataAllocation, EventAllocation, EventArgumentAllocation } from "../types/allocation"; -import { CalldataDecoding, EventDecoding, AbiArgument } from "../types/wire"; +import { CalldataDecoding, LogDecoding, AbiArgument } from "../types/wire"; import { encodeTupleAbi } from "../encode/abi"; import read from "../read"; import decode from "../decode"; @@ -33,6 +33,7 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator { +export function* decodeEvent(info: EvmInfo, address: string, targetName: string | null = null): IterableIterator { const allocations = info.allocations.event; debug("event allocations: %O", allocations); let rawSelector: Uint8Array; @@ -107,7 +111,8 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName: string const selector = CodecUtils.Conversion.toHexString(rawSelector); const topicsCount = info.state.eventtopics.length; //yeah, it's not great to read directly from the state like this (bypassing read), but what are you gonna do? - const { contract: contractAllocations, library: libraryAllocations } = allocations[selector][topicsCount]; + const { contract: contractAllocations, library: libraryAllocations } = allocations[topicsCount].bySelector[selector]; + const { contract: contractAnonymousAllocations, library: libraryAnonymousAllocations } = allocations[topicsCount].anonymous; //now: what contract are we (probably) dealing with? let's get its code to find out const codeBytes: Uint8Array = yield { type: "code", @@ -115,25 +120,44 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName: string }; const codeAsHex = CodecUtils.Conversion.toHexString(codeBytes); const contractContext = CodecUtils.Contexts.findDecoderContext(info.contexts, codeAsHex); - let possibleContractAllocations: [string, EventAllocation][]; + //the following two arrays contain id-allocation pairs + let possibleContractAllocations: [string, EventAllocation][]; //excludes anonymous events + let possibleContractAnonymousAllocations: [string, EventAllocation][]; //should be number, but we have to temporarily pass through string to get compilation to work... //(these are ID/allocation pairs) if(contractContext) { //if we found the contract, maybe it's from that contract const contractId = contractContext.contractId; const contractAllocation = contractAllocations[contractId]; + const contractAnonymousAllocation = contractAnonymousAllocations[contractId]; possibleContractAllocations = contractAllocation ? [[contractId.toString(), contractAllocation]] //array of a single pair : []; + if(contractAnonymousAllocation) { + possibleContractAnonymousAllocations = contractAnonymousAllocation.map( + allocation => [contractId.toString(), allocation] + ); + } + else { + possibleContractAnonymousAllocations = []; + } } else { //if we couldn't determine the contract, well, we have to assume it's from a library possibleContractAllocations = []; + possibleContractAnonymousAllocations = []; } - //now we add in all the library allocations! - const possibleAllocations = [...possibleContractAllocations, ...Object.entries(libraryAllocations)]; - let decodings: EventDecoding[] = []; - for(const [id, allocation] of possibleAllocations) { + //now we get all the library allocations! + const possibleLibraryAllocations = Object.entries(libraryAllocations); + const possibleLibraryAnonymousAllocations = [].concat(...Object.entries(libraryAnonymousAllocations).map( + ([id, allocations]) => allocations.map(allocation => [id, allocation]) + )); + //now we put it all together! + const possibleAllocations = possibleContractAllocations.concat(possibleLibraryAllocations); + const possibleAnonymousAllocations = possibleContractAnonymousAllocations.concat(possibleLibraryAnonymousAllocations); + const possibleAllocationsTotal = possibleAllocations.concat(possibleAnonymousAllocations); + let decodings: LogDecoding[] = []; + for(const [id, allocation] of possibleAllocationsTotal) { try { //first: do a name check so we can skip decoding if name is wrong if(targetName !== null && allocation.definition.name !== targetName) { @@ -170,12 +194,23 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName: string //are they equal? const encodedData = info.state.eventdata; //again, not great to read this directly, but oh well if(CodecUtils.EVM.equalData(encodedData, reEncodedData)) { - decodings.push({ - kind: "event", - class: contractType, - name: allocation.definition.name, - arguments: decodedArguments - }); + if(allocation.definition.anonymous) { + decodings.push({ + kind: "anonymous", + class: contractType, + name: allocation.definition.name, + arguments: decodedArguments + }); + } + else { + decodings.push({ + kind: "event", + class: contractType, + name: allocation.definition.name, + arguments: decodedArguments, + selector + }); + } } //otherwise, just move on } diff --git a/packages/truffle-codec/lib/interface/index.ts b/packages/truffle-codec/lib/interface/index.ts index 4c4e24f3639..c395935b81c 100644 --- a/packages/truffle-codec/lib/interface/index.ts +++ b/packages/truffle-codec/lib/interface/index.ts @@ -8,7 +8,7 @@ export { ContractAllocationInfo, StorageAllocations, StorageMemberAllocation, Ab export { Slot, isWordsLength, equalSlots } from "../types/storage"; export { DecoderRequest, isStorageRequest, isCodeRequest } from "../types/request"; export { EvmInfo, AllocationInfo } from "../types/evm"; -export { CalldataDecoding, EventDecoding } from "../types/wire"; +export { CalldataDecoding, LogDecoding } from "../types/wire"; export { decodeVariable, decodeEvent, decodeCalldata } from "./decoding"; diff --git a/packages/truffle-codec/lib/types/allocation.ts b/packages/truffle-codec/lib/types/allocation.ts index 42d2b693316..c2a179d5978 100644 --- a/packages/truffle-codec/lib/types/allocation.ts +++ b/packages/truffle-codec/lib/types/allocation.ts @@ -106,26 +106,29 @@ export interface CalldataArgumentAllocation { //finally we have events. these work like calldata, except that there's no //need for an offset, the ultimate pointer can be either an event data pointer -//or an event topic pointer, and, they're given *first* by selector, *then* -//by number of topics, *then* by contract ID (the latter being split into -//contracts and libraries) +//or an event topic pointer, and, they're given: +//1. first by # of topics +//2. then by anonymous or not +//3. then by selector (this one is skipped for anonymou) +//4. then by contract kind +//5. then by contract ID +//(and then the anonymous ones are in an array) export interface EventAllocations { - [selector: string]: EventSelectorAllocation; -} - -export interface EventSelectorAllocation { - [topics: number]: EventAllocationsNarrow; -} - -export interface EventAllocationsNarrow { - [contractKind: string]: EventContractAllocation; - //yes, this is a stupid way of doing this, but it's the easiest way to - //get things to compile -} - -export interface EventContractAllocation { - [contractId: number]: EventAllocation; + [topics: number]: { + bySelector: { + [selector: string]: { + [contractKind: string]: { + [contractId: number]: EventAllocation; + } + } + }; + anonymous: { + [contractKind: string]: { + [contractId: number]: EventAllocation[]; + } + } + } } export interface EventAllocation { @@ -141,7 +144,7 @@ export interface EventArgumentAllocation { //NOTE: not for outside use! just produced temporarily by the allocator! export interface EventAllocationTemporary { - selector: string; + selector?: string; //leave out for anonymous topics: number; allocation: EventAllocation; } diff --git a/packages/truffle-codec/lib/types/wire.ts b/packages/truffle-codec/lib/types/wire.ts index 3fccaa34861..b3bf8dfcbb9 100644 --- a/packages/truffle-codec/lib/types/wire.ts +++ b/packages/truffle-codec/lib/types/wire.ts @@ -1,23 +1,27 @@ import * as CodecUtils from "truffle-codec-utils"; export type CalldataDecoding = FunctionDecoding | ConstructorDecoding | FallbackDecoding | UnknownDecoding; +export type LogDecoding = EventDecoding | AnonymousDecoding; export interface FunctionDecoding { kind: "function"; class: CodecUtils.Types.ContractType; arguments: AbiArgument[]; name: string; + selector: string; } export interface ConstructorDecoding { kind: "constructor"; class: CodecUtils.Types.ContractType; arguments: AbiArgument[]; + bytecode: string; } export interface FallbackDecoding { kind: "fallback"; class: CodecUtils.Types.ContractType; + data: string; } export interface UnknownDecoding { @@ -29,6 +33,14 @@ export interface EventDecoding { class: CodecUtils.Types.ContractType; arguments: AbiArgument[]; name: string; + selector: string; +} + +export interface AnonymousDecoding { + kind: "anonymous"; + class: CodecUtils.Types.ContractType; + arguments: AbiArgument[]; + name: string; } export interface AbiArgument { diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 7584af13cee..14b027f1738 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -410,7 +410,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } //NOTE: will only work with logs for this address! - public async decodeLog(log: Log, name: string | null = null): Promise { + public async decodeLog(log: Log, name: string | null = null): Promise { if(log.address !== this.contractAddress) { throw new DecoderTypes.EventOrTransactionIsNotForThisContractError(log.address, this.contractAddress); } @@ -440,7 +440,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { result = decoder.next(response); } //at this point, result.value holds the final value - const decodings = result.value; + const decodings = result.value; return { ...log, @@ -449,11 +449,11 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } //NOTE: will only work with logs for this address! - public async decodeLogs(logs: Log[], name: string | null = null): Promise { + public async decodeLogs(logs: Log[], name: string | null = null): Promise { return await Promise.all(logs.map(log => this.decodeLog(log, name))); } - public async events(name: string | null = null, fromBlock: BlockType = "latest", toBlock: BlockType = "latest"): Promise { + public async events(name: string | null = null, fromBlock: BlockType = "latest", toBlock: BlockType = "latest"): Promise { const logs = await this.web3.eth.getPastLogs({ address: this.contractAddress, fromBlock, diff --git a/packages/truffle-decoder/lib/types.ts b/packages/truffle-decoder/lib/types.ts index ed9d7fc3633..1f4e77c0170 100644 --- a/packages/truffle-decoder/lib/types.ts +++ b/packages/truffle-decoder/lib/types.ts @@ -1,7 +1,7 @@ import BN from "bn.js"; import { ContractObject } from "truffle-contract-schema/spec"; import { Values } from "truffle-codec-utils"; -import { CalldataDecoding, EventDecoding } from "truffle-codec"; +import { CalldataDecoding, LogDecoding } from "truffle-codec"; import { Transaction, Log } from "web3-core"; export interface ContractState { @@ -18,8 +18,8 @@ export interface DecodedTransaction extends Transaction { decoding: CalldataDecoding; } -export interface DecodedEvent extends Log { - decodings: EventDecoding[]; +export interface DecodedLog extends Log { + decodings: LogDecoding[]; } export interface ContractMapping { diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 4967ad4bb27..23616daad25 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -182,7 +182,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - public async decodeLog(log: Log, name: string | null = null): Promise { + public async decodeLog(log: Log, name: string | null = null): Promise { const block = log.blockNumber; const data = CodecUtils.Conversion.toBytes(log.data); const topics = log.topics.map(CodecUtils.Conversion.toBytes); @@ -209,7 +209,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { result = decoder.next(response); } //at this point, result.value holds the final value - const decodings = result.value; + const decodings = result.value; return { ...log, @@ -217,11 +217,11 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - public async decodeLogs(logs: Log[], name: string | null = null): Promise { + public async decodeLogs(logs: Log[], name: string | null = null): Promise { return await Promise.all(logs.map(log => this.decodeLog(log, name))); } - public async events(name: string | null = null, fromBlock: BlockType = "latest", toBlock: BlockType = "latest"): Promise { + public async events(name: string | null = null, fromBlock: BlockType = "latest", toBlock: BlockType = "latest"): Promise { const logs = await this.web3.eth.getPastLogs({ fromBlock, toBlock, From e3b32196fda538a409dad54244bc8cc5703f3984 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 4 Jul 2019 00:35:19 -0400 Subject: [PATCH 52/89] Fix undefinedness error; add test of anonymous events --- .../truffle-codec/lib/interface/decoding.ts | 2 +- .../test/contracts/WireTest.sol | 14 +++ .../truffle-decoder/test/test/wire-test.js | 95 +++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index c83e22995a3..9dddecac75e 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -111,7 +111,7 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName: string const selector = CodecUtils.Conversion.toHexString(rawSelector); const topicsCount = info.state.eventtopics.length; //yeah, it's not great to read directly from the state like this (bypassing read), but what are you gonna do? - const { contract: contractAllocations, library: libraryAllocations } = allocations[topicsCount].bySelector[selector]; + const { contract: contractAllocations, library: libraryAllocations } = allocations[topicsCount].bySelector[selector] || {contract: {}, library: {}}; const { contract: contractAnonymousAllocations, library: libraryAnonymousAllocations } = allocations[topicsCount].anonymous; //now: what contract are we (probably) dealing with? let's get its code to find out const codeBytes: Uint8Array = yield { diff --git a/packages/truffle-decoder/test/contracts/WireTest.sol b/packages/truffle-decoder/test/contracts/WireTest.sol index a2436c03017..ba09df52a20 100644 --- a/packages/truffle-decoder/test/contracts/WireTest.sol +++ b/packages/truffle-decoder/test/contracts/WireTest.sol @@ -101,6 +101,18 @@ contract WireTest is WireTestParent { WireTestLibrary.emitUnambiguousEvent(); } + + event AnonUints(uint indexed, uint indexed, uint indexed, uint indexed) anonymous; + event NonAnon(uint indexed, uint indexed, uint indexed); + + function anonymousTest() public { + //first test: unambiguous + emit AnonUints(257, 1, 1, 1); + //second test: uint8 (from library) or uint? + emit AnonUints(1, 2, 3, 4); + //third test: uint, or not anonymous? + emit NonAnon(1, 2, 3); + } } library WireTestLibrary { @@ -118,4 +130,6 @@ library WireTestLibrary { uint[5] memory allZeroes; emit AmbiguousEvent(wrongLength, allZeroes); } + + event AnonUint8s(uint8 indexed, uint8 indexed, uint8 indexed, uint8 indexed) anonymous; } diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index c5ddb57a5f8..9682e9ecf17 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -1,5 +1,6 @@ const debug = require("debug")("decoder:test:wire-test"); const assert = require("chai").assert; +const BN = require("bn.js"); const TruffleDecoder = require("../../../truffle-decoder"); @@ -453,4 +454,98 @@ contract("WireTest", _accounts => { ]); assert.isUndefined(unambiguousDecodings[3].arguments[1].value.nativize()); }); + + it("Handles anonymous events", async () => { + let deployedContract = await WireTest.deployed(); + + const decoder = await TruffleDecoder.forProject( + [WireTest, WireTestParent, WireTestLibrary], + web3.currentProvider + ); + + //thankfully, ethers ignores anonymous events, + //so we don't need to use that hack here + let anonymousTest = await deployedContract.anonymousTest(); + let block = anonymousTest.blockNumber; + let anonymousTestEvents = await decoder.events(null, block, block); + + assert.lengthOf(anonymousTestEvents, 3); + + assert.lengthOf(anonymousTestEvents[0].decodings, 1); + assert.strictEqual(anonymousTestEvents[0].decodings[0].kind, "anonymous"); + assert.strictEqual(anonymousTestEvents[0].decodings[0].name, "AnonUints"); + assert.strictEqual( + anonymousTestEvents[0].decodings[0].class.typeName, + "WireTest" + ); + assert.lengthOf(anonymousTestEvents[0].decodings[0].arguments, 4); + assert.deepEqual( + anonymousTestEvents[0].decodings[0].arguments.map(({ value }) => + value.nativize() + ), + [257, 1, 1, 1] + ); + + assert.lengthOf(anonymousTestEvents[1].decodings, 2); + assert.strictEqual(anonymousTestEvents[1].decodings[0].kind, "anonymous"); + assert.strictEqual(anonymousTestEvents[1].decodings[0].name, "AnonUints"); + assert.strictEqual( + anonymousTestEvents[1].decodings[0].class.typeName, + "WireTest" + ); + assert.lengthOf(anonymousTestEvents[1].decodings[0].arguments, 4); + assert.deepEqual( + anonymousTestEvents[1].decodings[0].arguments.map(({ value }) => + value.nativize() + ), + [1, 2, 3, 4] + ); + assert.strictEqual(anonymousTestEvents[1].decodings[1].kind, "anonymous"); + assert.strictEqual(anonymousTestEvents[1].decodings[1].name, "AnonUint8s"); + assert.strictEqual( + anonymousTestEvents[1].decodings[1].class.typeName, + "WireTestLibrary" + ); + assert.lengthOf(anonymousTestEvents[1].decodings[1].arguments, 4); + assert.deepEqual( + anonymousTestEvents[1].decodings[1].arguments.map(({ value }) => + value.nativize() + ), + [1, 2, 3, 4] + ); + + assert.lengthOf(anonymousTestEvents[2].decodings, 2); + assert.strictEqual(anonymousTestEvents[2].decodings[0].kind, "event"); + assert.strictEqual(anonymousTestEvents[2].decodings[0].name, "NonAnon"); + assert.strictEqual( + anonymousTestEvents[2].decodings[0].class.typeName, + "WireTest" + ); + assert.lengthOf(anonymousTestEvents[2].decodings[0].arguments, 3); + assert.deepEqual( + anonymousTestEvents[2].decodings[0].arguments.map(({ value }) => + value.nativize() + ), + [1, 2, 3] + ); + let selector = anonymousTestEvents[2].decodings[0].selector; + assert.strictEqual(anonymousTestEvents[2].decodings[1].kind, "anonymous"); + assert.strictEqual(anonymousTestEvents[2].decodings[1].name, "AnonUints"); + assert.strictEqual( + anonymousTestEvents[2].decodings[1].class.typeName, + "WireTest" + ); + assert.lengthOf(anonymousTestEvents[2].decodings[1].arguments, 4); + assert.deepEqual( + anonymousTestEvents[2].decodings[1].arguments + .slice(1) + .map(({ value }) => value.nativize()), + [1, 2, 3] + ); + assert( + anonymousTestEvents[2].decodings[1].arguments[0].value.value.asBN.eq( + new BN(selector.slice(2), 16) + ) + ); + }); }); From a24601ed4b645b03366201736cd82540ada383ab Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 4 Jul 2019 01:45:29 -0400 Subject: [PATCH 53/89] Put more info in StopDecodingErrors; fix other error typing problems --- .../truffle-codec-utils/src/types/errors.ts | 102 +++++++++++---- packages/truffle-codec/lib/decode/abi.ts | 44 ++++--- packages/truffle-codec/lib/decode/event.ts | 2 +- packages/truffle-codec/lib/decode/value.ts | 116 +++++++----------- packages/truffle-codec/lib/types/errors.ts | 17 ++- packages/truffle-codec/lib/types/evm.ts | 4 +- 6 files changed, 166 insertions(+), 119 deletions(-) diff --git a/packages/truffle-codec-utils/src/types/errors.ts b/packages/truffle-codec-utils/src/types/errors.ts index 5ddc60808af..4130c741db7 100644 --- a/packages/truffle-codec-utils/src/types/errors.ts +++ b/packages/truffle-codec-utils/src/types/errors.ts @@ -37,6 +37,14 @@ export namespace Errors { | EnumErrorResult | ContractErrorResult | FunctionExternalErrorResult | FunctionInternalErrorResult; + export type DecoderError = GenericError + | UintError | IntError | BoolError | BytesStaticError | AddressError + | FixedError | UfixedError + | EnumError | ContractError | FunctionExternalError | FunctionInternalError + | InternalUseError; + //note that at the moment, no reference type has its own error category, so + //no reference types are listed here; this also includes Magic + //for internal use only! just here to facilitate code reuse! //please use the union types for actual use! //this class doesn't even have the error field! @@ -76,12 +84,14 @@ export namespace Errors { export class UintErrorResult extends ErrorResultBase { constructor( public uintType: Types.UintType, - public error: GenericError | UintPaddingError + public error: GenericError | UintError ) { super(); } } + export type UintError = UintPaddingError; + export class UintPaddingError extends DecoderErrorBase { raw: string; //hex string kind: "UintPaddingError"; @@ -99,12 +109,14 @@ export namespace Errors { export class IntErrorResult extends ErrorResultBase { constructor( public intType: Types.IntType, - public error: GenericError | IntPaddingError + public error: GenericError | IntError ) { super(); } } + export type IntError = IntPaddingError; + export class IntPaddingError extends DecoderErrorBase { raw: string; //hex string kind: "IntPaddingError"; @@ -122,12 +134,14 @@ export namespace Errors { export class BoolErrorResult extends ErrorResultBase { constructor( public boolType: Types.BoolType, - public error: GenericError | BoolPaddingError | BoolOutOfRangeError + public error: GenericError | BoolError ) { super(); } } + export type BoolError = BoolPaddingError | BoolOutOfRangeError; + export class BoolPaddingError extends DecoderErrorBase { raw: string; //should be hex string kind: "BoolPaddingError"; @@ -142,14 +156,14 @@ export namespace Errors { } export class BoolOutOfRangeError extends DecoderErrorBase { - raw: BN; + rawAsBN: BN; kind: "BoolOutOfRangeError"; [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `Invalid boolean (numeric value ${this.raw.toString()})`; + return `Invalid boolean (numeric value ${this.rawAsBN.toString()})`; } constructor(raw: BN) { super(); - this.raw = raw; + this.rawAsBN = raw; this.kind = "BoolOutOfRangeError"; } } @@ -158,12 +172,14 @@ export namespace Errors { export class BytesStaticErrorResult extends ErrorResultBase { constructor( public bytesType: Types.BytesTypeStatic, - public error: GenericError | BytesPaddingError + public error: GenericError | BytesStaticError ) { super(); } } + export type BytesStaticError = BytesPaddingError; + export class BytesPaddingError extends DecoderErrorBase { raw: string; //should be hex string kind: "BytesPaddingError"; @@ -191,12 +207,14 @@ export namespace Errors { export class AddressErrorResult extends ErrorResultBase { constructor( public addressType: Types.AddressType, - public error: GenericError | AddressPaddingError + public error: GenericError | AddressError ) { super(); } } + export type AddressError = AddressPaddingError; + export class AddressPaddingError extends DecoderErrorBase { raw: string; //should be hex string kind: "AddressPaddingError"; @@ -225,7 +243,7 @@ export namespace Errors { export class FixedErrorResult extends ErrorResultBase { constructor( public fixedType: Types.FixedType, - public error: GenericError | FixedPointNotYetSupportedError + public error: GenericError | FixedError ) { super(); } @@ -233,12 +251,15 @@ export namespace Errors { export class UfixedErrorResult extends ErrorResultBase { constructor( public ufixedType: Types.UfixedType, - public error: GenericError | FixedPointNotYetSupportedError + public error: GenericError | UfixedError ) { super(); } } + export type FixedError = FixedPointNotYetSupportedError; + export type UfixedError = FixedPointNotYetSupportedError; + export class FixedPointNotYetSupportedError extends DecoderErrorBase { raw: string; //hex string kind: "FixedPointNotYetSupportedError"; @@ -306,12 +327,14 @@ export namespace Errors { export class EnumErrorResult extends ErrorResultBase { constructor( public enumType: Types.EnumType, - public error: GenericError | EnumPaddingError | EnumOutOfRangeError | EnumNotFoundDecodingError + public error: GenericError | EnumError ) { super(); } } + export type EnumError = EnumPaddingError | EnumOutOfRangeError | EnumNotFoundDecodingError; + export class EnumPaddingError extends DecoderErrorBase { kind: "EnumPaddingError"; type: Types.EnumType; @@ -331,15 +354,15 @@ export namespace Errors { export class EnumOutOfRangeError extends DecoderErrorBase { kind: "EnumOutOfRangeError"; type: Types.EnumType; - raw: BN; + rawAsBN: BN; [util.inspect.custom](depth: number | null, options: InspectOptions): string { let typeName = (this.type.kind === "local" ? (this.type.definingContractName + ".") : "") + this.type.typeName; - return `Invalid ${typeName} (numeric value ${this.raw.toString()})`; + return `Invalid ${typeName} (numeric value ${this.rawAsBN.toString()})`; } constructor(enumType: Types.EnumType, raw: BN) { super(); this.type = enumType; - this.raw = raw; + this.rawAsBN = raw; this.kind = "EnumOutOfRangeError"; } } @@ -347,15 +370,15 @@ export namespace Errors { export class EnumNotFoundDecodingError extends DecoderErrorBase { kind: "EnumNotFoundDecodingError"; type: Types.EnumType; - raw: BN; + rawAsBN: BN; [util.inspect.custom](depth: number | null, options: InspectOptions): string { let typeName = (this.type.kind === "local" ? (this.type.definingContractName + ".") : "") + this.type.typeName; - return `Unknown enum type ${typeName} of id ${this.type.id} (numeric value ${this.raw.toString()})`; + return `Unknown enum type ${typeName} of id ${this.type.id} (numeric value ${this.rawAsBN.toString()})`; } constructor(enumType: Types.EnumType, raw: BN) { super(); this.type = enumType; - this.raw = raw; + this.rawAsBN = raw; this.kind = "EnumNotFoundDecodingError"; } } @@ -368,12 +391,14 @@ export namespace Errors { export class ContractErrorResult extends ErrorResultBase { constructor( public contractType: Types.ContractType, - public error: GenericError | ContractPaddingError + public error: GenericError | ContractError ) { super(); } } + export type ContractError = ContractPaddingError; + export class ContractPaddingError extends DecoderErrorBase { raw: string; //should be hex string kind: "ContractPaddingError"; @@ -395,12 +420,14 @@ export namespace Errors { export class FunctionExternalErrorResult extends ErrorResultBase { constructor( public functionType: Types.FunctionTypeExternal, - public error: GenericError | FunctionExternalNonStackPaddingError | FunctionExternalStackPaddingError + public error: GenericError | FunctionExternalError ) { super(); } } + export type FunctionExternalError = FunctionExternalNonStackPaddingError | FunctionExternalStackPaddingError; + export class FunctionExternalNonStackPaddingError extends DecoderErrorBase { raw: string; //should be hex string kind: "FunctionExternalNonStackPaddingError"; @@ -437,13 +464,15 @@ export namespace Errors { export class FunctionInternalErrorResult extends ErrorResultBase { constructor( public functionType: Types.FunctionTypeInternal, - public error: GenericError | FunctionInternalPaddingError - | NoSuchInternalFunctionError | DeployedFunctionInConstructorError | MalformedInternalFunctionError + public error: GenericError | FunctionInternalError ) { super(); } } + export type FunctionInternalError = FunctionInternalPaddingError | NoSuchInternalFunctionError + | DeployedFunctionInConstructorError | MalformedInternalFunctionError; + export class FunctionInternalPaddingError extends DecoderErrorBase { raw: string; //should be hex string kind: "FunctionInternalPaddingError"; @@ -534,6 +563,7 @@ export namespace Errors { //attempted to decode an indexed parameter of reference type error export class IndexedReferenceTypeError extends DecoderErrorBase { + kind: "IndexedReferenceTypeError"; type: Types.ReferenceType; raw: string; //should be hex string message() { @@ -543,6 +573,7 @@ export namespace Errors { super(); this.type = referenceType; this.raw = raw; + this.kind = "IndexedReferenceTypeError"; } } @@ -576,6 +607,7 @@ export namespace Errors { } export class ReadErrorTopic extends DecoderErrorBase { + kind: "ReadErrorTopic"; topic: number; message() { return `Can't read event topic ${this.topic}`; @@ -583,6 +615,7 @@ export namespace Errors { constructor(topic: number) { super(); this.topic = topic; + this.kind = "ReadErrorTopic"; } } @@ -631,4 +664,31 @@ export namespace Errors { } } } + + /* SECTION 9: Internal use errors */ + + export type InternalUseError = OverlongArrayOrStringError | InternalFunctionInABIError; + + //you should never see this returned. this is only for internal use. + export class OverlongArrayOrStringError extends DecoderErrorBase { + kind: "OverlongArrayOrStringError"; + lengthAsBN: BN; + dataLength: number; + constructor(length: BN, dataLength: number) { + super(); + this.lengthAsBN = length; + this.dataLength = dataLength; + this.kind = "OverlongArrayOrStringError"; + } + } + + //this one should never come up at all, but just to be sure... + export class InternalFunctionInABIError extends DecoderErrorBase { + kind: "InternalFunctionInABIError"; + constructor() { + super(); + this.kind = "InternalFunctionInABIError"; + } + } + } diff --git a/packages/truffle-codec/lib/decode/abi.ts b/packages/truffle-codec/lib/decode/abi.ts index 773cf19cf06..2aeeb19f174 100644 --- a/packages/truffle-codec/lib/decode/abi.ts +++ b/packages/truffle-codec/lib/decode/abi.ts @@ -22,7 +22,7 @@ export default function* decodeAbi(dataType: Types.Type, pointer: AbiDataPointer } catch(error) { //error: Errors.DecodingError if(options.strictAbiMode) { - throw new StopDecodingError(); + throw new StopDecodingError(error.error); } return Errors.makeGenericErrorResult(dataType, error.error); } @@ -55,7 +55,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin } catch(error) { //error: Errors.DecodingError if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error.error); } return Errors.makeGenericErrorResult(dataType, error.error); } @@ -69,7 +69,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin } catch(error) { //error: Errors.DecodingError if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error.error); } return Errors.makeGenericErrorResult(dataType, error.error); } @@ -80,7 +80,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin } catch(error) { //error: Errors.DecodingError if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error.error); } return Errors.makeGenericErrorResult(dataType, error.error); } @@ -107,7 +107,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin } catch(error) { //error: Errors.DecodingError if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error.error); } return Errors.makeGenericErrorResult(dataType, error.error); } @@ -116,7 +116,11 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin //you may notice that the comparison is a bit crude; that's OK, this is //just to prevent huge numbers from DOSing us, other errors will still //be caught regardless - throw new StopDecodingError(); + throw new StopDecodingError( + new Errors.OverlongArrayOrStringError( + lengthAsBN, info.state[location].length + ) + ); } length = lengthAsBN.toNumber(); @@ -142,7 +146,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin } catch(error) { //error: Errors.DecodingError if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error.error); } return Errors.makeGenericErrorResult(dataType, error.error); } @@ -151,7 +155,11 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin //you may notice that the comparison is a bit crude; that's OK, this is //just to prevent huge numbers from DOSing us, other errors will still //be caught regardless - throw new StopDecodingError(); + throw new StopDecodingError( + new Errors.OverlongArrayOrStringError( + lengthAsBN, info.state[location].length + ) + ); } length = lengthAsBN.toNumber(); startPosition += CodecUtils.EVM.WORD_SIZE; //increment startPosition @@ -172,7 +180,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin } catch(error) { //error: Errors.DecodingError if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error.error); } return Errors.makeGenericErrorResult(dataType, error.error); } @@ -214,7 +222,7 @@ export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer } catch(error) { //error: Errors.DecodingError if(options.strictAbiMode) { - throw new StopDecodingError(); + throw new StopDecodingError(error.error); } return Errors.makeGenericErrorResult(dataType, error.error); } @@ -251,13 +259,11 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc const typeId = dataType.id; const structAllocation = allocations[typeId]; if(!structAllocation) { + let error = new Errors.UserDefinedTypeNotFoundError(dataType); if(options.strictAbiMode) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.StructErrorResult( - dataType, - new Errors.UserDefinedTypeNotFoundError(dataType) - ); + return new Errors.StructErrorResult(dataType, error); } let decodedMembers: Values.NameValuePair[] = []; @@ -273,13 +279,11 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc let memberName = memberAllocation.definition.name; let storedType = userDefinedTypes[typeId]; if(!storedType) { + let error = new Errors.UserDefinedTypeNotFoundError(dataType); if(options.strictAbiMode) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.StructErrorResult( - dataType, - new Errors.UserDefinedTypeNotFoundError(dataType) - ); + return new Errors.StructErrorResult(dataType, error); } let storedMemberType = storedType.memberTypes[index].type; let memberType = Types.specifyLocation(storedMemberType, typeLocation); diff --git a/packages/truffle-codec/lib/decode/event.ts b/packages/truffle-codec/lib/decode/event.ts index 8ea293d274a..87ff79f939c 100644 --- a/packages/truffle-codec/lib/decode/event.ts +++ b/packages/truffle-codec/lib/decode/event.ts @@ -18,7 +18,7 @@ export default function* decodeTopic(dataType: Types.Type, pointer: EventTopicPo } catch(error) { //error: Values.DecodingError if(options.strictAbiMode) { - throw new StopDecodingError(); + throw new StopDecodingError(error.error); } return Errors.makeGenericErrorResult(dataType, error.error); } diff --git a/packages/truffle-codec/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts index 607d598e23e..9f3d33e91dc 100644 --- a/packages/truffle-codec/lib/decode/value.ts +++ b/packages/truffle-codec/lib/decode/value.ts @@ -23,7 +23,7 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, catch(error) { //error: Errors.DecodingError debug("segfault, pointer %o, state: %O", pointer, state); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error.error); } return Errors.makeGenericErrorResult(dataType, error.error); } @@ -36,13 +36,11 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "bool": { if(!checkPaddingLeft(bytes, 1)) { + let error = new Errors.BoolPaddingError(CodecUtils.Conversion.toHexString(bytes)); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.BoolErrorResult( - dataType, - new Errors.BoolPaddingError(CodecUtils.Conversion.toHexString(bytes)) - ); + return new Errors.BoolErrorResult(dataType, error); } const numeric = CodecUtils.Conversion.toBN(bytes); if(numeric.eqn(0)) { @@ -52,26 +50,22 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, return new Values.BoolValue(dataType, true); } else { + let error = new Errors.BoolOutOfRangeError(numeric); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.BoolErrorResult( - dataType, - new Errors.BoolOutOfRangeError(numeric) - ); + return new Errors.BoolErrorResult(dataType, error); } } case "uint": //first, check padding (if needed) if(!permissivePadding && !checkPaddingLeft(bytes, dataType.bits/8)) { + let error = new Errors.UintPaddingError(CodecUtils.Conversion.toHexString(bytes)); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.UintErrorResult( - dataType, - new Errors.UintPaddingError(CodecUtils.Conversion.toHexString(bytes)) - ); + return new Errors.UintErrorResult(dataType, error); } //now, truncate to appropriate length (keeping the bytes on the right) bytes = bytes.slice(-dataType.bits/8); @@ -83,13 +77,11 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "int": //first, check padding (if needed) if(!permissivePadding && !checkPaddingSigned(bytes, dataType.bits/8)) { + let error = new Errors.IntPaddingError(CodecUtils.Conversion.toHexString(bytes)); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.IntErrorResult( - dataType, - new Errors.IntPaddingError(CodecUtils.Conversion.toHexString(bytes)) - ); + return new Errors.IntErrorResult(dataType, error); } //now, truncate to appropriate length (keeping the bytes on the right) bytes = bytes.slice(-dataType.bits/8); @@ -101,13 +93,11 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "address": if(!permissivePadding && !checkPaddingLeft(bytes, CodecUtils.EVM.ADDRESS_SIZE)) { + let error = new Errors.AddressPaddingError(CodecUtils.Conversion.toHexString(bytes)); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.AddressErrorResult( - dataType, - new Errors.AddressPaddingError(CodecUtils.Conversion.toHexString(bytes)) - ); + return new Errors.AddressErrorResult(dataType, error); } return new Values.AddressValue( dataType, @@ -117,13 +107,11 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "contract": if(!permissivePadding && !checkPaddingLeft(bytes, CodecUtils.EVM.ADDRESS_SIZE)) { + let error = new Errors.ContractPaddingError(CodecUtils.Conversion.toHexString(bytes)); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.ContractErrorResult( - dataType, - new Errors.ContractPaddingError(CodecUtils.Conversion.toHexString(bytes)) - ); + return new Errors.ContractErrorResult(dataType, error); } const fullType = Types.fullType(dataType, info.userDefinedTypes); const contractValueInfo = (yield* decodeContract(bytes, info)); @@ -134,13 +122,11 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "static": //first, check padding (if needed) if(!permissivePadding && !checkPaddingRight(bytes, dataType.length)) { + let error = new Errors.BytesPaddingError(CodecUtils.Conversion.toHexString(bytes)); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.BytesStaticErrorResult( - dataType, - new Errors.BytesPaddingError(CodecUtils.Conversion.toHexString(bytes)) - ); + return new Errors.BytesStaticErrorResult(dataType, error); } //now, truncate to appropriate length bytes = bytes.slice(0, dataType.length); @@ -162,13 +148,11 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, switch(dataType.visibility) { case "external": if(!checkPaddingRight(bytes, CodecUtils.EVM.ADDRESS_SIZE + CodecUtils.EVM.SELECTOR_SIZE)) { + let error = new Errors.FunctionExternalNonStackPaddingError(CodecUtils.Conversion.toHexString(bytes)); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.FunctionExternalErrorResult( - dataType, - new Errors.FunctionExternalNonStackPaddingError(CodecUtils.Conversion.toHexString(bytes)) - ); + return new Errors.FunctionExternalErrorResult(dataType, error); } const address = bytes.slice(0, CodecUtils.EVM.ADDRESS_SIZE); const selector = bytes.slice(CodecUtils.EVM.ADDRESS_SIZE, CodecUtils.EVM.ADDRESS_SIZE + CodecUtils.EVM.SELECTOR_SIZE); @@ -176,10 +160,14 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, (yield* decodeExternalFunction(address, selector, info)) ); case "internal": + if(strict) { + //internal functions don't go in the ABI! + //this should never happen, but just to be sure... + throw new StopDecodingError( + new Errors.InternalFunctionInABIError() + ); + } if(!checkPaddingLeft(bytes, 2 * CodecUtils.EVM.PC_SIZE)) { - if(strict) { - throw new StopDecodingError(); - } return new Errors.FunctionInternalErrorResult( dataType, new Errors.FunctionInternalPaddingError(CodecUtils.Conversion.toHexString(bytes)) @@ -195,61 +183,51 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, const numeric = CodecUtils.Conversion.toBN(bytes); const fullType = Types.fullType(dataType, info.userDefinedTypes); if(!fullType.options) { + let error = new Errors.EnumNotFoundDecodingError(fullType, numeric); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.EnumErrorResult( - fullType, - new Errors.EnumNotFoundDecodingError(fullType, numeric) - ); + return new Errors.EnumErrorResult(fullType, error); } const numOptions = fullType.options.length; const numBytes = Math.ceil(Math.log2(numOptions) / 8); if(!checkPaddingLeft(bytes, numBytes)) { + let error = new Errors.EnumPaddingError(fullType, CodecUtils.Conversion.toHexString(bytes)); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.EnumErrorResult( - fullType, - new Errors.EnumPaddingError(fullType, CodecUtils.Conversion.toHexString(bytes)) - ); + return new Errors.EnumErrorResult(fullType, error); } if(numeric.ltn(numOptions)) { const name = fullType.options[numeric.toNumber()]; return new Values.EnumValue(fullType, numeric, name); } else { + let error = new Errors.EnumOutOfRangeError(fullType, numeric); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.EnumErrorResult( - fullType, - new Errors.EnumOutOfRangeError(fullType, numeric) - ); + return new Errors.EnumErrorResult(fullType, error); } } case "fixed": { //skipping padding check as we don't support this anyway const hex = CodecUtils.Conversion.toHexString(bytes); + let error = new Errors.FixedPointNotYetSupportedError(hex); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.FixedErrorResult( - dataType, - new Errors.FixedPointNotYetSupportedError(hex) - ); + return new Errors.FixedErrorResult(dataType, error); } case "ufixed": { //skipping padding check as we don't support this anyway const hex = CodecUtils.Conversion.toHexString(bytes); + let error = new Errors.FixedPointNotYetSupportedError(hex); if(strict) { - throw new StopDecodingError(); + throw new StopDecodingError(error); } - return new Errors.UfixedErrorResult( - dataType, - new Errors.FixedPointNotYetSupportedError(hex) - ); + return new Errors.UfixedErrorResult(dataType, error); } } } diff --git a/packages/truffle-codec/lib/types/errors.ts b/packages/truffle-codec/lib/types/errors.ts index 18a7139c621..2dd690b37ac 100644 --- a/packages/truffle-codec/lib/types/errors.ts +++ b/packages/truffle-codec/lib/types/errors.ts @@ -1,4 +1,4 @@ -import { AbiUtils } from "truffle-codec-utils"; +import { AbiUtils, Errors } from "truffle-codec-utils"; export class UnknownBaseContractIdError extends Error { public derivedId: number; @@ -42,12 +42,17 @@ export class NoDefinitionFoundForABIEntryError extends Error { } } -//used to stop decoding; apologies for the lack of details in this one, -//but this one is actually meant to be used for control flow rather than -//display, so I'm hoping that's OK +//used to stop decoding; like DecodingError, but used in contexts +//where I don't expect it to be caught +//NOTE: currently we don't actually check the type of a thrown error, +//we just rely on context. still, I think it makes sense to be a separate +//type. export class StopDecodingError extends Error { - constructor() { - const message = `Stopping decoding!`; + public error: Errors.DecoderError; + constructor(error: Errors.DecoderError) { + const message = `Stopping decoding: ${error.kind}`; //sorry about the bare-bones message, + //but again, users shouldn't actually see this, so I think this should suffice for now super(message); + this.error = error; } } diff --git a/packages/truffle-codec/lib/types/evm.ts b/packages/truffle-codec/lib/types/evm.ts index eb746a67cac..6d9d8b19ce5 100644 --- a/packages/truffle-codec/lib/types/evm.ts +++ b/packages/truffle-codec/lib/types/evm.ts @@ -56,8 +56,8 @@ export interface InternalFunction { } export interface DecoderOptions { - permissivePadding?: boolean; - strictAbiMode?: boolean; + permissivePadding?: boolean; //allows incorrect padding on certain data types + strictAbiMode?: boolean; //throw errors instead of returning; check array & string lengths (crudely) abiPointerBase?: number; memoryVisited?: string[]; //for the future } From 8b9555ed5b7a9273613572211fe5486a03a57b2d Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 4 Jul 2019 02:33:43 -0400 Subject: [PATCH 54/89] Change events() to take options object --- packages/truffle-decoder/lib/contract.ts | 15 ++++- packages/truffle-decoder/lib/types.ts | 9 ++- packages/truffle-decoder/lib/wire.ts | 17 ++++-- .../truffle-decoder/test/test/wire-test.js | 59 +++++++++---------- 4 files changed, 61 insertions(+), 39 deletions(-) diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 14b027f1738..4838718fbe0 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -9,7 +9,7 @@ import { ContractObject } from "truffle-contract-schema/spec"; import BN from "bn.js"; import { Definition as DefinitionUtils, AbiUtils, EVM, AstDefinition, AstReferences } from "truffle-codec-utils"; import { BlockType, Transaction } from "web3/eth/types"; -import { EventLog, Log } from "web3/types"; +import { Log } from "web3/types"; import { Provider } from "web3/providers"; import * as Codec from "truffle-codec"; import * as DecoderTypes from "./types"; @@ -453,7 +453,18 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { return await Promise.all(logs.map(log => this.decodeLog(log, name))); } - public async events(name: string | null = null, fromBlock: BlockType = "latest", toBlock: BlockType = "latest"): Promise { + public async events(options: DecoderTypes.EventOptions = {}): Promise { + let { name, fromBlock, toBlock } = options; + if(name === undefined) { + name = null; // null means any name is OK + } + if(fromBlock === undefined) { + fromBlock = "latest"; + } + if(toBlock === undefined) { + toBlock = "latest"; + } + const logs = await this.web3.eth.getPastLogs({ address: this.contractAddress, fromBlock, diff --git a/packages/truffle-decoder/lib/types.ts b/packages/truffle-decoder/lib/types.ts index 1f4e77c0170..f74cb79ebcb 100644 --- a/packages/truffle-decoder/lib/types.ts +++ b/packages/truffle-decoder/lib/types.ts @@ -2,7 +2,8 @@ import BN from "bn.js"; import { ContractObject } from "truffle-contract-schema/spec"; import { Values } from "truffle-codec-utils"; import { CalldataDecoding, LogDecoding } from "truffle-codec"; -import { Transaction, Log } from "web3-core"; +import { Transaction, BlockType } from "web3/eth/types"; +import { Log } from "web3/types"; export interface ContractState { name: string; @@ -40,6 +41,12 @@ export interface CodeCache { }; } +export interface EventOptions { + name?: string; + fromBlock?: BlockType; + toBlock?: BlockType; +} + export class ContractBeingDecodedHasNoNodeError extends Error { constructor() { const message = "Contract does not appear to have been compiled with Solidity (cannot locate contract node)"; diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 23616daad25..3308f00aba9 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -44,7 +44,6 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { this.contractNodes[node.id] = node; if(contract.deployedBytecode) { const context = Utils.makeContext(contract, node); - debug("context: %O", context); const hash = CodecUtils.Conversion.toHexString( CodecUtils.EVM.keccak256({type: "string", value: context.binary @@ -54,7 +53,6 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { } if(contract.bytecode) { const constructorContext = Utils.makeContext(contract, node, true); - debug("constructorContext: %O", constructorContext); const hash = CodecUtils.Conversion.toHexString( CodecUtils.EVM.keccak256({type: "string", value: constructorContext.binary @@ -66,7 +64,6 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { } this.contexts = CodecUtils.Contexts.normalizeContexts(this.contexts); - debug("contexts: %O", this.contexts); this.contextsById = Object.assign({}, ...Object.values(this.contexts).filter( ({isConstructor}) => !isConstructor ).map(context => @@ -85,7 +82,6 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { debug("init called"); [this.referenceDeclarations, this.userDefinedTypes] = this.getUserDefinedTypes(); - debug("ccbyId: %O", this.constructorContextsById); let allocationInfo: Codec.ContractAllocationInfo[] = Object.entries(this.contracts).map( ([id, { abi }]) => ({ abi: abi, @@ -221,7 +217,18 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { return await Promise.all(logs.map(log => this.decodeLog(log, name))); } - public async events(name: string | null = null, fromBlock: BlockType = "latest", toBlock: BlockType = "latest"): Promise { + public async events(options: DecoderTypes.EventOptions = {}): Promise { + let { name, fromBlock, toBlock } = options; + if(name === undefined) { + name = null; // null means any name is OK + } + if(fromBlock === undefined) { + fromBlock = "latest"; + } + if(toBlock === undefined) { + toBlock = "latest"; + } + const logs = await this.web3.eth.getPastLogs({ fromBlock, toBlock, diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index 9682e9ecf17..d86a710ab5d 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -157,36 +157,30 @@ contract("WireTest", _accounts => { //discard the error! } - let constructorEvents = await decoder.events( - null, - constructorBlock, - constructorBlock - ); - let emitStuffEvents = await decoder.events( - null, - emitStuffBlock, - emitStuffBlock - ); - let moreStuffEvents = await decoder.events( - null, - moreStuffBlock, - moreStuffBlock - ); - let inheritedEvents = await decoder.events( - null, - inheritedBlock, - inheritedBlock - ); - let indexTestEvents = await decoder.events( - null, - indexTestBlock, - indexTestBlock - ); - let libraryTestEvents = await decoder.events( - null, - libraryTestBlock, - libraryTestBlock - ); + let constructorEvents = await decoder.events({ + fromBlock: constructorBlock, + toBlock: constructorBlock + }); + let emitStuffEvents = await decoder.events({ + fromBlock: emitStuffBlock, + toBlock: emitStuffBlock + }); + let moreStuffEvents = await decoder.events({ + fromBlock: moreStuffBlock, + toBlock: moreStuffBlock + }); + let inheritedEvents = await decoder.events({ + fromBlock: inheritedBlock, + toBlock: inheritedBlock + }); + let indexTestEvents = await decoder.events({ + fromBlock: indexTestBlock, + toBlock: indexTestBlock + }); + let libraryTestEvents = await decoder.events({ + fromBlock: libraryTestBlock, + toBlock: libraryTestBlock + }); //HACK -- since danger was last, we can just ask for the //events from the latest block let dangerEvents = await decoder.events(); @@ -467,7 +461,10 @@ contract("WireTest", _accounts => { //so we don't need to use that hack here let anonymousTest = await deployedContract.anonymousTest(); let block = anonymousTest.blockNumber; - let anonymousTestEvents = await decoder.events(null, block, block); + let anonymousTestEvents = await decoder.events({ + fromBlock: block, + toBlock: block + }); assert.lengthOf(anonymousTestEvents, 3); From b9a00546fc64d768c96ffeee5630fae48ed53caf Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Fri, 5 Jul 2019 15:05:48 -0400 Subject: [PATCH 55/89] Fix decoding of events with no topics --- .../truffle-codec/lib/interface/decoding.ts | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 9dddecac75e..8b337d2bd32 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -96,22 +96,28 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName: string const allocations = info.allocations.event; debug("event allocations: %O", allocations); let rawSelector: Uint8Array; - try { + let selector: string; + let contractAllocations: {[contractId: number]: EventAllocation}; //for non-anonymous events + let libraryAllocations: {[contractId: number]: EventAllocation}; //similar + const topicsCount = info.state.eventtopics.length; + //yeah, it's not great to read directly from the state like this (bypassing read), but what are you gonna do? + if(topicsCount > 0) { rawSelector = read( { location: "eventtopic", topic: 0 }, info.state ).next().value; //no requests should occur, we can just get the first value + selector = CodecUtils.Conversion.toHexString(rawSelector); + ({ contract: contractAllocations, library: libraryAllocations } = allocations[topicsCount].bySelector[selector] || {contract: {}, library: {}}); } - catch(error) { - //if we can't read the selector, return an empty set of decodings - return []; + else { + //if we don't have a selector, it means we don't have any non-anonymous events + contractAllocations = {}; + libraryAllocations = {}; } - const selector = CodecUtils.Conversion.toHexString(rawSelector); - const topicsCount = info.state.eventtopics.length; - //yeah, it's not great to read directly from the state like this (bypassing read), but what are you gonna do? - const { contract: contractAllocations, library: libraryAllocations } = allocations[topicsCount].bySelector[selector] || {contract: {}, library: {}}; + //now: let's get our allocations for anonymous events + //note: these ones map contract IDs to *arrays* of event allocations, not individual allocations! const { contract: contractAnonymousAllocations, library: libraryAnonymousAllocations } = allocations[topicsCount].anonymous; //now: what contract are we (probably) dealing with? let's get its code to find out const codeBytes: Uint8Array = yield { From a8eeb8790688c51a5e9b7b219d06bf8f551fe050 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Fri, 5 Jul 2019 16:07:47 -0400 Subject: [PATCH 56/89] Add some more tests --- .../test/contracts/WireTest.sol | 3 ++ .../truffle-decoder/test/test/wire-test.js | 38 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/truffle-decoder/test/contracts/WireTest.sol b/packages/truffle-decoder/test/contracts/WireTest.sol index ba09df52a20..90d542ecdce 100644 --- a/packages/truffle-decoder/test/contracts/WireTest.sol +++ b/packages/truffle-decoder/test/contracts/WireTest.sol @@ -104,6 +104,7 @@ contract WireTest is WireTestParent { event AnonUints(uint indexed, uint indexed, uint indexed, uint indexed) anonymous; event NonAnon(uint indexed, uint indexed, uint indexed); + event ObviouslyAnon(byte) anonymous; function anonymousTest() public { //first test: unambiguous @@ -112,6 +113,8 @@ contract WireTest is WireTestParent { emit AnonUints(1, 2, 3, 4); //third test: uint, or not anonymous? emit NonAnon(1, 2, 3); + //fourth test: no selector + emit ObviouslyAnon(0xfe); } } diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index d86a710ab5d..cfc968d7802 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -465,8 +465,14 @@ contract("WireTest", _accounts => { fromBlock: block, toBlock: block }); + //also, let's do a test with a specified name + let specifiedNameEvents = await decoder.events({ + name: "AnonUint8s", + fromBlock: block, + toBlock: block + }); - assert.lengthOf(anonymousTestEvents, 3); + assert.lengthOf(anonymousTestEvents, 4); assert.lengthOf(anonymousTestEvents[0].decodings, 1); assert.strictEqual(anonymousTestEvents[0].decodings[0].kind, "anonymous"); @@ -544,5 +550,35 @@ contract("WireTest", _accounts => { new BN(selector.slice(2), 16) ) ); + + assert.lengthOf(anonymousTestEvents[3].decodings, 1); + assert.strictEqual(anonymousTestEvents[3].decodings[0].kind, "anonymous"); + assert.strictEqual( + anonymousTestEvents[3].decodings[0].name, + "ObviouslyAnon" + ); + assert.strictEqual( + anonymousTestEvents[3].decodings[0].class.typeName, + "WireTest" + ); + assert.lengthOf(anonymousTestEvents[3].decodings[0].arguments, 1); + assert.strictEqual( + anonymousTestEvents[3].decodings[0].arguments[0].value.nativize(), + "0xfe" + ); + + //now, let's test the specified name events + assert.lengthOf(specifiedNameEvents, 1); + let specifiedNameEvent = specifiedNameEvents[0]; + assert.lengthOf(specifiedNameEvent.decodings, 1); + let specifiedNameDecoding = specifiedNameEvent.decodings[0]; + assert.strictEqual(specifiedNameDecoding.kind, "anonymous"); + assert.strictEqual(specifiedNameDecoding.name, "AnonUint8s"); + assert.strictEqual(specifiedNameDecoding.class.typeName, "WireTestLibrary"); + assert.lengthOf(specifiedNameDecoding.arguments, 4); + assert.deepEqual( + specifiedNameDecoding.arguments.map(({ value }) => value.nativize()), + [1, 2, 3, 4] + ); }); }); From ba6f6c0297fe1d785c0501ab7ee0d83426fe9136 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Fri, 5 Jul 2019 17:41:26 -0400 Subject: [PATCH 57/89] Get rid of confusing id-allocation pairs in event decoder --- .../truffle-codec/lib/interface/decoding.ts | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 8b337d2bd32..7cd0b8b3a58 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -126,27 +126,17 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName: string }; const codeAsHex = CodecUtils.Conversion.toHexString(codeBytes); const contractContext = CodecUtils.Contexts.findDecoderContext(info.contexts, codeAsHex); - //the following two arrays contain id-allocation pairs - let possibleContractAllocations: [string, EventAllocation][]; //excludes anonymous events - let possibleContractAnonymousAllocations: [string, EventAllocation][]; - //should be number, but we have to temporarily pass through string to get compilation to work... - //(these are ID/allocation pairs) + let possibleContractAllocations: EventAllocation[]; //excludes anonymous events + let possibleContractAnonymousAllocations: EventAllocation[]; if(contractContext) { //if we found the contract, maybe it's from that contract const contractId = contractContext.contractId; const contractAllocation = contractAllocations[contractId]; const contractAnonymousAllocation = contractAnonymousAllocations[contractId]; possibleContractAllocations = contractAllocation - ? [[contractId.toString(), contractAllocation]] //array of a single pair + ? [contractAllocation] : []; - if(contractAnonymousAllocation) { - possibleContractAnonymousAllocations = contractAnonymousAllocation.map( - allocation => [contractId.toString(), allocation] - ); - } - else { - possibleContractAnonymousAllocations = []; - } + possibleContractAnonymousAllocations = contractAnonymousAllocation || []; } else { //if we couldn't determine the contract, well, we have to assume it's from a library @@ -154,22 +144,21 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName: string possibleContractAnonymousAllocations = []; } //now we get all the library allocations! - const possibleLibraryAllocations = Object.entries(libraryAllocations); - const possibleLibraryAnonymousAllocations = [].concat(...Object.entries(libraryAnonymousAllocations).map( - ([id, allocations]) => allocations.map(allocation => [id, allocation]) - )); + const possibleLibraryAllocations = Object.values(libraryAllocations); + const possibleLibraryAnonymousAllocations = [].concat(...Object.values(libraryAnonymousAllocations)); //now we put it all together! const possibleAllocations = possibleContractAllocations.concat(possibleLibraryAllocations); const possibleAnonymousAllocations = possibleContractAnonymousAllocations.concat(possibleLibraryAnonymousAllocations); const possibleAllocationsTotal = possibleAllocations.concat(possibleAnonymousAllocations); let decodings: LogDecoding[] = []; - for(const [id, allocation] of possibleAllocationsTotal) { + for(const allocation of possibleAllocationsTotal) { try { //first: do a name check so we can skip decoding if name is wrong if(targetName !== null && allocation.definition.name !== targetName) { continue; } - const attemptContext = info.contexts[parseInt(id)]; + const id = allocation.contractId; + const attemptContext = info.contexts[id]; const contractType = CodecUtils.Contexts.contextToType(attemptContext); //you can't map with a generator, so we have to do this map manually let decodedArguments: AbiArgument[] = []; From 5391cb36f18e058f1288fad5b75872019ccadad4 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 9 Jul 2019 14:24:34 -0400 Subject: [PATCH 58/89] Update function types with more information --- packages/truffle-codec-utils/src/abi.ts | 40 +++-------------- packages/truffle-codec-utils/src/contexts.ts | 2 +- .../truffle-codec-utils/src/types/errors.ts | 4 +- .../truffle-codec-utils/src/types/types.ts | 43 +++++++++++++++---- .../truffle-codec-utils/src/types/values.ts | 26 ++++++----- packages/truffle-codec/lib/decode/value.ts | 8 ++-- packages/truffle-codec/lib/types/evm.ts | 5 ++- .../lib/data/selectors/index.js | 7 ++- .../lib/solidity/selectors/index.js | 2 + 9 files changed, 70 insertions(+), 67 deletions(-) diff --git a/packages/truffle-codec-utils/src/abi.ts b/packages/truffle-codec-utils/src/abi.ts index d110af2d89f..1147e7b92a6 100644 --- a/packages/truffle-codec-utils/src/abi.ts +++ b/packages/truffle-codec-utils/src/abi.ts @@ -5,7 +5,6 @@ import { Abi as SchemaAbi } from "truffle-contract-schema/spec"; import { EVM as EVMUtils } from "./evm"; import { AstDefinition, AstReferences, Mutability } from "./ast"; import { Definition as DefinitionUtils } from "./definition"; -import { Values } from "./types/values"; import { UnknownUserDefinedTypeError } from "./errors"; import Web3 from "web3"; @@ -55,43 +54,21 @@ export namespace AbiUtils { components?: AbiParameter[]; //only preset for tuples (structs) } - export interface FunctionAbiEntryWithSelector extends FunctionAbiEntry { - signature: string; //note: this should be the SELECTOR, - //not the written-out signature. it's called "signature" - //for compatibility. + export interface FunctionAbiBySelectors { + [selector: string]: FunctionAbiEntry } - export interface EventAbiEntryWithSelector extends EventAbiEntry { - signature: string; //note: this should be the SELECTOR, - //not the written-out signature. it's called "signature" - //for compatibility. - } - - export type AbiEntryWithSelector = FunctionAbiEntryWithSelector | EventAbiEntryWithSelector; - - export interface AbiBySelectors { - [selector: string]: AbiEntryWithSelector - //note this necessary excludes constructor/fallback - } - - export function computeSelectors(abiLoose: Abi | SchemaAbi | undefined): AbiBySelectors | undefined { + //note the return value only includes functions! + export function computeSelectors(abiLoose: Abi | SchemaAbi | undefined): FunctionAbiBySelectors | undefined { if(abiLoose === undefined) { return undefined; } const abi = abiLoose; return Object.assign({}, ...abi.filter( - (abiEntry: AbiEntry) => abiEntry.type === "function" || abiEntry.type === "event" + (abiEntry: AbiEntry) => abiEntry.type === "function" ).map( - (abiEntry: FunctionAbiEntry | EventAbiEntry) => { - let signature = abiSelector(abiEntry); - return { - [signature]: { - ...abiEntry, - signature - } - }; - } + (abiEntry: FunctionAbiEntry) => ({ [abiSelector(abiEntry)]: abiEntry }) ) ) } @@ -248,11 +225,6 @@ export namespace AbiUtils { } export function abiSelector(abiEntry: FunctionAbiEntry | EventAbiEntry): string { - //first, try reading it from the entry; only recompute if needed - let storedSelector = (abiEntry).signature; - if(storedSelector !== undefined) { - return storedSelector; - } let signature = abiSignature(abiEntry); //NOTE: web3's soliditySha3 has a problem if the empty //string is passed in. Fortunately, that should never happen here. diff --git a/packages/truffle-codec-utils/src/contexts.ts b/packages/truffle-codec-utils/src/contexts.ts index ba54dc80336..101584a5b72 100644 --- a/packages/truffle-codec-utils/src/contexts.ts +++ b/packages/truffle-codec-utils/src/contexts.ts @@ -37,7 +37,7 @@ export namespace Contexts { contractName?: string; contractId?: number; contractKind?: ContractKind; //note: should never be "interface" - abi?: AbiUtils.AbiBySelectors; + abi?: AbiUtils.FunctionAbiBySelectors; payable?: boolean; compiler?: CompilerVersion; } diff --git a/packages/truffle-codec-utils/src/types/errors.ts b/packages/truffle-codec-utils/src/types/errors.ts index 4130c741db7..d14f51db356 100644 --- a/packages/truffle-codec-utils/src/types/errors.ts +++ b/packages/truffle-codec-utils/src/types/errors.ts @@ -419,7 +419,7 @@ export namespace Errors { //external functions export class FunctionExternalErrorResult extends ErrorResultBase { constructor( - public functionType: Types.FunctionTypeExternal, + public functionType: Types.FunctionExternalType, public error: GenericError | FunctionExternalError ) { super(); @@ -463,7 +463,7 @@ export namespace Errors { //Internal functions export class FunctionInternalErrorResult extends ErrorResultBase { constructor( - public functionType: Types.FunctionTypeInternal, + public functionType: Types.FunctionInternalType, public error: GenericError | FunctionInternalError ) { super(); diff --git a/packages/truffle-codec-utils/src/types/types.ts b/packages/truffle-codec-utils/src/types/types.ts index 9cfd0b46a88..7d9317a66f9 100644 --- a/packages/truffle-codec-utils/src/types/types.ts +++ b/packages/truffle-codec-utils/src/types/types.ts @@ -18,6 +18,8 @@ const debug = debugModule("codec-utils:types:types"); //Similarly with global structs and enums. //Foreign contract types aren't really implemented yet either, although //those aren't produced from definitions. +//(General external functions aren't really implemented yet either for the +//same reason.) //NOTE: not all of these optional fields are actually implemented. Some are //just intended for the future. @@ -110,9 +112,9 @@ export namespace Types { location?: "storage"; } - export type FunctionType = FunctionTypeInternal | FunctionTypeExternal; + export type FunctionType = FunctionInternalType | FunctionExternalType; - export interface FunctionTypeInternal { + export interface FunctionInternalType { typeClass: "function"; visibility: "internal"; mutability: Ast.Mutability; @@ -121,15 +123,25 @@ export namespace Types { //we do not presently support bound functions } - export interface FunctionTypeExternal { + export type FunctionExternalType = FunctionExternalTypeSpecific | FunctionExternalTypeGeneral; + + export interface FunctionExternalTypeSpecific { typeClass: "function"; visibility: "external"; + kind: "specific"; mutability: Ast.Mutability; inputParameterTypes: Type[]; outputParameterTypes: Type[]; //we do not presently support bound functions } + export interface FunctionExternalTypeGeneral { + typeClass: "function"; + visibility: "external"; + kind: "general"; + //we do not presently support bound functions + } + export type ContractDefinedType = StructTypeLocal | EnumTypeLocal; export type UserDefinedType = ContractDefinedType | ContractTypeNative | StructTypeGlobal | EnumTypeGlobal; @@ -380,13 +392,26 @@ export namespace Types { //note: don't force a location on these! use the listed location! let inputParameterTypes = inputParameters.map(parameter => definitionToType(parameter, compiler)); let outputParameterTypes = outputParameters.map(parameter => definitionToType(parameter, compiler)); - return { - typeClass, - visibility, - mutability, - inputParameterTypes, - outputParameterTypes + switch(visibility) { + case "internal": + return { + typeClass, + visibility, + mutability, + inputParameterTypes, + outputParameterTypes + }; + case "external": + return { + typeClass, + visibility, + kind: "specific", + mutability, + inputParameterTypes, + outputParameterTypes + }; } + break; //to satisfy typescript } case "struct": { let id = DefinitionUtils.typeId(definition); diff --git a/packages/truffle-codec-utils/src/types/values.ts b/packages/truffle-codec-utils/src/types/values.ts index 231a9669463..b01b2613680 100644 --- a/packages/truffle-codec-utils/src/types/values.ts +++ b/packages/truffle-codec-utils/src/types/values.ts @@ -22,8 +22,9 @@ import { Types } from "./types"; import { Errors } from "./errors"; import { InspectOptions, cleanStylize } from "./inspect"; import util from "util"; -import { AstDefinition } from "../ast"; +import { AstDefinition, Mutability } from "../ast"; import { Definition as DefinitionUtils } from "../definition"; +import { AbiUtils } from "../abi"; export namespace Values { @@ -633,7 +634,7 @@ export namespace Values { export type FunctionExternalResult = FunctionExternalValue | Errors.FunctionExternalErrorResult; export class FunctionExternalValue { - type: Types.FunctionTypeExternal; + type: Types.FunctionExternalType; kind: "value"; value: FunctionExternalValueInfo; [util.inspect.custom](depth: number | null, options: InspectOptions): string { @@ -642,7 +643,7 @@ export namespace Values { nativize(): any { return this.value.nativize(); } - constructor(functionType: Types.FunctionTypeExternal, value: FunctionExternalValueInfo) { + constructor(functionType: Types.FunctionExternalType, value: FunctionExternalValueInfo) { this.type = functionType; this.kind = "value"; this.value = value; @@ -660,24 +661,24 @@ export namespace Values { kind: "known"; contract: ContractValueInfoKnown; selector: string; //formatted as a bytes4 - name: string; + abi: AbiUtils.FunctionAbiEntry; [util.inspect.custom](depth: number | null, options: InspectOptions): string { //first, let's inspect that contract, but w/o color let contractString = util.inspect(this.contract, { ...cleanStylize(options), colors: false }); - let firstLine = `[Function: ${this.name} of`; + let firstLine = `[Function: ${this.abi.name} of`; let secondLine = `${contractString}]`; let breakingSpace = firstLine.length >= options.breakLength ? "\n" : " "; //now, put it together return options.stylize(firstLine + breakingSpace + secondLine, "special"); } nativize(): any { - return `${this.contract.nativize()}.${this.name}` + return `${this.contract.nativize()}.${this.abi.name}` } - constructor(contract: ContractValueInfoKnown, selector: string, name: string) { + constructor(contract: ContractValueInfoKnown, selector: string, abi: AbiUtils.FunctionAbiEntry) { this.kind = "known"; this.contract = contract; this.selector = selector; - this.name = name; + this.abi = abi; } } @@ -739,7 +740,7 @@ export namespace Values { export type FunctionInternalResult = FunctionInternalValue | Errors.FunctionInternalErrorResult; export class FunctionInternalValue { - type: Types.FunctionTypeInternal; + type: Types.FunctionInternalType; kind: "value"; value: FunctionInternalValueInfo; [util.inspect.custom](depth: number | null, options: InspectOptions): string { @@ -748,7 +749,7 @@ export namespace Values { nativize(): any { return this.value.nativize(); } - constructor(functionType: Types.FunctionTypeInternal, value: FunctionInternalValueInfo) { + constructor(functionType: Types.FunctionInternalType, value: FunctionInternalValueInfo) { this.type = functionType; this.kind = "value"; this.value = value; @@ -769,6 +770,7 @@ export namespace Values { constructorProgramCounter: number; name: string; definedIn: Types.ContractType; + mutability?: Mutability; [util.inspect.custom](depth: number | null, options: InspectOptions): string { return options.stylize(`[Function: ${this.definedIn.typeName}.${this.name}]`, "special"); } @@ -780,7 +782,8 @@ export namespace Values { deployedProgramCounter: number, constructorProgramCounter: number, name: string, - definedIn: Types.ContractType + definedIn: Types.ContractType, + mutability?: Mutability ) { this.kind = "function"; this.context = context; @@ -788,6 +791,7 @@ export namespace Values { this.constructorProgramCounter = constructorProgramCounter; this.name = name; this.definedIn = definedIn; + this.mutability = mutability; } } diff --git a/packages/truffle-codec/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts index 9f3d33e91dc..75e2f5cf21f 100644 --- a/packages/truffle-codec/lib/decode/value.ts +++ b/packages/truffle-codec/lib/decode/value.ts @@ -289,12 +289,11 @@ export function* decodeExternalFunction(addressBytes: Uint8Array, selectorBytes: if(abiEntry === undefined) { return new Values.FunctionExternalValueInfoInvalid(contract, selector) } - let functionName = abiEntry.name; - return new Values.FunctionExternalValueInfoKnown(contract, selector, functionName) + return new Values.FunctionExternalValueInfoKnown(contract, selector, abiEntry) } //this one works a bit differently -- in order to handle errors, it *does* return a FunctionInternalResult -export function decodeInternalFunction(dataType: Types.FunctionTypeInternal, deployedPcBytes: Uint8Array, constructorPcBytes: Uint8Array, info: EvmInfo): Values.FunctionInternalResult { +export function decodeInternalFunction(dataType: Types.FunctionInternalType, deployedPcBytes: Uint8Array, constructorPcBytes: Uint8Array, info: EvmInfo): Values.FunctionInternalResult { let deployedPc: number = CodecUtils.Conversion.toBN(deployedPcBytes).toNumber(); let constructorPc: number = CodecUtils.Conversion.toBN(constructorPcBytes).toNumber(); let context: Types.ContractType = { @@ -353,6 +352,7 @@ export function decodeInternalFunction(dataType: Types.FunctionTypeInternal, dep ); } let name = functionEntry.name; + let mutability = functionEntry.mutability; let definedIn: Types.ContractType = { typeClass: "contract", kind: "native", @@ -363,7 +363,7 @@ export function decodeInternalFunction(dataType: Types.FunctionTypeInternal, dep }; return new Values.FunctionInternalValue( dataType, - new Values.FunctionInternalValueInfoKnown(context, deployedPc, constructorPc, name, definedIn) + new Values.FunctionInternalValueInfoKnown(context, deployedPc, constructorPc, name, definedIn, mutability) ); } diff --git a/packages/truffle-codec/lib/types/evm.ts b/packages/truffle-codec/lib/types/evm.ts index 6d9d8b19ce5..cf5d89f719a 100644 --- a/packages/truffle-codec/lib/types/evm.ts +++ b/packages/truffle-codec/lib/types/evm.ts @@ -1,4 +1,4 @@ -import { AstDefinition, AstReferences, ContractKind, Contexts, Types } from "truffle-codec-utils"; +import { AstDefinition, AstReferences, ContractKind, Mutability, Contexts, Types } from "truffle-codec-utils"; import * as Allocations from "./allocation"; import { Slot } from "./storage"; @@ -46,6 +46,7 @@ export interface InternalFunction { node?: AstDefinition; name?: string; id?: number; + mutability?: Mutability; contractPointer?: string; contractNode?: AstDefinition; contractName?: string; @@ -59,5 +60,5 @@ export interface DecoderOptions { permissivePadding?: boolean; //allows incorrect padding on certain data types strictAbiMode?: boolean; //throw errors instead of returning; check array & string lengths (crudely) abiPointerBase?: number; - memoryVisited?: string[]; //for the future + memoryVisited?: number[]; //for the future } diff --git a/packages/truffle-debugger/lib/data/selectors/index.js b/packages/truffle-debugger/lib/data/selectors/index.js index 52c2069bf78..7258a819ea6 100644 --- a/packages/truffle-debugger/lib/data/selectors/index.js +++ b/packages/truffle-debugger/lib/data/selectors/index.js @@ -235,10 +235,9 @@ const data = createSelectorTree({ * 0. we only include non-constructor contexts * 1. we now index by contract ID rather than hash * 2. we strip out context, sourceMap, and primarySource - * 3. we alter abi in several ways: - * 3a. we strip out constructors and fallback functions - * 3b. we augment these functions with signatures (here meaning selectors) - * 3c. abi is now an object, not an array, and indexed by these signatures + * 3. we alter abi in two ways: + * 3a. we strip out everything but functions + * 3b. abi is now an object, not an array, and indexed by these signatures */ contexts: createLeaf([evm.info.contexts], contexts => Object.assign( diff --git a/packages/truffle-debugger/lib/solidity/selectors/index.js b/packages/truffle-debugger/lib/solidity/selectors/index.js index bbd98b1840f..52374f07631 100644 --- a/packages/truffle-debugger/lib/solidity/selectors/index.js +++ b/packages/truffle-debugger/lib/solidity/selectors/index.js @@ -4,6 +4,7 @@ const debug = debugModule("debugger:solidity:selectors"); import { createSelectorTree, createLeaf } from "reselect-tree"; import SolidityUtils from "truffle-solidity-utils"; import CodeUtils from "truffle-code-utils"; +import { Definition as DefinitionUtils } from "truffle-codec-utils"; import { findRange } from "lib/ast/map"; import jsonpointer from "json-pointer"; @@ -323,6 +324,7 @@ let solidity = createSelectorTree({ node, name: node.name, id: node.id, + mutability: DefinitionUtils.mutability(node), contractPointer, contractNode, contractName: contractNode.name, From c6c04512d59025de04d01fa7155ce9af03d80260 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 9 Jul 2019 14:48:25 -0400 Subject: [PATCH 59/89] Add address parameter to events(); simplify handling of other options --- .../truffle-codec/lib/interface/decoding.ts | 4 ++-- packages/truffle-decoder/lib/contract.ts | 14 +++----------- packages/truffle-decoder/lib/types.ts | 1 + packages/truffle-decoder/lib/wire.ts | 18 +++++------------- 4 files changed, 11 insertions(+), 26 deletions(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 7cd0b8b3a58..f3e33c4ea39 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -92,7 +92,7 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator { +export function* decodeEvent(info: EvmInfo, address: string, targetName?: string): IterableIterator { const allocations = info.allocations.event; debug("event allocations: %O", allocations); let rawSelector: Uint8Array; @@ -154,7 +154,7 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName: string for(const allocation of possibleAllocationsTotal) { try { //first: do a name check so we can skip decoding if name is wrong - if(targetName !== null && allocation.definition.name !== targetName) { + if(targetName !== undefined && allocation.definition.name !== targetName) { continue; } const id = allocation.contractId; diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 4838718fbe0..d0382844d99 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -410,7 +410,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } //NOTE: will only work with logs for this address! - public async decodeLog(log: Log, name: string | null = null): Promise { + public async decodeLog(log: Log, name?: string): Promise { if(log.address !== this.contractAddress) { throw new DecoderTypes.EventOrTransactionIsNotForThisContractError(log.address, this.contractAddress); } @@ -449,21 +449,13 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } //NOTE: will only work with logs for this address! - public async decodeLogs(logs: Log[], name: string | null = null): Promise { + public async decodeLogs(logs: Log[], name?: string): Promise { return await Promise.all(logs.map(log => this.decodeLog(log, name))); } public async events(options: DecoderTypes.EventOptions = {}): Promise { let { name, fromBlock, toBlock } = options; - if(name === undefined) { - name = null; // null means any name is OK - } - if(fromBlock === undefined) { - fromBlock = "latest"; - } - if(toBlock === undefined) { - toBlock = "latest"; - } + //note: address option is ignored! const logs = await this.web3.eth.getPastLogs({ address: this.contractAddress, diff --git a/packages/truffle-decoder/lib/types.ts b/packages/truffle-decoder/lib/types.ts index f74cb79ebcb..0fc02cf4714 100644 --- a/packages/truffle-decoder/lib/types.ts +++ b/packages/truffle-decoder/lib/types.ts @@ -45,6 +45,7 @@ export interface EventOptions { name?: string; fromBlock?: BlockType; toBlock?: BlockType; + address?: string; //ignored by contract decoder! } export class ContractBeingDecodedHasNoNodeError extends Error { diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 3308f00aba9..7f3a5c49d8a 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -178,7 +178,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - public async decodeLog(log: Log, name: string | null = null): Promise { + public async decodeLog(log: Log, name?: string): Promise { const block = log.blockNumber; const data = CodecUtils.Conversion.toBytes(log.data); const topics = log.topics.map(CodecUtils.Conversion.toBytes); @@ -213,23 +213,15 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - public async decodeLogs(logs: Log[], name: string | null = null): Promise { + public async decodeLogs(logs: Log[], name?: string): Promise { return await Promise.all(logs.map(log => this.decodeLog(log, name))); } public async events(options: DecoderTypes.EventOptions = {}): Promise { - let { name, fromBlock, toBlock } = options; - if(name === undefined) { - name = null; // null means any name is OK - } - if(fromBlock === undefined) { - fromBlock = "latest"; - } - if(toBlock === undefined) { - toBlock = "latest"; - } + let { address, name, fromBlock, toBlock } = options; const logs = await this.web3.eth.getPastLogs({ + address, fromBlock, toBlock, }); @@ -240,7 +232,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { //if a target name was specified, we'll restrict to events that decoded //to something with that name. (note that only decodings with that name //will have been returned from decodeLogs in the first place) - if(name !== null) { + if(name !== undefined) { events = events.filter( event => event.decodings.length > 0 ); From 50c023e99b123051ce8016a7cc98e34eaec9072f Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 9 Jul 2019 15:18:21 -0400 Subject: [PATCH 60/89] Account for absent "function" type in abi entries --- packages/truffle-codec-utils/src/abi.ts | 20 +++++++++++++------ .../truffle-debugger/lib/session/index.js | 3 +++ packages/truffle-decoder/lib/contract.ts | 6 +++--- packages/truffle-decoder/lib/utils.ts | 5 +++-- packages/truffle-decoder/lib/wire.ts | 2 +- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/truffle-codec-utils/src/abi.ts b/packages/truffle-codec-utils/src/abi.ts index 1147e7b92a6..ffaaaffc630 100644 --- a/packages/truffle-codec-utils/src/abi.ts +++ b/packages/truffle-codec-utils/src/abi.ts @@ -10,6 +10,8 @@ import Web3 from "web3"; //NOTE: SchemaAbi is kind of loose and a pain to use. //So we'll generally coerce things to Abi before use. +//(we combine this with adding a "function" tag for +//entries that lack it) export namespace AbiUtils { @@ -58,12 +60,19 @@ export namespace AbiUtils { [selector: string]: FunctionAbiEntry } + export function schemaAbiToAbi(abiLoose: SchemaAbi): Abi { + return abiLoose.map( + entry => entry.type + ? entry + : {type: "function", ...entry} + ); + } + //note the return value only includes functions! - export function computeSelectors(abiLoose: Abi | SchemaAbi | undefined): FunctionAbiBySelectors | undefined { - if(abiLoose === undefined) { + export function computeSelectors(abi: Abi | undefined): FunctionAbiBySelectors | undefined { + if(abi === undefined) { return undefined; } - const abi = abiLoose; return Object.assign({}, ...abi.filter( (abiEntry: AbiEntry) => abiEntry.type === "function" @@ -74,11 +83,10 @@ export namespace AbiUtils { } //does this ABI have a payable fallback function? - export function abiHasPayableFallback(abiLoose: Abi | SchemaAbi | undefined): boolean | undefined { - if(abiLoose === undefined) { + export function abiHasPayableFallback(abi: Abi | undefined): boolean | undefined { + if(abi === undefined) { return undefined; } - const abi = abiLoose; return abi.some( (abiEntry: AbiEntry) => abiEntry.type === "fallback" && abiMutability(abiEntry) === "payable" diff --git a/packages/truffle-debugger/lib/session/index.js b/packages/truffle-debugger/lib/session/index.js index e0ad2f35372..bf0e82d69f5 100644 --- a/packages/truffle-debugger/lib/session/index.js +++ b/packages/truffle-debugger/lib/session/index.js @@ -1,6 +1,8 @@ import debugModule from "debug"; const debug = debugModule("debugger:session"); +import { AbiUtils } from "truffle-codec-utils"; + import configureStore from "lib/store"; import * as controller from "lib/controller/actions"; @@ -129,6 +131,7 @@ export default class Session { let contractId = contractNode.id; let contractKind = contractNode.contractKind; + abi = AbiUtils.schemAbiToAbi(abi); //let's handle this up front debug("contractName %s", contractName); debug("sourceMap %o", sourceMap); diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index d0382844d99..8d39d550da9 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -134,7 +134,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { ). map( ([id, { abi }]) => ({ - abi: abi, + abi: AbiUtils.schemaAbiToAbi(abi), id: parseInt(id) }) ); @@ -147,7 +147,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.allocations.abi = Codec.getAbiAllocations(this.referenceDeclarations); this.allocations.calldata = Codec.getCalldataAllocations( [{ - abi: this.contract.abi, + abi: AbiUtils.schemaAbiToAbi(this.contract.abi), id: this.contractNode.id, constructorContext: this.constructorContext }], @@ -157,7 +157,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.allocations.event = Codec.getEventAllocations( [ { - abi: this.contract.abi, + abi: AbiUtils.schemaAbiToAbi(this.contract.abi), id: this.contractNode.id }, ...libraryAllocationInfo diff --git a/packages/truffle-decoder/lib/utils.ts b/packages/truffle-decoder/lib/utils.ts index e2803b2964b..d1ccb38ff79 100644 --- a/packages/truffle-decoder/lib/utils.ts +++ b/packages/truffle-decoder/lib/utils.ts @@ -11,14 +11,15 @@ export function getContractNode(contract: ContractObject): AstDefinition { } export function makeContext(contract: ContractObject, node: AstDefinition, isConstructor = false): ContextsUtils.DecoderContext { + let abi = AbiUtils.schemaAbiToAbi(contract.abi); return { contractName: contract.contractName, binary: isConstructor ? contract.bytecode : contract.deployedBytecode, contractId: node.id, contractKind: node.contractKind, isConstructor, - abi: AbiUtils.computeSelectors(contract.abi), - payable: AbiUtils.abiHasPayableFallback(contract.abi), + abi: AbiUtils.computeSelectors(abi), + payable: AbiUtils.abiHasPayableFallback(abi), compiler: contract.compiler }; } diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 7f3a5c49d8a..48906a950c9 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -84,7 +84,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { let allocationInfo: Codec.ContractAllocationInfo[] = Object.entries(this.contracts).map( ([id, { abi }]) => ({ - abi: abi, + abi: AbiUtils.schemaAbiToAbi(abi), id: parseInt(id), constructorContext: this.constructorContextsById[parseInt(id)] }) From beeb90fc333cee3f5383e34f1330f128b5d9eed0 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 9 Jul 2019 18:30:58 -0400 Subject: [PATCH 61/89] Account for getters in calldata allocation --- packages/truffle-codec-utils/src/abi.ts | 130 ++------- .../truffle-codec-utils/src/definition.ts | 259 +++++++++++++++++- packages/truffle-codec/lib/allocate/abi.ts | 111 ++++---- 3 files changed, 345 insertions(+), 155 deletions(-) diff --git a/packages/truffle-codec-utils/src/abi.ts b/packages/truffle-codec-utils/src/abi.ts index ffaaaffc630..57165cb2ca8 100644 --- a/packages/truffle-codec-utils/src/abi.ts +++ b/packages/truffle-codec-utils/src/abi.ts @@ -5,7 +5,6 @@ import { Abi as SchemaAbi } from "truffle-contract-schema/spec"; import { EVM as EVMUtils } from "./evm"; import { AstDefinition, AstReferences, Mutability } from "./ast"; import { Definition as DefinitionUtils } from "./definition"; -import { UnknownUserDefinedTypeError } from "./errors"; import Web3 from "web3"; //NOTE: SchemaAbi is kind of loose and a pain to use. @@ -107,109 +106,6 @@ export namespace AbiUtils { return "nonpayable"; } - //note: in future, this will be replaced by a toABI function, - //which will also work for variable declarations - export function matchesAbi(abiEntry: AbiEntry, node: AstDefinition, referenceDeclarations: AstReferences): boolean { - //first: does the basic name and type match? - switch(node.nodeType) { - case "FunctionDefinition": - if(node.visibility !== "external" && node.visibility !== "public") { - return false; - } - if(abiEntry.type !== DefinitionUtils.functionKind(node)) { - return false; - } - if(abiEntry.type === "function") { - if(node.name !== abiEntry.name) { - return false; - } - } - break; - case "EventDefinition": - if(abiEntry.type !== "event") { - return false; - } - if(node.name !== abiEntry.name) { - return false; - } - break; - default: - return false; - } - //if it's a fallback function, we're done - if(abiEntry.type === "fallback") { - return true; - } - //otherwise, we've got to start checking input types (we don't check output types) - return matchesAbiParameters(abiEntry.inputs, node.parameters.parameters, referenceDeclarations); - } - - function matchesAbiParameters(abiParameters: AbiParameter[], nodeParameters: AstDefinition[], referenceDeclarations: AstReferences): boolean { - if(abiParameters.length !== nodeParameters.length) { - return false; - } - for(let i = 0; i < abiParameters.length; i++) { - if(!matchesAbiType(abiParameters[i], nodeParameters[i], referenceDeclarations)) { - return false; - } - } - return true; - } - - function matchesAbiType(abiParameter: AbiParameter, nodeParameter: AstDefinition, referenceDeclarations: AstReferences): boolean { - if(toAbiType(nodeParameter, referenceDeclarations) !== abiParameter.type) { - return false; - } - if(abiParameter.type.startsWith("tuple")) { - let referenceId = DefinitionUtils.typeId(nodeParameter); - let referenceDeclaration = referenceDeclarations[referenceId]; - if(referenceDeclaration === undefined) { - let typeString = DefinitionUtils.typeString(nodeParameter); - throw new UnknownUserDefinedTypeError(referenceId, typeString); - } - return matchesAbiParameters(abiParameter.components, referenceDeclaration.members, referenceDeclarations); - } - else { - return true; - } - } - - //note: this is only meant for types that can go in the ABI - //it returns how that type is notated in the ABI -- just the string, - //to be clear, not components of tuples - function toAbiType(definition: AstDefinition, referenceDeclarations: AstReferences): string { - let basicType = DefinitionUtils.typeClassLongForm(definition); //get that whole first segment! - switch(basicType) { - case "contract": - return "address"; - case "struct": - return "tuple"; //the more detailed checking will be handled elsewhere - case "enum": - let referenceId = DefinitionUtils.typeId(definition); - let referenceDeclaration = referenceDeclarations[referenceId]; - if(referenceDeclaration === undefined) { - let typeString = DefinitionUtils.typeString(definition); - throw new UnknownUserDefinedTypeError(referenceId, typeString); - } - let numOptions = referenceDeclaration.members.length; - let bits = 8 * Math.ceil(Math.log2(numOptions) / 8); - return `uint${bits}`; - case "array": - let baseType = toAbiType(DefinitionUtils.baseDefinition(definition), referenceDeclarations); - return DefinitionUtils.isDynamicArray(definition) - ? `${baseType}[]` - : `${baseType}[${DefinitionUtils.staticLength(definition)}]`; - default: - return basicType; - //note that: int/uint/fixed/ufixed/bytes will have their size and such left on; - //address will have "payable" left off; - //external functions will be reduced to "function" (and internal functions shouldn't - //be passed in!) - //(mappings shouldn't be passed in either obviously) - } - } - - //NOTE: this function returns the written out SIGNATURE, not the SELECTOR export function abiSignature(abiEntry: FunctionAbiEntry | EventAbiEntry): string { return abiEntry.name + abiTupleSignature(abiEntry.inputs); @@ -245,6 +141,32 @@ export namespace AbiUtils { } } + //note: undefined does not match itself :P + export function abisMatch(entry1: AbiEntry | undefined, entry2: AbiEntry | undefined): boolean { + //we'll consider two abi entries to match if they have the same + //type, name (if applicable), and inputs (if applicable). + //since there's already a signature function, we can just use that. + if(!entry1 || !entry2) { + return false; + } + if(entry1.type !== entry2.type) { + return false; + } + switch(entry1.type) { + case "function": + case "event": + return abiSignature(entry1) === abiSignature(entry2); + case "constructor": + return abiTupleSignature(entry1.inputs) === abiTupleSignature((entry2).inputs); + case "fallback": + return true; + } + } + + export function definitionMatchesAbi(abiEntry: AbiEntry, definition: AstDefinition, referenceDeclarations: AstReferences): boolean { + return abisMatch(abiEntry, DefinitionUtils.definitionToAbi(definition, referenceDeclarations)); + } + export function topicsCount(abiEntry: EventAbiEntry): number { let selectorCount = abiEntry.anonymous ? 0 : 1; //if the event is not anonymous, we must account for the selector return abiEntry.inputs.filter(({ indexed }) => indexed).length + selectorCount; diff --git a/packages/truffle-codec-utils/src/definition.ts b/packages/truffle-codec-utils/src/definition.ts index 5d7b9fe195c..d94b957760c 100644 --- a/packages/truffle-codec-utils/src/definition.ts +++ b/packages/truffle-codec-utils/src/definition.ts @@ -2,9 +2,11 @@ import debugModule from "debug"; const debug = debugModule("codec-utils:definition"); import { EVM as EVMUtils } from "./evm"; -import { AstDefinition, Scopes, Visibility, Mutability, Location, ContractKind } from "./ast"; +import { AstDefinition, AstReferences, Scopes, Visibility, Mutability, Location, ContractKind } from "./ast"; import { Contexts } from "./contexts"; import { CompilerVersion } from "./compiler"; +import { AbiUtils } from "./abi"; +import { UnknownUserDefinedTypeError } from "./errors"; import BN from "bn.js"; import cloneDeep from "lodash.clonedeep"; import semver from "semver"; @@ -78,7 +80,7 @@ export namespace Definition { return num; default: - // debug("Unknown type for size specification: %s", typeIdentifier(definition)); + debug("Unknown type for size specification: %s", typeIdentifier(definition)); } } @@ -288,7 +290,7 @@ export namespace Definition { //returns input parameters, then output parameters export function parameters(definition: AstDefinition): [AstDefinition[], AstDefinition[]] { let typeObject = definition.typeName || definition; - return [typeObject.parameterTypes.parameters, typeObject.returnParameterTypes.parameters]; + return [typeObject.parameters.parameters, typeObject.returnParameters.parameters]; } //compatibility function, since pre-0.5.0 functions don't have node.kind @@ -413,4 +415,255 @@ export namespace Definition { } } + //section: converting a definition to an ABI entry! + + //the main function. just does some dispatch. + //returns undefined on bad input + export function definitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.AbiEntry | undefined { + switch(node.nodeType) { + case "FunctionDefinition": + if(node.visibility === "public" || node.visibility === "external") { + return functionDefinitionToAbi(node, referenceDeclarations); + } + else { + return undefined; + } + case "EventDefinition": + return eventDefinitionToAbi(node, referenceDeclarations); + case "VariableDeclaration": + if(node.visibility === "public") { + return getterDefinitionToAbi(node, referenceDeclarations); + } + else { + return undefined; + } + default: + return undefined; + } + } + + //note: not for FunctionTypeNames or VariableDeclarations + function functionDefinitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.FunctionAbiEntry | AbiUtils.ConstructorAbiEntry | AbiUtils.FallbackAbiEntry { + let kind = functionKind(node); + let stateMutability = mutability(node); + let payable = stateMutability === "payable"; + let constant = stateMutability === "view" || stateMutability == "pure"; + let inputs; + switch(kind) { + case "function": + let name = node.name; + let outputs = parametersToAbi(node.returnParameters.parameters, referenceDeclarations); + inputs = parametersToAbi(node.parameters.parameters, referenceDeclarations); + return { + type: "function", + name, + inputs, + outputs, + stateMutability, + constant, + payable + }; + case "constructor": + inputs = parametersToAbi(node.parameters.parameters, referenceDeclarations); + //note: need to coerce because of mutability restructions + return { + type: "constructor", + inputs, + stateMutability, + payable + }; + case "fallback": + //note: need to coerce because of mutability restructions + return { + type: "fallback", + stateMutability, + payable + }; + } + } + + function eventDefinitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.EventAbiEntry { + let inputs = parametersToAbi(node.parameters.parameters, referenceDeclarations, true); + let name = node.name; + let anonymous = node.anonymous; + return { + type: "event", + inputs, + name, + anonymous + }; + } + + function parametersToAbi(nodes: AstDefinition[], referenceDeclarations: AstReferences, checkIndexed: boolean = false): AbiUtils.AbiParameter[] { + return nodes.map(node => parameterToAbi(node, referenceDeclarations, checkIndexed)); + } + + function parameterToAbi(node: AstDefinition, referenceDeclarations: AstReferences, checkIndexed: boolean = false): AbiUtils.AbiParameter { + let name = node.name; //may be the empty string + let abiTypeString = toAbiType(node, referenceDeclarations); + let components: AbiUtils.AbiParameter[]; + if(abiTypeString.startsWith("tuple")) { + let baseType = node.typeName; + while(typeClass(baseType) === "array") { + baseType = baseDefinition(baseType); + } + let id = typeId(baseType); + let referenceDeclaration = referenceDeclarations[id]; + if(referenceDeclaration === undefined) { + let typeToDisplay = typeString(baseType); + throw new UnknownUserDefinedTypeError(id, typeToDisplay); + } + components = parametersToAbi(referenceDeclaration.members, referenceDeclarations, checkIndexed); + } + if(checkIndexed) { + return { + name, + type: abiTypeString, + indexed: node.indexed, + components + }; + } + else { + return { + name, + type: abiTypeString, + components + }; + } + } + + //note: this is only meant for types that can go in the ABI + //it returns how that type is notated in the ABI -- just the string, + //to be clear, not components of tuples + function toAbiType(node: AstDefinition, referenceDeclarations: AstReferences): string { + let basicType = typeClassLongForm(node); //get that whole first segment! + switch(basicType) { + case "contract": + return "address"; + case "struct": + return "tuple"; //the more detailed checking will be handled elsewhere + case "enum": + let referenceId = typeId(node); + let referenceDeclaration = referenceDeclarations[referenceId]; + if(referenceDeclaration === undefined) { + let typeToDisplay = typeString(node); + throw new UnknownUserDefinedTypeError(referenceId, typeToDisplay); + } + let numOptions = referenceDeclaration.members.length; + let bits = 8 * Math.ceil(Math.log2(numOptions) / 8); + return `uint${bits}`; + case "array": + let baseType = toAbiType(baseDefinition(node), referenceDeclarations); + return isDynamicArray(node) + ? `${baseType}[]` + : `${baseType}[${staticLength(node)}]`; + default: + return basicType; + //note that: int/uint/fixed/ufixed/bytes will have their size and such left on; + //address will have "payable" left off; + //external functions will be reduced to "function" (and internal functions shouldn't + //be passed in!) + //(mappings shouldn't be passed in either obviously) + } + } + + function getterDefinitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.FunctionAbiEntry { + let name = node.name; + let inputs = getterInputs(node); //does not depend on reference declarations! + let outputs = getterOutputs(node, referenceDeclarations); + return { + type: "function", + name, + inputs, + outputs, + stateMutability: "view", + constant: true, + payable: false + }; + } + + //array getters & mapping getters take inputs; if stacked they take multiple inputs + //struct getters do not take inputs + function getterInputs(node: AstDefinition): AbiUtils.AbiParameter[] { + node = node.typeName; + let inputs: AbiUtils.AbiParameter[] = []; + while(typeClass(node) === "array" || typeClass(node) === "mapping") { + let keyNode = keyDefinition(node); //note: if node is an array, this spoofs up a uint256 definition + let parameterAbi = parameterToAbi(keyNode, null); //it's an elementary type; no need for ref declarations + parameterAbi.name = ""; //this might be garbage, let's overwrite it with the correct value (empty string) + inputs.push(parameterAbi); + switch(typeClass(node)) { + case "array": + node = node.baseType; + break; + case "mapping": + node = node.valueType; + break; + } + } + return inputs; + } + + //this is similar to the above, but it returns an array of definitions instead + //(for use by the allocator) + export function getterInputsAsDefinitions(node: AstDefinition): AstDefinition[] { + node = node.typeName; + let inputs: AstDefinition[] = []; + while(typeClass(node) === "array" || typeClass(node) === "mapping") { + let keyNode = keyDefinition(node); //note: if node is an array, this spoofs up a uint256 definition + inputs.push(keyNode); + switch(typeClass(node)) { + case "array": + node = node.baseType; + break; + case "mapping": + node = node.valueType; + break; + } + } + return inputs; + } + + //most getters return a single output. + //however, struct getters (or array of struct, or mapping to struct) + //returns multiple outputs, those outputs being the members of the + //struct *other* than arrays or mappings. + //Note that any deeper nested structs are *not* split up or filtered + //in this way! + function getterOutputs(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.AbiParameter[] { + let baseNode: AstDefinition = node.typeName; + while(typeClass(baseNode) === "array" || typeClass(baseNode) === "mapping") { + switch(typeClass(baseNode)) { + case "array": + baseNode = baseNode.baseType; + break; + case "mapping": + baseNode = baseNode.valueType; + break; + } + } + //at this point, baseNode should hold the base type + //now we face the question: is it a struct? + if(typeClass(baseNode) === "struct") { + let id = typeId(baseNode); + let referenceDeclaration = referenceDeclarations[id]; + if(referenceDeclaration === undefined) { + let typeToDisplay = typeString(baseNode); + throw new UnknownUserDefinedTypeError(id, typeToDisplay); + } + return referenceDeclaration.members.filter( + member => typeClass(member) !== "array" && typeClass(member) !== "mapping" + ).map( + member => parameterToAbi(member, referenceDeclarations) + ); + } + else { + return [{ + name: "", + type: toAbiType(baseNode, referenceDeclarations), + //it's not a struct or an array; there are no components + }]; + } + } + } diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index 4cf123c5608..1e94cbec283 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -96,70 +96,70 @@ function abiSizeAndAllocate(definition: AstDefinition, referenceDeclarations?: A case "ufixed": case "enum": return { - size: CodecUtils.EVM.WORD_SIZE, - dynamic: false, - allocations: existingAllocations + size: CodecUtils.EVM.WORD_SIZE, + dynamic: false, + allocations: existingAllocations }; case "string": return { - size: CodecUtils.EVM.WORD_SIZE, - dynamic: true, - allocations: existingAllocations + size: CodecUtils.EVM.WORD_SIZE, + dynamic: true, + allocations: existingAllocations }; case "bytes": return { - size: CodecUtils.EVM.WORD_SIZE, - dynamic: CodecUtils.Definition.specifiedSize(definition) == null, - allocations: existingAllocations + size: CodecUtils.EVM.WORD_SIZE, + dynamic: CodecUtils.Definition.specifiedSize(definition) == null, + allocations: existingAllocations }; case "mapping": return { - allocations: existingAllocations + allocations: existingAllocations }; case "function": switch (CodecUtils.Definition.visibility(definition)) { case "external": - return { - size: CodecUtils.EVM.WORD_SIZE, - dynamic: false, - allocations: existingAllocations - }; + return { + size: CodecUtils.EVM.WORD_SIZE, + dynamic: false, + allocations: existingAllocations + }; case "internal": - return { - allocations: existingAllocations - }; + return { + allocations: existingAllocations + }; } case "array": { if(CodecUtils.Definition.isDynamicArray(definition)) { - return { - size: CodecUtils.EVM.WORD_SIZE, - dynamic: true, - allocations: existingAllocations - }; + return { + size: CodecUtils.EVM.WORD_SIZE, + dynamic: true, + allocations: existingAllocations + }; } else { //static array case const length: number = CodecUtils.Definition.staticLength(definition); if(length === 0) { //arrays of length 0 are static regardless of base type - return { - size: 0, - dynamic: false, - allocations: existingAllocations - }; + return { + size: 0, + dynamic: false, + allocations: existingAllocations + }; } const baseDefinition: AstDefinition = definition.baseType || definition.typeName.baseType; const {size: baseSize, dynamic, allocations} = abiSizeAndAllocate(baseDefinition, referenceDeclarations, existingAllocations); - return { - size: length * baseSize, - dynamic, - allocations - }; + return { + size: length * baseSize, + dynamic, + allocations + }; } } @@ -179,17 +179,17 @@ function abiSizeAndAllocate(definition: AstDefinition, referenceDeclarations?: A } //having found our allocation, if it's not null, we can just look up its size and dynamicity if(allocation !== null) { - return { - size: allocation.length, - dynamic: allocation.dynamic, - allocations - }; + return { + size: allocation.length, + dynamic: allocation.dynamic, + allocations + }; } //if it is null, this type doesn't go in the abi else { - return { - allocations - }; + return { + allocations + }; } } } @@ -263,9 +263,11 @@ function allocateCalldata( case "constructor": let rawLength = constructorContext.binary.length; offset = (rawLength - 2)/2; //number of bytes in 0x-prefixed bytestring - //for a constructor, we only want to search the particular contract, which + //for a constructor, we only want to search the particular contract node = contractNode.nodes.find( - functionNode => AbiUtils.matchesAbi( + functionNode => AbiUtils.definitionMatchesAbi( + //note this needn't actually be a function node, but then it will + //return false (well, unless it's a getter node!) abiEntry, functionNode, referenceDeclarations ) ); @@ -287,7 +289,7 @@ function allocateCalldata( throw new UnknownBaseContractIdError(contractNode.id, contractNode.name, contractNode.contractKind, baseContractId); } return baseContractNode.nodes.find( //may be undefined! that's OK! - functionNode => AbiUtils.matchesAbi( + functionNode => AbiUtils.definitionMatchesAbi( abiEntry, functionNode, referenceDeclarations ) ); @@ -301,8 +303,20 @@ function allocateCalldata( } break; } - //now: perform the allocation! - const abiAllocation = allocateMembers(node, node.parameters.parameters, referenceDeclarations, abiAllocations, offset)[node.id]; + //now: perform the allocation! however this will depend on whether + //we're looking at a normal function or a getter + let abiAllocation: Allocations.AbiAllocation; + switch(node.nodeType) { + case "FunctionDefinition": + //normal case + abiAllocation = allocateMembers(node, node.parameters.parameters, referenceDeclarations, abiAllocations, offset)[node.id]; + break; + case "VariableDeclaration": + //getter case + let getterInputs = CodecUtils.Definition.getterInputsAsDefinitions(node); + abiAllocation = allocateMembers(node, getterInputs, referenceDeclarations, abiAllocations, offset)[node.id]; + break; + } //finally: transform it appropriately let argumentsAllocation = []; for(const member of abiAllocation.members) { @@ -348,8 +362,9 @@ function allocateEvent( throw new UnknownBaseContractIdError(contractNode.id, contractNode.name, contractNode.contractKind, baseContractId); } return baseContractNode.nodes.find( //may be undefined! that's OK! - functionNode => AbiUtils.matchesAbi( - abiEntry, functionNode, referenceDeclarations + eventNode => AbiUtils.definitionMatchesAbi( + //note this needn't actually be a event node, but then it will return false + abiEntry, eventNode, referenceDeclarations ) ); }, From d859252e7c67aafc99bb45c3a3d5c40d6ebe81aa Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 9 Jul 2019 18:49:04 -0400 Subject: [PATCH 62/89] Fix various errors and add tests --- packages/truffle-codec-utils/src/ast.ts | 6 +++ .../truffle-codec-utils/src/definition.ts | 4 +- packages/truffle-codec/lib/allocate/abi.ts | 11 +++-- .../truffle-debugger/lib/session/index.js | 2 +- .../test/data/function-decoding.js | 6 +-- .../test/contracts/WireTest.sol | 5 +++ .../truffle-decoder/test/test/wire-test.js | 44 +++++++++++++++++++ 7 files changed, 67 insertions(+), 11 deletions(-) diff --git a/packages/truffle-codec-utils/src/ast.ts b/packages/truffle-codec-utils/src/ast.ts index dabdb9ee9ba..06fb492453a 100644 --- a/packages/truffle-codec-utils/src/ast.ts +++ b/packages/truffle-codec-utils/src/ast.ts @@ -31,6 +31,12 @@ export interface AstDefinition { //things stateMutability?: Mutability; referencedDeclaration?: any; + parameters?: { + parameters: AstDefinition[]; + }; + returnParameters?: { + parameters: AstDefinition[]; + }; parameterTypes?: { parameters: AstDefinition[]; }; diff --git a/packages/truffle-codec-utils/src/definition.ts b/packages/truffle-codec-utils/src/definition.ts index d94b957760c..be65009ffe8 100644 --- a/packages/truffle-codec-utils/src/definition.ts +++ b/packages/truffle-codec-utils/src/definition.ts @@ -288,9 +288,11 @@ export namespace Definition { } //returns input parameters, then output parameters + //NOTE: ONLY FOR VARIABLE DECLARATIONS OF FUNCTION TYPE + //NOT FOR FUNCTION DEFINITIONS export function parameters(definition: AstDefinition): [AstDefinition[], AstDefinition[]] { let typeObject = definition.typeName || definition; - return [typeObject.parameters.parameters, typeObject.returnParameters.parameters]; + return [typeObject.parameterTypes.parameters, typeObject.returnParameterTypes.parameters]; } //compatibility function, since pre-0.5.0 functions don't have node.kind diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index 1e94cbec283..2cc631eb03e 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -305,22 +305,21 @@ function allocateCalldata( } //now: perform the allocation! however this will depend on whether //we're looking at a normal function or a getter - let abiAllocation: Allocations.AbiAllocation; + let parameters: AstDefinition[]; switch(node.nodeType) { case "FunctionDefinition": - //normal case - abiAllocation = allocateMembers(node, node.parameters.parameters, referenceDeclarations, abiAllocations, offset)[node.id]; + parameters = node.parameters.parameters; break; case "VariableDeclaration": //getter case - let getterInputs = CodecUtils.Definition.getterInputsAsDefinitions(node); - abiAllocation = allocateMembers(node, getterInputs, referenceDeclarations, abiAllocations, offset)[node.id]; + parameters = CodecUtils.Definition.getterInputsAsDefinitions(node); break; } + const abiAllocation = allocateMembers(node, parameters, referenceDeclarations, abiAllocations, offset)[node.id]; //finally: transform it appropriately let argumentsAllocation = []; for(const member of abiAllocation.members) { - const position = node.parameters.parameters.findIndex( + const position = parameters.findIndex( (parameter: AstDefinition) => parameter.id === member.definition.id ); argumentsAllocation[position] = { diff --git a/packages/truffle-debugger/lib/session/index.js b/packages/truffle-debugger/lib/session/index.js index bf0e82d69f5..e7ac2432379 100644 --- a/packages/truffle-debugger/lib/session/index.js +++ b/packages/truffle-debugger/lib/session/index.js @@ -131,7 +131,7 @@ export default class Session { let contractId = contractNode.id; let contractKind = contractNode.contractKind; - abi = AbiUtils.schemAbiToAbi(abi); //let's handle this up front + abi = AbiUtils.schemaAbiToAbi(abi); //let's handle this up front debug("contractName %s", contractName); debug("sourceMap %o", sourceMap); diff --git a/packages/truffle-debugger/test/data/function-decoding.js b/packages/truffle-debugger/test/data/function-decoding.js index de2cabb4401..f248338aa0d 100644 --- a/packages/truffle-debugger/test/data/function-decoding.js +++ b/packages/truffle-debugger/test/data/function-decoding.js @@ -170,17 +170,17 @@ describe("Function Pointer Decoding", function() { variables.storageFn.value.contract.class.typeName, "ExternalsDerived" ); - assert.equal(variables.storageFn.value.name, "doThing"); + assert.equal(variables.storageFn.value.abi.name, "doThing"); assert.equal( variables.memoryFns.value[0].value.contract.class.typeName, "ExternalsDerived" ); - assert.equal(variables.memoryFns.value[0].value.name, "doThing"); + assert.equal(variables.memoryFns.value[0].value.abi.name, "doThing"); assert.equal( variables.stackFn.value.contract.class.typeName, "ExternalsDerived" ); - assert.equal(variables.stackFn.value.name, "doThing"); + assert.equal(variables.stackFn.value.abi.name, "doThing"); }); it("Decodes internal function pointers correctly (deployed)", async function() { diff --git a/packages/truffle-decoder/test/contracts/WireTest.sol b/packages/truffle-decoder/test/contracts/WireTest.sol index 90d542ecdce..a0a92354ec2 100644 --- a/packages/truffle-decoder/test/contracts/WireTest.sol +++ b/packages/truffle-decoder/test/contracts/WireTest.sol @@ -14,6 +14,8 @@ contract WireTestParent { contract WireTest is WireTestParent { constructor(bool status, bytes memory info, Ternary whoknows) public { + deepStruct["blornst"].length = 9; + deepString.length = 9; emit ConstructorEvent(status, info, whoknows); } @@ -116,6 +118,9 @@ contract WireTest is WireTestParent { //fourth test: no selector emit ObviouslyAnon(0xfe); } + + mapping(string => Triple[]) public deepStruct; + mapping(string => string)[] public deepString; } library WireTestLibrary { diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index cfc968d7802..601139f5fc2 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -53,10 +53,26 @@ contract("WireTest", _accounts => { let libraryTestArg = "zooglyzooglyzooglyzoogly"; let libraryTest = await deployedContract.libraryTest(libraryTestArg); + let getter1Args = ["blornst", 7]; + //this function is view so we have to use sendTransaction + let getterTest1 = await deployedContract.deepStruct.sendTransaction( + ...getter1Args + ); + let getterHash1 = getterTest1.tx; + + let getter2Args = [7, "blornst"]; + //this function is view so we have to use sendTransaction + let getterTest2 = await deployedContract.deepString.sendTransaction( + ...getter2Args + ); + let getterHash2 = getterTest2.tx; + let constructorTx = await web3.eth.getTransaction(constructorHash); let emitStuffTx = await web3.eth.getTransaction(emitStuffHash); let moreStuffTx = await web3.eth.getTransaction(moreStuffHash); let inheritedTx = await web3.eth.getTransaction(inheritedHash); + let getterTx1 = await web3.eth.getTransaction(getterHash1); + let getterTx2 = await web3.eth.getTransaction(getterHash2); let defaultConstructorTx = await web3.eth.getTransaction( defaultConstructorHash ); @@ -69,6 +85,8 @@ contract("WireTest", _accounts => { .decoding; let inheritedDecoding = (await decoder.decodeTransaction(inheritedTx)) .decoding; + let getterDecoding1 = (await decoder.decodeTransaction(getterTx1)).decoding; + let getterDecoding2 = (await decoder.decodeTransaction(getterTx2)).decoding; let defaultConstructorDecoding = (await decoder.decodeTransaction( defaultConstructorTx )).decoding; @@ -141,6 +159,32 @@ contract("WireTest", _accounts => { ); assert.isEmpty(defaultConstructorDecoding.arguments); + assert.strictEqual(getterDecoding1.kind, "function"); + assert.strictEqual(getterDecoding1.name, "deepStruct"); + assert.strictEqual(getterDecoding1.class.typeName, "WireTest"); + assert.lengthOf(getterDecoding1.arguments, 2); + assert.strictEqual( + getterDecoding1.arguments[0].value.nativize(), + getter1Args[0] + ); + assert.strictEqual( + getterDecoding1.arguments[1].value.nativize(), + getter1Args[1] + ); + + assert.strictEqual(getterDecoding2.kind, "function"); + assert.strictEqual(getterDecoding2.name, "deepString"); + assert.strictEqual(getterDecoding2.class.typeName, "WireTest"); + assert.lengthOf(getterDecoding2.arguments, 2); + assert.strictEqual( + getterDecoding2.arguments[0].value.nativize(), + getter2Args[0] + ); + assert.strictEqual( + getterDecoding2.arguments[1].value.nativize(), + getter2Args[1] + ); + //now for events! let constructorBlock = constructorTx.blockNumber; let emitStuffBlock = emitStuff.receipt.blockNumber; From 89fc176336485c01d56a48737568b92cb012c21d Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 9 Jul 2019 19:27:20 -0400 Subject: [PATCH 63/89] Simplify process of getting getter inputs --- .../truffle-codec-utils/src/definition.ts | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/packages/truffle-codec-utils/src/definition.ts b/packages/truffle-codec-utils/src/definition.ts index be65009ffe8..0d7edde15ff 100644 --- a/packages/truffle-codec-utils/src/definition.ts +++ b/packages/truffle-codec-utils/src/definition.ts @@ -587,23 +587,14 @@ export namespace Definition { //array getters & mapping getters take inputs; if stacked they take multiple inputs //struct getters do not take inputs function getterInputs(node: AstDefinition): AbiUtils.AbiParameter[] { - node = node.typeName; - let inputs: AbiUtils.AbiParameter[] = []; - while(typeClass(node) === "array" || typeClass(node) === "mapping") { - let keyNode = keyDefinition(node); //note: if node is an array, this spoofs up a uint256 definition - let parameterAbi = parameterToAbi(keyNode, null); //it's an elementary type; no need for ref declarations - parameterAbi.name = ""; //this might be garbage, let's overwrite it with the correct value (empty string) - inputs.push(parameterAbi); - switch(typeClass(node)) { - case "array": - node = node.baseType; - break; - case "mapping": - node = node.valueType; - break; + let inputsAsDefinitions = getterInputsAsDefinitions(node); + return inputsAsDefinitions.map( + inputDefinition => { + let input = parameterToAbi(inputDefinition, null); //it's an elementary type; no need for ref declarations + input.name = ""; //this might be garbage, let's overwrite it with the correct value (empty string) + return input; } - } - return inputs; + ); } //this is similar to the above, but it returns an array of definitions instead From f7e3dbc5333771619956d1b5eb6213c82c986a91 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 10 Jul 2019 02:43:44 -0400 Subject: [PATCH 64/89] Further simplify definition-to-abi logic and fix possible bugs --- .../truffle-codec-utils/src/definition.ts | 152 ++++++++++-------- packages/truffle-codec/lib/allocate/abi.ts | 2 +- 2 files changed, 82 insertions(+), 72 deletions(-) diff --git a/packages/truffle-codec-utils/src/definition.ts b/packages/truffle-codec-utils/src/definition.ts index 0d7edde15ff..c664e02c257 100644 --- a/packages/truffle-codec-utils/src/definition.ts +++ b/packages/truffle-codec-utils/src/definition.ts @@ -318,10 +318,7 @@ export namespace Definition { //returns undefined if you don't give it a FunctionDefinition or //VariableDeclaration export function mutability(node: AstDefinition): Mutability | undefined { - if(node.typeName) { - //for variable declarations, e.g. - node = node.typeName; - } + node = node.typeName || node; if(node.nodeType !== "FunctionDefinition" && node.nodeType !== "FunctionTypeName") { return undefined; } @@ -501,42 +498,49 @@ export namespace Definition { } function parameterToAbi(node: AstDefinition, referenceDeclarations: AstReferences, checkIndexed: boolean = false): AbiUtils.AbiParameter { - let name = node.name; //may be the empty string - let abiTypeString = toAbiType(node, referenceDeclarations); + let name = node.name; //may be the empty string... or even undefined for a base type let components: AbiUtils.AbiParameter[]; - if(abiTypeString.startsWith("tuple")) { - let baseType = node.typeName; - while(typeClass(baseType) === "array") { - baseType = baseDefinition(baseType); - } - let id = typeId(baseType); - let referenceDeclaration = referenceDeclarations[id]; - if(referenceDeclaration === undefined) { - let typeToDisplay = typeString(baseType); - throw new UnknownUserDefinedTypeError(id, typeToDisplay); - } - components = parametersToAbi(referenceDeclaration.members, referenceDeclarations, checkIndexed); - } + let indexed: boolean; if(checkIndexed) { + indexed = node.indexed; //note: may be undefined for a base type + } + //is this an array? if so use separate logic + if(typeClass(node) === "array") { + let baseType = node.typeName ? node.typeName.baseType : node.baseType; + let baseAbi = parameterToAbi(baseType, referenceDeclarations, checkIndexed); + let arraySuffix = isDynamicArray(node) + ? `[]` + : `[${staticLength(node)}]`; return { name, - type: abiTypeString, - indexed: node.indexed, - components + type: baseAbi.type + arraySuffix, + indexed, + components: baseAbi.components }; } - else { - return { - name, - type: abiTypeString, - components - }; + let abiTypeString = toAbiType(node, referenceDeclarations); + //otherwise... is it a struct? if so we need to populate components + if(typeClass(node) === "struct") { + let id = typeId(node); + let referenceDeclaration = referenceDeclarations[id]; + if(referenceDeclaration === undefined) { + let typeToDisplay = typeString(node); + throw new UnknownUserDefinedTypeError(id, typeToDisplay); + } + components = parametersToAbi(referenceDeclaration.members, referenceDeclarations, checkIndexed); } + return { + name, //may be empty string but should only be undefined in recursive calls + type: abiTypeString, + indexed, //undefined if !checkedIndex + components //undefined if not a struct or (multidim) array of structs + }; } - //note: this is only meant for types that can go in the ABI + //note: this is only meant for non-array types that can go in the ABI //it returns how that type is notated in the ABI -- just the string, //to be clear, not components of tuples + //again, NOT FOR ARRAYS function toAbiType(node: AstDefinition, referenceDeclarations: AstReferences): string { let basicType = typeClassLongForm(node); //get that whole first segment! switch(basicType) { @@ -554,11 +558,6 @@ export namespace Definition { let numOptions = referenceDeclaration.members.length; let bits = 8 * Math.ceil(Math.log2(numOptions) / 8); return `uint${bits}`; - case "array": - let baseType = toAbiType(baseDefinition(node), referenceDeclarations); - return isDynamicArray(node) - ? `${baseType}[]` - : `${baseType}[${staticLength(node)}]`; default: return basicType; //note that: int/uint/fixed/ufixed/bytes will have their size and such left on; @@ -566,41 +565,55 @@ export namespace Definition { //external functions will be reduced to "function" (and internal functions shouldn't //be passed in!) //(mappings shouldn't be passed in either obviously) + //(nor arrays :P ) } } function getterDefinitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.FunctionAbiEntry { + debug("getter node: %O", node); let name = node.name; - let inputs = getterInputs(node); //does not depend on reference declarations! - let outputs = getterOutputs(node, referenceDeclarations); + let { inputs, outputs, isBaseStruct } = getterParameters(node, referenceDeclarations); + let inputsAbi = washNames(parametersToAbi(inputs, referenceDeclarations)); + debug("inputsAbi: %O", inputsAbi); + let outputsAbi = isBaseStruct + ? parametersToAbi(outputs, referenceDeclarations) //output parameters for a struct getter are named! + : washNames(parametersToAbi(outputs, referenceDeclarations)); + debug("outputsAbi: %O", inputsAbi); return { type: "function", name, - inputs, - outputs, + inputs: inputsAbi, + outputs: outputsAbi, stateMutability: "view", constant: true, payable: false }; } - //array getters & mapping getters take inputs; if stacked they take multiple inputs - //struct getters do not take inputs - function getterInputs(node: AstDefinition): AbiUtils.AbiParameter[] { - let inputsAsDefinitions = getterInputsAsDefinitions(node); - return inputsAsDefinitions.map( - inputDefinition => { - let input = parameterToAbi(inputDefinition, null); //it's an elementary type; no need for ref declarations - input.name = ""; //this might be garbage, let's overwrite it with the correct value (empty string) - return input; - } - ); - } - - //this is similar to the above, but it returns an array of definitions instead - //(for use by the allocator) - export function getterInputsAsDefinitions(node: AstDefinition): AstDefinition[] { - node = node.typeName; + function washNames(parameters: AbiUtils.AbiParameter[]): AbiUtils.AbiParameter[] { + return parameters.map(parameter => ({...parameter, name: ""})); + } + + //how getter parameters work: + //INPUT: + //types other than arrays and mappings take no input. + //array getters take uint256 input. mapping getters take input of their key type. + //if arrays, mappings, stacked, then takes multiple inputs, in order from outside + //to in. + //These parameters are unnamed. + //OUTPUT: + //if base type (beneath mappings & arrays) is not a struct, returns that. + //(This return parameter has no name -- it is *not* named for the variable!) + //if it is a struct, returns multiple outputs, one for each member of the struct, + //*except* arrays and mappings. (And they have names, the names of the members.) + //important note: inner structs within a struct are just returned, not + //partially destructured like the outermost struct! Yes, this is confusing. + + //here's a simplified function that just does the inputs. it's for use by the + //allocator. I'm keeping it separate because it doesn't require a + //referenceDeclarations argument. + export function getterInputs(node: AstDefinition): AstDefinition[] { + node = node.typeName || node; let inputs: AstDefinition[] = []; while(typeClass(node) === "array" || typeClass(node) === "mapping") { let keyNode = keyDefinition(node); //note: if node is an array, this spoofs up a uint256 definition @@ -617,15 +630,16 @@ export namespace Definition { return inputs; } - //most getters return a single output. - //however, struct getters (or array of struct, or mapping to struct) - //returns multiple outputs, those outputs being the members of the - //struct *other* than arrays or mappings. - //Note that any deeper nested structs are *not* split up or filtered - //in this way! - function getterOutputs(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.AbiParameter[] { - let baseNode: AstDefinition = node.typeName; + //again, despite the duplication, this function is kept separate from the + //more straightforward getterInputs function because, since it has to handle + //outputs too, it requires referenceDeclarations + function getterParameters(node: AstDefinition, referenceDeclarations: AstReferences): {inputs: AstDefinition[], outputs: AstDefinition[], isBaseStruct: boolean} { + let baseNode: AstDefinition = node.typeName || node; + let inputs: AstDefinition[] = []; while(typeClass(baseNode) === "array" || typeClass(baseNode) === "mapping") { + let keyNode = keyDefinition(baseNode); //note: if baseNode is an array, this spoofs up a uint256 definition + inputs.push(keyNode); + debug("pushed: %O", keyNode); switch(typeClass(baseNode)) { case "array": baseNode = baseNode.baseType; @@ -644,18 +658,14 @@ export namespace Definition { let typeToDisplay = typeString(baseNode); throw new UnknownUserDefinedTypeError(id, typeToDisplay); } - return referenceDeclaration.members.filter( + let outputs = referenceDeclaration.members.filter( member => typeClass(member) !== "array" && typeClass(member) !== "mapping" - ).map( - member => parameterToAbi(member, referenceDeclarations) ); + return { inputs, outputs, isBaseStruct: true }; } else { - return [{ - name: "", - type: toAbiType(baseNode, referenceDeclarations), - //it's not a struct or an array; there are no components - }]; + //only one output + return { inputs, outputs: [baseNode], isBaseStruct: false }; } } diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index 2cc631eb03e..4f4cd02b344 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -312,7 +312,7 @@ function allocateCalldata( break; case "VariableDeclaration": //getter case - parameters = CodecUtils.Definition.getterInputsAsDefinitions(node); + parameters = CodecUtils.Definition.getterInputs(node); break; } const abiAllocation = allocateMembers(node, parameters, referenceDeclarations, abiAllocations, offset)[node.id]; From 2750b052cfb95c2b71c1f38d471a106afc3467fa Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 10 Jul 2019 03:58:11 -0400 Subject: [PATCH 65/89] Pre-wash names so that definitions will have names washed too --- .../truffle-codec-utils/src/definition.ts | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/packages/truffle-codec-utils/src/definition.ts b/packages/truffle-codec-utils/src/definition.ts index c664e02c257..21b766f96b7 100644 --- a/packages/truffle-codec-utils/src/definition.ts +++ b/packages/truffle-codec-utils/src/definition.ts @@ -409,7 +409,7 @@ export namespace Definition { nodeType: "VariableDeclaration", typeDescriptions: { typeIdentifier: "t_contract$_" + formattedName + "_$" + contractId, - typeString: contractKind + " " + contractName + typeString: contractKind + " " + contractName } } } @@ -572,13 +572,9 @@ export namespace Definition { function getterDefinitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.FunctionAbiEntry { debug("getter node: %O", node); let name = node.name; - let { inputs, outputs, isBaseStruct } = getterParameters(node, referenceDeclarations); - let inputsAbi = washNames(parametersToAbi(inputs, referenceDeclarations)); - debug("inputsAbi: %O", inputsAbi); - let outputsAbi = isBaseStruct - ? parametersToAbi(outputs, referenceDeclarations) //output parameters for a struct getter are named! - : washNames(parametersToAbi(outputs, referenceDeclarations)); - debug("outputsAbi: %O", inputsAbi); + let { inputs, outputs } = getterParameters(node, referenceDeclarations); + let inputsAbi = parametersToAbi(inputs, referenceDeclarations); + let outputsAbi = parametersToAbi(outputs, referenceDeclarations); return { type: "function", name, @@ -590,10 +586,6 @@ export namespace Definition { }; } - function washNames(parameters: AbiUtils.AbiParameter[]): AbiUtils.AbiParameter[] { - return parameters.map(parameter => ({...parameter, name: ""})); - } - //how getter parameters work: //INPUT: //types other than arrays and mappings take no input. @@ -617,7 +609,7 @@ export namespace Definition { let inputs: AstDefinition[] = []; while(typeClass(node) === "array" || typeClass(node) === "mapping") { let keyNode = keyDefinition(node); //note: if node is an array, this spoofs up a uint256 definition - inputs.push(keyNode); + inputs.push({...keyNode, name: ""}); //getter input params have no name switch(typeClass(node)) { case "array": node = node.baseType; @@ -633,13 +625,12 @@ export namespace Definition { //again, despite the duplication, this function is kept separate from the //more straightforward getterInputs function because, since it has to handle //outputs too, it requires referenceDeclarations - function getterParameters(node: AstDefinition, referenceDeclarations: AstReferences): {inputs: AstDefinition[], outputs: AstDefinition[], isBaseStruct: boolean} { + function getterParameters(node: AstDefinition, referenceDeclarations: AstReferences): {inputs: AstDefinition[], outputs: AstDefinition[]} { let baseNode: AstDefinition = node.typeName || node; let inputs: AstDefinition[] = []; while(typeClass(baseNode) === "array" || typeClass(baseNode) === "mapping") { let keyNode = keyDefinition(baseNode); //note: if baseNode is an array, this spoofs up a uint256 definition - inputs.push(keyNode); - debug("pushed: %O", keyNode); + inputs.push({...keyNode, name: ""}); //again, getter input params have no name switch(typeClass(baseNode)) { case "array": baseNode = baseNode.baseType; @@ -661,11 +652,11 @@ export namespace Definition { let outputs = referenceDeclaration.members.filter( member => typeClass(member) !== "array" && typeClass(member) !== "mapping" ); - return { inputs, outputs, isBaseStruct: true }; + return { inputs, outputs }; //no need to wash name! } else { - //only one output - return { inputs, outputs: [baseNode], isBaseStruct: false }; + //only one output; it's just the base node, with its name washed + return { inputs, outputs: [{...baseNode, name: ""}] }; } } From e4a089099bc18d504ee0f3f5afa81f6d6d8657f0 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 10 Jul 2019 04:02:16 -0400 Subject: [PATCH 66/89] Add test of name-washing --- packages/truffle-decoder/test/test/wire-test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index 601139f5fc2..1a62af42c72 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -163,10 +163,12 @@ contract("WireTest", _accounts => { assert.strictEqual(getterDecoding1.name, "deepStruct"); assert.strictEqual(getterDecoding1.class.typeName, "WireTest"); assert.lengthOf(getterDecoding1.arguments, 2); + assert.isUndefined(getterDecoding1.arguments[0].name); assert.strictEqual( getterDecoding1.arguments[0].value.nativize(), getter1Args[0] ); + assert.isUndefined(getterDecoding1.arguments[1].name); assert.strictEqual( getterDecoding1.arguments[1].value.nativize(), getter1Args[1] @@ -176,10 +178,12 @@ contract("WireTest", _accounts => { assert.strictEqual(getterDecoding2.name, "deepString"); assert.strictEqual(getterDecoding2.class.typeName, "WireTest"); assert.lengthOf(getterDecoding2.arguments, 2); + assert.isUndefined(getterDecoding2.arguments[0].name); assert.strictEqual( getterDecoding2.arguments[0].value.nativize(), getter2Args[0] ); + assert.isUndefined(getterDecoding2.arguments[1].name); assert.strictEqual( getterDecoding2.arguments[1].value.nativize(), getter2Args[1] From 1874484c384aa8edeef278e9b8d3302e9d6bcc5f Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 11 Jul 2019 22:28:07 -0400 Subject: [PATCH 67/89] Replace classes with interfaces and move methods elsewhere! --- .../truffle-codec-utils/src/conversion.ts | 86 ++- packages/truffle-codec-utils/src/index.ts | 2 + .../truffle-codec-utils/src/types/errors.ts | 566 +++++------------- .../truffle-codec-utils/src/types/inspect.ts | 223 +++++++ .../truffle-codec-utils/src/types/values.ts | 544 +---------------- packages/truffle-codec/lib/allocate/abi.ts | 10 +- .../truffle-codec/lib/allocate/storage.ts | 10 +- packages/truffle-codec/lib/decode/abi.ts | 106 +++- packages/truffle-codec/lib/decode/constant.ts | 19 +- packages/truffle-codec/lib/decode/event.ts | 27 +- packages/truffle-codec/lib/decode/memory.ts | 64 +- packages/truffle-codec/lib/decode/special.ts | 138 +++-- packages/truffle-codec/lib/decode/stack.ts | 33 +- packages/truffle-codec/lib/decode/storage.ts | 82 ++- packages/truffle-codec/lib/decode/value.ts | 415 +++++++++---- packages/truffle-codec/lib/encode/abi.ts | 4 +- packages/truffle-codec/lib/read/constant.ts | 5 +- packages/truffle-codec/lib/read/index.ts | 22 +- packages/truffle-codec/lib/read/stack.ts | 6 +- packages/truffle-codec/lib/read/storage.ts | 12 +- packages/truffle-codec/lib/types/storage.ts | 11 +- packages/truffle-debug-utils/index.js | 3 +- packages/truffle-debugger/test/data/codex.js | 7 +- .../truffle-debugger/test/data/helpers.js | 5 +- packages/truffle-debugger/test/data/ids.js | 3 +- packages/truffle-decoder/lib/contract.ts | 4 +- .../truffle-decoder/test/test/wire-test.js | 145 ++--- 27 files changed, 1217 insertions(+), 1335 deletions(-) diff --git a/packages/truffle-codec-utils/src/conversion.ts b/packages/truffle-codec-utils/src/conversion.ts index 3725c262df3..80a448465b2 100644 --- a/packages/truffle-codec-utils/src/conversion.ts +++ b/packages/truffle-codec-utils/src/conversion.ts @@ -5,6 +5,7 @@ import BN from "bn.js"; import Web3 from "web3"; import { Constants } from "./constants"; import { Values } from "./types/values"; +import { enumFullName } from "./types/inspect"; export namespace Conversion { @@ -160,7 +161,90 @@ export namespace Conversion { //for convenience: invokes the nativize method on all the given variables export function nativizeVariables(variables: {[name: string]: Values.Result}): {[name: string]: any} { return Object.assign({}, ...Object.entries(variables).map( - ([name, value]) => ({[name]: value.nativize()}) + ([name, value]) => ({[name]: nativize(value)}) )); } + + //HACK! Avoid using! Only use this if: + //1. you absolutely have to, or + //2. it's just testing, not real code + export function nativize(result: Values.Result): any { + if(result.kind === "error") { + return undefined; + } + switch(result.type.typeClass) { + case "uint": + case "int": + return (result).value.asBN.toNumber(); //WARNING + case "bool": + return (result).value.asBool; + case "bytes": + return (result).value.asHex; + case "address": + return (result).value.asAddress; + case "string": { + let coercedResult = result; + switch(coercedResult.value.kind) { + case "valid": + return coercedResult.value.asString; + case "malformed": + return coercedResult.value.asHex; //WARNING + } + } + //fixed and ufixed are skipped for now + case "array": //WARNING: circular case not handled + return (result).value.map(nativize); + case "mapping": + return Object.assign({}, ...(result).value.map( + ({key, value}) => ({[nativize(key).toString()]: nativize(value)}) + )); + case "struct": //WARNING: circular case not handled + return Object.assign({}, ...(result).value.map( + ({name, value}) => ({[name]: nativize(value)}) + )); + case "magic": + Object.assign({}, ...Object.entries((result).value).map( + ([key, value]) => ({[key]: nativize(value)}) + )); + case "enum": + return enumFullName(result); + case "contract": { + let coercedResult = result; + switch(coercedResult.value.kind) { + case "known": + return `${coercedResult.value.class.typeName}(${coercedResult.value.address})`; + case "unknown": + return coercedResult.value.address; + } + break; //to satisfy typescript + } + case "function": + switch(result.type.visibility) { + case "external": { + let coercedResult = result; + switch(coercedResult.value.kind) { + case "known": + return `${coercedResult.value.contract.class.typeName}(${coercedResult.value.contract.address}).${coercedResult.value.abi.name}` + case "invalid": + return `${coercedResult.value.contract.class.typeName}(${coercedResult.value.contract.address}).call(${coercedResult.value.selector}...)` + case "unknown": + return `${coercedResult.value.contract.address}.call(${coercedResult.value.selector}...)` + } + } + case "internal": { + let coercedResult = result; + switch(coercedResult.value.kind) { + case "function": + return `${coercedResult.value.definedIn.typeName}.${coercedResult.value.name}`; + case "exception": + return coercedResult.value.deployedProgramCounter === 0 + ? `` + : `assert(false)`; + case "unknown": + return ``; + } + } + } + } + } } diff --git a/packages/truffle-codec-utils/src/index.ts b/packages/truffle-codec-utils/src/index.ts index 98725380624..d8be2a67432 100644 --- a/packages/truffle-codec-utils/src/index.ts +++ b/packages/truffle-codec-utils/src/index.ts @@ -6,6 +6,8 @@ export * from "./contexts"; export * from "./abi"; export * from "./compiler"; export * from "./errors"; +export * from "./wrap"; export * from "./types/types"; export * from "./types/values"; export * from "./types/errors"; +export * from "./types/inspect"; diff --git a/packages/truffle-codec-utils/src/types/errors.ts b/packages/truffle-codec-utils/src/types/errors.ts index d14f51db356..2e5bb9434aa 100644 --- a/packages/truffle-codec-utils/src/types/errors.ts +++ b/packages/truffle-codec-utils/src/types/errors.ts @@ -23,10 +23,10 @@ export namespace Errors { //For when we need to throw an error, here's a wrapper class that extends Error. //Apologies about the confusing name, but I wanted something that would make //sense should it not be caught and thus accidentally exposed to the outside. - export class DecodingError extends Error { - error: GenericError; - constructor(error: GenericError) { - super(error.message()); + export class DecodingError extends Error{ + error: ErrorForThrowing; + constructor(error: ErrorForThrowing) { + super(message(error)); this.error = error; this.name = "DecodingError"; } @@ -38,39 +38,14 @@ export namespace Errors { | ContractErrorResult | FunctionExternalErrorResult | FunctionInternalErrorResult; export type DecoderError = GenericError - | UintError | IntError | BoolError | BytesStaticError | AddressError - | FixedError | UfixedError + | UintError | IntError | BoolError | BytesStaticError | BytesDynamicError | AddressError + | StringError | FixedError | UfixedError + | ArrayError | MappingError | StructError | MagicError | EnumError | ContractError | FunctionExternalError | FunctionInternalError | InternalUseError; //note that at the moment, no reference type has its own error category, so //no reference types are listed here; this also includes Magic - //for internal use only! just here to facilitate code reuse! - //please use the union types for actual use! - //this class doesn't even have the error field! - abstract class ErrorResultBase { - type: Types.Type; - kind: "error"; - error: DecoderErrorBase; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return util.inspect(this.error, options); - } - nativize(): any { - return undefined; - } - toSoliditySha3Input(): {type: string; value: any} { - return undefined; //will cause an error! should not occur! - } - constructor() { - this.kind = "error"; - } - } - - //also just for internal use to make things easier! - abstract class DecoderErrorBase { - kind: string; - } - /* * SECTION 2: Elementary values */ @@ -81,306 +56,191 @@ export namespace Errors { export type BytesErrorResult = BytesStaticErrorResult | BytesDynamicErrorResult; //Uints - export class UintErrorResult extends ErrorResultBase { - constructor( - public uintType: Types.UintType, - public error: GenericError | UintError - ) { - super(); - } + export interface UintErrorResult { + type: Types.UintType; + kind: "error"; + error: GenericError | UintError; } export type UintError = UintPaddingError; - export class UintPaddingError extends DecoderErrorBase { + export interface UintPaddingError { raw: string; //hex string kind: "UintPaddingError"; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `Uint has extra leading bytes (padding error) (raw value ${this.raw})`; - } - constructor(raw: string) { - super(); - this.raw = raw; - this.kind = "UintPaddingError"; - } } //Ints - export class IntErrorResult extends ErrorResultBase { - constructor( - public intType: Types.IntType, - public error: GenericError | IntError - ) { - super(); - } + export interface IntErrorResult { + type: Types.IntType; + kind: "error"; + error: GenericError | IntError; } export type IntError = IntPaddingError; - export class IntPaddingError extends DecoderErrorBase { + export interface IntPaddingError { raw: string; //hex string kind: "IntPaddingError"; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `Int out of range (padding error) (numeric value ${this.raw})`; - } - constructor(raw: string) { - super(); - this.raw = raw; - this.kind = "IntPaddingError"; - } } //Bools - export class BoolErrorResult extends ErrorResultBase { - constructor( - public boolType: Types.BoolType, - public error: GenericError | BoolError - ) { - super(); - } + export interface BoolErrorResult { + type: Types.BoolType; + kind: "error"; + error: GenericError | BoolError; } export type BoolError = BoolPaddingError | BoolOutOfRangeError; - export class BoolPaddingError extends DecoderErrorBase { + export interface BoolPaddingError { raw: string; //should be hex string kind: "BoolPaddingError"; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `Bool has extra leading bytes (padding error) (raw value ${this.raw})`; - } - constructor(raw: string) { - super(); - this.raw = raw; - this.kind = "BoolPaddingError"; - } } - export class BoolOutOfRangeError extends DecoderErrorBase { - rawAsBN: BN; + export interface BoolOutOfRangeError { + rawAsNumber: number; kind: "BoolOutOfRangeError"; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `Invalid boolean (numeric value ${this.rawAsBN.toString()})`; - } - constructor(raw: BN) { - super(); - this.rawAsBN = raw; - this.kind = "BoolOutOfRangeError"; - } } //bytes (static) - export class BytesStaticErrorResult extends ErrorResultBase { - constructor( - public bytesType: Types.BytesTypeStatic, - public error: GenericError | BytesStaticError - ) { - super(); - } + export interface BytesStaticErrorResult { + type: Types.BytesTypeStatic; + kind: "error"; + error: GenericError | BytesStaticError; } export type BytesStaticError = BytesPaddingError; - export class BytesPaddingError extends DecoderErrorBase { + export interface BytesPaddingError { raw: string; //should be hex string kind: "BytesPaddingError"; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `Bytestring has extra trailing bytes (padding error) (raw value ${this.raw})`; - } - constructor(raw: string) { - super(); - this.raw = raw; - this.kind = "BytesPaddingError"; - } } //bytes (dynamic) - export class BytesDynamicErrorResult extends ErrorResultBase { - constructor( - public bytesType: Types.BytesTypeDynamic, - public error: GenericError - ) { - super(); - } + export interface BytesDynamicErrorResult { + type: Types.BytesTypeDynamic; + kind: "error"; + error: GenericError | BytesDynamicError; } + export type BytesDynamicError = never; //bytes dynamic has no specific errors atm + //addresses - export class AddressErrorResult extends ErrorResultBase { - constructor( - public addressType: Types.AddressType, - public error: GenericError | AddressError - ) { - super(); - } + export interface AddressErrorResult { + type: Types.AddressType; + kind: "error"; + error: GenericError | AddressError; } export type AddressError = AddressPaddingError; - export class AddressPaddingError extends DecoderErrorBase { + export interface AddressPaddingError { raw: string; //should be hex string kind: "AddressPaddingError"; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `Address has extra leading bytes (padding error) (raw value ${this.raw})`; - } - constructor(raw: string) { - super(); - this.raw = raw; - this.kind = "AddressPaddingError"; - } } //strings - export class StringErrorResult extends ErrorResultBase { - constructor( - public stringType: Types.StringType, - public error: GenericError - ) { - super(); - } + export interface StringErrorResult { + type: Types.StringType; + kind: "error"; + error: GenericError | StringError; } + export type StringError = never; //again, string has no specific errors + //Fixed & Ufixed //These don't have a value format yet, so they just decode to errors for now! - export class FixedErrorResult extends ErrorResultBase { - constructor( - public fixedType: Types.FixedType, - public error: GenericError | FixedError - ) { - super(); - } + export interface FixedErrorResult { + type: Types.FixedType; + kind: "error"; + error: GenericError | FixedError; } - export class UfixedErrorResult extends ErrorResultBase { - constructor( - public ufixedType: Types.UfixedType, - public error: GenericError | UfixedError - ) { - super(); - } + export interface UfixedErrorResult { + type: Types.UfixedType; + kind: "error"; + error: GenericError | UfixedError; } export type FixedError = FixedPointNotYetSupportedError; export type UfixedError = FixedPointNotYetSupportedError; - export class FixedPointNotYetSupportedError extends DecoderErrorBase { + export interface FixedPointNotYetSupportedError { raw: string; //hex string kind: "FixedPointNotYetSupportedError"; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `Fixed-point decoding not yet supported (raw value: ${this.raw})`; - } - constructor(raw: string) { - super(); - this.raw = raw; - this.kind = "FixedPointNotYetSupportedError"; - } } //no separate padding error here, that would be pointless right now; will make later /* * SECTION 3: CONTAINER TYPES (including magic) + * none of these have type-specific errors */ //Arrays - export class ArrayErrorResult extends ErrorResultBase { - constructor( - public arrayType: Types.ArrayType, - public error: GenericError - ) { - super(); - } + export interface ArrayErrorResult { + type: Types.ArrayType; + kind: "error"; + error: GenericError | ArrayError; } + export type ArrayError = never; + //Mappings - export class MappingErrorResult extends ErrorResultBase { - constructor( - public mappingType: Types.MappingType, - public error: GenericError - ) { - super(); - } + export interface MappingErrorResult { + type: Types.MappingType; + kind: "error"; + error: GenericError | MappingError; } + export type MappingError = never; + //Structs - export class StructErrorResult extends ErrorResultBase { - constructor( - public structType: Types.StructType, - public error: GenericError - ) { - super(); - } + export interface StructErrorResult { + type: Types.StructType; + kind: "error"; + error: GenericError | StructError; } + export type StructError = never; + //Magic variables - export class MagicErrorResult extends ErrorResultBase { - constructor( - public magicType: Types.MagicType, - public error: GenericError - ) { - super(); - } + export interface MagicErrorResult { + type: Types.MagicType; + kind: "error"; + error: GenericError | MagicError; } + export type MagicError = never; + /* * SECTION 4: ENUMS * (they didn't fit anywhere else :P ) */ //Enums - export class EnumErrorResult extends ErrorResultBase { - constructor( - public enumType: Types.EnumType, - public error: GenericError | EnumError - ) { - super(); - } + export interface EnumErrorResult { + type: Types.EnumType; + kind: "error"; + error: GenericError | EnumError; } export type EnumError = EnumPaddingError | EnumOutOfRangeError | EnumNotFoundDecodingError; - export class EnumPaddingError extends DecoderErrorBase { + export interface EnumPaddingError { kind: "EnumPaddingError"; type: Types.EnumType; raw: string; //should be hex string - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - let typeName = (this.type.kind === "local" ? (this.type.definingContractName + ".") : "") + this.type.typeName; - return `${typeName} has extra leading bytes (padding error) (raw value ${this.raw})`; - } - constructor(enumType: Types.EnumType, raw: string) { - super(); - this.type = enumType; - this.raw = raw; - this.kind = "EnumPaddingError"; - } } - export class EnumOutOfRangeError extends DecoderErrorBase { + export interface EnumOutOfRangeError { kind: "EnumOutOfRangeError"; type: Types.EnumType; rawAsBN: BN; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - let typeName = (this.type.kind === "local" ? (this.type.definingContractName + ".") : "") + this.type.typeName; - return `Invalid ${typeName} (numeric value ${this.rawAsBN.toString()})`; - } - constructor(enumType: Types.EnumType, raw: BN) { - super(); - this.type = enumType; - this.rawAsBN = raw; - this.kind = "EnumOutOfRangeError"; - } } - export class EnumNotFoundDecodingError extends DecoderErrorBase { + export interface EnumNotFoundDecodingError { kind: "EnumNotFoundDecodingError"; type: Types.EnumType; rawAsBN: BN; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - let typeName = (this.type.kind === "local" ? (this.type.definingContractName + ".") : "") + this.type.typeName; - return `Unknown enum type ${typeName} of id ${this.type.id} (numeric value ${this.rawAsBN.toString()})`; - } - constructor(enumType: Types.EnumType, raw: BN) { - super(); - this.type = enumType; - this.rawAsBN = raw; - this.kind = "EnumNotFoundDecodingError"; - } } /* @@ -388,28 +248,17 @@ export namespace Errors { */ //Contracts - export class ContractErrorResult extends ErrorResultBase { - constructor( - public contractType: Types.ContractType, - public error: GenericError | ContractError - ) { - super(); - } + export interface ContractErrorResult { + type: Types.ContractType; + kind: "error"; + error: GenericError | ContractError; } export type ContractError = ContractPaddingError; - export class ContractPaddingError extends DecoderErrorBase { + export interface ContractPaddingError { raw: string; //should be hex string kind: "ContractPaddingError"; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `Contract address has extra leading bytes (padding error) (raw value ${this.raw})`; - } - constructor(raw: string) { - super(); - this.raw = raw; - this.kind = "ContractPaddingError"; - } } /* @@ -417,43 +266,23 @@ export namespace Errors { */ //external functions - export class FunctionExternalErrorResult extends ErrorResultBase { - constructor( - public functionType: Types.FunctionExternalType, - public error: GenericError | FunctionExternalError - ) { - super(); - } + export interface FunctionExternalErrorResult { + type: Types.FunctionExternalType; + kind: "error"; + error: GenericError | FunctionExternalError; } export type FunctionExternalError = FunctionExternalNonStackPaddingError | FunctionExternalStackPaddingError; - export class FunctionExternalNonStackPaddingError extends DecoderErrorBase { + export interface FunctionExternalNonStackPaddingError { raw: string; //should be hex string kind: "FunctionExternalNonStackPaddingError"; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `External function has extra trailing bytes (padding error) (raw value ${this.raw})`; - } - constructor(raw: string) { - super(); - this.raw = raw; - this.kind = "FunctionExternalNonStackPaddingError"; - } } - export class FunctionExternalStackPaddingError extends DecoderErrorBase { + export interface FunctionExternalStackPaddingError { rawAddress: string; rawSelector: string; kind: "FunctionExternalStackPaddingError"; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `External function address or selector has extra leading bytes (padding error) (raw address ${this.rawAddress}, raw selector ${this.rawSelector})`; - } - constructor(rawAddress: string, rawSelector: string) { - super(); - this.rawAddress = rawAddress; - this.rawSelector = rawSelector; - this.kind = "FunctionExternalStackPaddingError"; - } } /* @@ -461,80 +290,39 @@ export namespace Errors { */ //Internal functions - export class FunctionInternalErrorResult extends ErrorResultBase { - constructor( - public functionType: Types.FunctionInternalType, - public error: GenericError | FunctionInternalError - ) { - super(); - } + export interface FunctionInternalErrorResult { + type: Types.FunctionInternalType; + kind: "error"; + error: GenericError | FunctionInternalError; } export type FunctionInternalError = FunctionInternalPaddingError | NoSuchInternalFunctionError | DeployedFunctionInConstructorError | MalformedInternalFunctionError; - export class FunctionInternalPaddingError extends DecoderErrorBase { + export interface FunctionInternalPaddingError { raw: string; //should be hex string kind: "FunctionInternalPaddingError"; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `Internal function has extra leading bytes (padding error) (raw value ${this.raw})`; - } - constructor(raw: string) { - super(); - this.raw = raw; - this.kind = "FunctionInternalPaddingError"; - } } - export class NoSuchInternalFunctionError extends DecoderErrorBase { + export interface NoSuchInternalFunctionError { kind: "NoSuchInternalFunctionError"; context: Types.ContractType; deployedProgramCounter: number; constructorProgramCounter: number; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `Invalid function (Deployed PC=${this.deployedProgramCounter}, constructor PC=${this.constructorProgramCounter}) of contract ${this.context.typeName}`; - } - constructor(context: Types.ContractType, deployedProgramCounter: number, constructorProgramCounter: number) { - super(); - this.context = context; - this.deployedProgramCounter = deployedProgramCounter; - this.constructorProgramCounter = constructorProgramCounter; - this.kind = "NoSuchInternalFunctionError"; - } } - export class DeployedFunctionInConstructorError extends DecoderErrorBase { + export interface DeployedFunctionInConstructorError { kind: "DeployedFunctionInConstructorError"; context: Types.ContractType; deployedProgramCounter: number; constructorProgramCounter: number; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `Deployed-style function (PC=${this.deployedProgramCounter}) in constructor`; - } - constructor(context: Types.ContractType, deployedProgramCounter: number) { - super(); - this.context = context; - this.deployedProgramCounter = deployedProgramCounter; - this.constructorProgramCounter = 0; - this.kind = "DeployedFunctionInConstructorError"; - } } - export class MalformedInternalFunctionError extends DecoderErrorBase { + export interface MalformedInternalFunctionError { kind: "MalformedInternalFunctionError"; context: Types.ContractType; deployedProgramCounter: number; constructorProgramCounter: number; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return `Malformed internal function w/constructor PC only (value: ${this.constructorProgramCounter})`; - } - constructor(context: Types.ContractType, constructorProgramCounter: number) { - super(); - this.context = context; - this.deployedProgramCounter = 0; - this.constructorProgramCounter = constructorProgramCounter; - this.kind = "MalformedInternalFunctionError"; - } } /* @@ -542,126 +330,50 @@ export namespace Errors { */ export type GenericError = UserDefinedTypeNotFoundError | IndexedReferenceTypeError - | UnsupportedConstantError | ReadErrorStack | ReadErrorTopic; + | UnsupportedConstantError | ReadErrorStack; - //type-location error - export class UserDefinedTypeNotFoundError extends DecoderErrorBase { - kind: "UserDefinedTypeNotFoundError"; - type: Types.UserDefinedType; - message() { - let typeName = Types.isContractDefinedType(this.type) - ? this.type.definingContractName + "." + this.type.typeName - : this.type.typeName; - return `Unknown ${this.type.typeClass} type ${typeName} of id ${this.type.id}`; - } - constructor(unknownType: Types.UserDefinedType) { - super(); - this.type = unknownType; - this.kind = "UserDefinedTypeNotFoundError"; - } - } + export type ErrorForThrowing = UserDefinedTypeNotFoundError | + UnsupportedConstantError | ReadErrorStack; //attempted to decode an indexed parameter of reference type error - export class IndexedReferenceTypeError extends DecoderErrorBase { + export interface IndexedReferenceTypeError { kind: "IndexedReferenceTypeError"; type: Types.ReferenceType; raw: string; //should be hex string - message() { - return `Cannot decode indexed parameter of reference type ${this.type.typeClass} (raw value ${this.raw})`; - } - constructor(referenceType: Types.ReferenceType, raw: string) { - super(); - this.type = referenceType; - this.raw = raw; - this.kind = "IndexedReferenceTypeError"; - } + } + + //type-location error + export interface UserDefinedTypeNotFoundError { + kind: "UserDefinedTypeNotFoundError"; + type: Types.UserDefinedType; } //Read errors - export class UnsupportedConstantError extends DecoderErrorBase { + export interface UnsupportedConstantError { kind: "UnsupportedConstantError"; definition: AstDefinition; - message() { - return `Unsupported constant type ${DefinitionUtils.typeClass(this.definition)}$`; - } - constructor(definition: AstDefinition) { - super(); - this.definition = definition; - this.kind = "UnsupportedConstantError"; - } } - export class ReadErrorStack extends DecoderErrorBase { + export interface ReadErrorStack { kind: "ReadErrorStack"; from: number; to: number; - message() { - return `Can't read stack from position ${this.from} to ${this.to}`; - } - constructor(from: number, to: number) { - super(); - this.from = from; - this.to = to; - this.kind = "ReadErrorStack"; - } } - export class ReadErrorTopic extends DecoderErrorBase { - kind: "ReadErrorTopic"; - topic: number; - message() { - return `Can't read event topic ${this.topic}`; - } - constructor(topic: number) { - super(); - this.topic = topic; - this.kind = "ReadErrorTopic"; - } - } - - //finally, a convenience function for constructing generic errors - export function makeGenericErrorResult(dataType: Types.Type, error: GenericError): ErrorResult { - switch(dataType.typeClass) { - case "uint": - return new UintErrorResult(dataType, error); - case "int": - return new IntErrorResult(dataType, error); - case "bool": - return new BoolErrorResult(dataType, error); - case "bytes": - switch(dataType.kind) { - case "static": - return new BytesStaticErrorResult(dataType, error); - case "dynamic": - return new BytesDynamicErrorResult(dataType, error); - } - case "address": - return new AddressErrorResult(dataType, error); - case "fixed": - return new FixedErrorResult(dataType, error); - case "ufixed": - return new UfixedErrorResult(dataType, error); - case "string": - return new StringErrorResult(dataType, error); - case "array": - return new ArrayErrorResult(dataType, error); - case "mapping": - return new MappingErrorResult(dataType, error); - case "struct": - return new StructErrorResult(dataType, error); - case "enum": - return new EnumErrorResult(dataType, error); - case "contract": - return new ContractErrorResult(dataType, error); - case "magic": - return new MagicErrorResult(dataType, error); - case "function": - switch(dataType.visibility) { - case "external": - return new FunctionExternalErrorResult(dataType, error); - case "internal": - return new FunctionInternalErrorResult(dataType, error); - } + //this function gives an error message + //for those errors that are meant to possibly + //be wrapped in a DecodingError and thrown + export function message(error: ErrorForThrowing) { + switch(error.kind) { + case "UserDefinedTypeNotFoundError": + let typeName = Types.isContractDefinedType(error.type) + ? error.type.definingContractName + "." + error.type.typeName + : error.type.typeName; + return `Unknown ${error.type.typeClass} type ${typeName} of id ${error.type.id}`; + case "UnsupportedConstantError": + return `Unsupported constant type ${DefinitionUtils.typeClass(error.definition)}$`; + case "ReadErrorStack": + return `Can't read stack from position ${error.from} to ${error.to}`; } } @@ -670,25 +382,15 @@ export namespace Errors { export type InternalUseError = OverlongArrayOrStringError | InternalFunctionInABIError; //you should never see this returned. this is only for internal use. - export class OverlongArrayOrStringError extends DecoderErrorBase { + export interface OverlongArrayOrStringError { kind: "OverlongArrayOrStringError"; lengthAsBN: BN; dataLength: number; - constructor(length: BN, dataLength: number) { - super(); - this.lengthAsBN = length; - this.dataLength = dataLength; - this.kind = "OverlongArrayOrStringError"; - } } //this one should never come up at all, but just to be sure... - export class InternalFunctionInABIError extends DecoderErrorBase { + export interface InternalFunctionInABIError { kind: "InternalFunctionInABIError"; - constructor() { - super(); - this.kind = "InternalFunctionInABIError"; - } } } diff --git a/packages/truffle-codec-utils/src/types/inspect.ts b/packages/truffle-codec-utils/src/types/inspect.ts index 47a8ad7fd17..9d032a81b32 100644 --- a/packages/truffle-codec-utils/src/types/inspect.ts +++ b/packages/truffle-codec-utils/src/types/inspect.ts @@ -2,6 +2,9 @@ import debugModule from "debug"; const debug = debugModule("codec-utils:types:inspect"); import util from "util"; +import { Types } from "./types"; +import { Values } from "./values"; +import { Errors } from "./errors"; //we'll need to write a typing for the options type ourself, it seems; just //going to include the relevant properties here @@ -20,3 +23,223 @@ export function cleanStylize(options: InspectOptions) { : {[key]: value} )); } + +export class ResultInspector { + result: Values.Result; + constructor(result: Values.Result) { + this.result = result; + } + [util.inspect.custom](depth: number | null, options: InspectOptions): string { + switch(this.result.kind) { + case "value": + switch(this.result.type.typeClass) { + case "uint": + case "int": + return options.stylize((this.result).value.asBN.toString(), "number"); + case "bool": + return util.inspect((this.result).value.asBool, options); + case "bytes": + let hex = (this.result).value.asHex; + switch(this.result.type.kind) { + case "static": + return options.stylize(hex, "number"); + case "dynamic": + return styleHexString(hex, options); + } + case "address": + return options.stylize((this.result).value.asAddress, "number"); + case "string": { + let coercedResult = this.result; + switch(coercedResult.value.kind) { + case "valid": + return util.inspect(coercedResult.value.asString, options); + case "malformed": + return styleHexString(coercedResult.value.asHex, options) + " (malformed)"; + } + } + case "array": { + let coercedResult = this.result; + if(coercedResult.reference !== undefined) { + return formatCircular(coercedResult.reference, options); + } + return util.inspect( + coercedResult.value.map( + element => new ResultInspector(element) + ), + options + ); + } + case "mapping": + return util.inspect( + new Map( + (this.result).value.map( + ({key, value}) => [new ResultInspector(key), new ResultInspector(value)] + ) + ), + options + ); + case "struct": { + let coercedResult = this.result; + if(coercedResult.reference !== undefined) { + return formatCircular(coercedResult.reference, options); + } + return util.inspect( + Object.assign({}, ...coercedResult.value.map( + ({name, value}) => ({[name]: new ResultInspector(value)}) + )), + options + ); + } + case "magic": + return util.inspect( + Object.assign({}, ...Object.entries((this.result).value).map( + ([key, value]) => ({[key]: new ResultInspector(value)}) + )), + options + ) + case "enum": { + return enumFullName(this.result); //not stylized + } + case "contract": { + return util.inspect( + new ContractInfoInspector( + (this.result).value + ), + options + ); + } + case "function": + switch(this.result.type.visibility) { + case "external": { + let coercedResult = this.result; + let contractString = util.inspect( + new ContractInfoInspector( + coercedResult.value.contract + ), + { ...cleanStylize(options), colors: false } + ); + let firstLine: string; + switch(coercedResult.value.kind) { + case "known": + firstLine = `[Function: ${coercedResult.value.abi.name} of`; + break; + case "invalid": + case "unknown": + firstLine = `[Function: Unknown selector ${coercedResult.value.selector} of`; + break; + } + let secondLine = `${contractString}]`; + let breakingSpace = firstLine.length >= options.breakLength ? "\n" : " "; + //now, put it together + return options.stylize(firstLine + breakingSpace + secondLine, "special"); + } + case "internal": { + let coercedResult = this.result; + switch(coercedResult.value.kind) { + case "function": + return options.stylize( + `[Function: ${coercedResult.value.definedIn.typeName}.${coercedResult.value.name}]`, + "special" + ); + case "exception": + return coercedResult.value.deployedProgramCounter === 0 + ? options.stylize(`[Function: ]`, "special") + : options.stylize(`[Function: assert(false)]`, "special"); + case "unknown": + let firstLine = `[Function: decoding not supported (raw info:`; + let secondLine = `deployed PC=${coercedResult.value.deployedProgramCounter}, constructor PC=${coercedResult.value.constructorProgramCounter})]`; + let breakingSpace = firstLine.length >= options.breakLength ? "\n" : " "; + //now, put it together + return options.stylize(firstLine + breakingSpace + secondLine, "special"); + } + } + } + } + case "error": { + debug("this.result: %O", this.result); + let errorResult = this.result; //the hell?? why couldn't it make this inference?? + switch(errorResult.error.kind) { + case "UintPaddingError": + return `Uint has extra leading bytes (padding error) (raw value ${errorResult.error.raw})`; + case "IntPaddingError": + return `Int out of range (padding error) (numeric value ${errorResult.error.raw})`; + case "BoolPaddingError": + return `Bool has extra leading bytes (padding error) (raw value ${errorResult.error.raw})`; + case "BoolOutOfRangeError": + return `Invalid boolean (numeric value ${errorResult.error.rawAsNumber})`; + case "BytesPaddingError": + return `Bytestring has extra trailing bytes (padding error) (raw value ${errorResult.error.raw})`; + case "AddressPaddingError": + return `Address has extra leading bytes (padding error) (raw value ${errorResult.error.raw})`; + case "FixedPointNotYetSupportedError": + return `Fixed-point decoding not yet supported (raw value: ${errorResult.error.raw})`; + case "EnumPaddingError": + return `${enumTypeName(errorResult.error.type)} has extra leading bytes (padding error) (raw value ${errorResult.error.raw})`; + case "EnumOutOfRangeError": + return `Invalid ${enumTypeName(errorResult.error.type)} (numeric value ${errorResult.error.rawAsBN.toString()})`; + case "EnumNotFoundDecodingError": + return `Unknown enum type ${enumTypeName(errorResult.error.type)} of id ${errorResult.error.type.id} (numeric value ${errorResult.error.rawAsBN.toString()})`; + case "ContractPaddingError": + return `Contract address has extra leading bytes (padding error) (raw value ${errorResult.error.raw})`; + case "FunctionExternalNonStackPaddingError": + return `External function has extra trailing bytes (padding error) (raw value ${errorResult.error.raw})`; + case "FunctionExternalStackPaddingError": + return `External function address or selector has extra leading bytes (padding error) (raw address ${errorResult.error.rawAddress}, raw selector ${errorResult.error.rawSelector})`; + case "FunctionInternalPaddingError": + return `Internal function has extra leading bytes (padding error) (raw value ${errorResult.error.raw})`; + case "NoSuchInternalFunctionError": + return `Invalid function (Deployed PC=${errorResult.error.deployedProgramCounter}, constructor PC=${errorResult.error.constructorProgramCounter}) of contract ${errorResult.error.context.typeName}`; + case "DeployedFunctionInConstructorError": + return `Deployed-style function (PC=${errorResult.error.deployedProgramCounter}) in constructor`; + case "MalformedInternalFunctionError": + return `Malformed internal function w/constructor PC only (value: ${errorResult.error.constructorProgramCounter})`; + case "IndexedReferenceTypeError": + return `Cannot decode indexed parameter of reference type ${errorResult.error.type.typeClass} (raw value ${errorResult.error.raw})`; + case "UserDefinedTypeNotFoundError": + case "UnsupportedConstantError": + case "ReadErrorStack": + return Errors.message(errorResult.error); //yay, these three are already defined! + } + } + } + } +} + +//these get there own class to deal with a minor complication +class ContractInfoInspector { + value: Values.ContractValueInfo; + constructor(value: Values.ContractValueInfo) { + this.value = value; + } + [util.inspect.custom](depth: number | null, options: InspectOptions): string { + switch(this.value.kind) { + case "known": + return options.stylize(this.value.address, "number") + ` (${this.value.class.typeName})`; + case "unknown": + return options.stylize(this.value.address, "number") + " of unknown class"; + } + } +} + +function enumTypeName(enumType: Types.EnumType) { + return (enumType.kind === "local" ? (enumType.definingContractName + ".") : "") + enumType.typeName; +} + +function styleHexString(hex: string, options: InspectOptions): string { + return options.stylize(`hex'${hex.slice(2)}'`, "string"); +} + +//this function will be used in the future for displaying circular +//structures +function formatCircular(loopLength: number, options: InspectOptions): string { + return options.stylize(`[Circular (=up ${this.loopLength})]`, "special"); +} + +export function enumFullName(value: Values.EnumValue): string { + switch(value.type.kind) { + case "local": + return `${value.type.definingContractName}.${value.type.typeName}.${value.value.name}`; + case "global": + return `${value.type.typeName}.${value.value.name}`; + } +} diff --git a/packages/truffle-codec-utils/src/types/values.ts b/packages/truffle-codec-utils/src/types/values.ts index b01b2613680..f9815748290 100644 --- a/packages/truffle-codec-utils/src/types/values.ts +++ b/packages/truffle-codec-utils/src/types/values.ts @@ -20,7 +20,6 @@ const debug = debugModule("codec-utils:types:values"); import BN from "bn.js"; import { Types } from "./types"; import { Errors } from "./errors"; -import { InspectOptions, cleanStylize } from "./inspect"; import util from "util"; import { AstDefinition, Mutability } from "../ast"; import { Definition as DefinitionUtils } from "../definition"; @@ -64,190 +63,70 @@ export namespace Values { //Uints export type UintResult = UintValue | Errors.UintErrorResult; - export class UintValue { + export interface UintValue { type: Types.UintType; kind: "value"; value: { asBN: BN; rawAsBN?: BN; }; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return options.stylize(this.toString(), "number"); - } - toSoliditySha3Input() { - return { - type: "uint", - value: this.value.asBN - } - } - nativize(): any { - return this.value.asBN.toNumber(); //beware! - } - toString(): string { - return this.value.asBN.toString(); - } - constructor(uintType: Types.UintType, value: BN, rawValue?: BN) { - this.type = uintType; - this.kind = "value"; - this.value = { asBN: value, rawAsBN: rawValue }; - } } //Ints export type IntResult = IntValue | Errors.IntErrorResult; - export class IntValue { + export interface IntValue { type: Types.IntType; kind: "value"; value: { asBN: BN; rawAsBN?: BN; }; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return options.stylize(this.toString(), "number"); - } - toSoliditySha3Input() { - return { - type: "int", - value: this.value.asBN - } - } - nativize(): any { - return this.value.asBN.toNumber(); //beware! - } - toString(): string { - return this.value.asBN.toString(); - } - constructor(intType: Types.IntType, value: BN, rawValue?: BN) { - this.type = intType; - this.kind = "value"; - this.value = { asBN: value, rawAsBN: rawValue }; - } } //Bools export type BoolResult = BoolValue | Errors.BoolErrorResult; - export class BoolValue { + export interface BoolValue { type: Types.BoolType; kind: "value"; value: { asBool: boolean; }; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return util.inspect(this.value.asBool, options); - } - toSoliditySha3Input() { - return { - type: "uint", //used to achieve left-padding - value: this.value.asBool ? new BN(1) : new BN(0) //true & false won't work here - } - } - nativize(): any { - return this.value.asBool; - } - toString(): string { - return this.value.asBool.toString(); - } - constructor(boolType: Types.BoolType, value: boolean) { - this.type = boolType; - this.kind = "value"; - this.value = { asBool: value }; - } } //bytes (static) export type BytesStaticResult = BytesStaticValue | Errors.BytesStaticErrorResult; - export class BytesStaticValue { + export interface BytesStaticValue { type: Types.BytesTypeStatic; kind: "value"; value: { asHex: string; //should be hex-formatted, with leading "0x" - rawAsHex: string; + rawAsHex?: string; }; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return options.stylize(this.value.asHex, "number"); - } - toSoliditySha3Input() { - return { - type: "bytes32", //used to achieve right-padding - value: this.value.asHex - }; - } - nativize(): any { - return this.value.asHex; - } - toString(): string { - return this.value.asHex; - } - constructor(bytesType: Types.BytesTypeStatic, value: string, rawValue?: string) { - this.type = bytesType; - this.kind = "value"; - this.value = { asHex: value, rawAsHex: rawValue }; - } } //bytes (dynamic) export type BytesDynamicResult = BytesDynamicValue | Errors.BytesDynamicErrorResult; - export class BytesDynamicValue { + export interface BytesDynamicValue { type: Types.BytesTypeDynamic; kind: "value"; value: { asHex: string; //should be hex-formatted, with leading "0x" }; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return options.stylize(`hex'${this.value.asHex.slice(2)}'`, "string") - } - toSoliditySha3Input() { - return { - type: "bytes", - value: this.value.asHex - }; - } - nativize(): any { - return this.value.asHex; - } - toString(): string { - return this.value.asHex; - } - constructor(bytesType: Types.BytesTypeDynamic, value: string) { - this.type = bytesType; - this.kind = "value"; - this.value = { asHex: value }; - } } //addresses export type AddressResult = AddressValue | Errors.AddressErrorResult; - export class AddressValue { + export interface AddressValue { type: Types.AddressType; kind: "value"; value: { asAddress: string; //should have 0x and be checksum-cased - rawAsHex: string; - } - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return options.stylize(this.value.asAddress, "number"); - } - toSoliditySha3Input() { - return { - type: "uint", //used to achieve left-padding - value: this.value.asAddress - } - } - nativize(): any { - return this.value.asAddress; - } - toString(): string { - return this.value.asAddress; - } - constructor(addressType: Types.AddressType, value: string, rawValue?: string) { - this.type = addressType; - this.kind = "value"; - this.value = { asAddress: value, rawAsHex: rawValue }; + rawAsHex?: string; } } @@ -255,80 +134,25 @@ export namespace Values { export type StringResult = StringValue | Errors.StringErrorResult; //strings have a special new type as their value: StringValueInfo - export class StringValue { + export interface StringValue { type: Types.StringType; kind: "value"; value: StringValueInfo; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return util.inspect(this.value, options); - } - toSoliditySha3Input() { - return this.value.toSoliditySha3Input(); - } - nativize(): any { - return this.value.nativize(); - } - toString(): string { - return this.value.toString(); - } - constructor(stringType: Types.StringType, value: StringValueInfo) { - this.type = stringType; - this.kind = "value"; - this.value = value; - } } //these come in two types: valid strings and malformed strings export type StringValueInfo = StringValueInfoValid | StringValueInfoMalformed; //valid strings - export class StringValueInfoValid { + export interface StringValueInfoValid { kind: "valid"; asString: string; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return util.inspect(this.asString, options); - } - toSoliditySha3Input() { - return { - type: "string", - value: this.asString - }; - } - nativize(): any { - return this.asString; - } - toString(): string { - return this.asString; - } - constructor(value: string) { - this.kind = "valid"; - this.asString = value; - } } //malformed strings - export class StringValueInfoMalformed { + export interface StringValueInfoMalformed { kind: "malformed"; asHex: string; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return options.stylize(`hex'${this.asHex.slice(2)}'`, "string") + " (malformed)"; - } - toSoliditySha3Input() { - return { - type: "bytes", - value: this.asHex - }; - } - nativize(): any { - return this.asHex; //warning! - } - toString(): string { - return this.asHex; //warning! - } - constructor(value: string) { - this.kind = "malformed"; - this.asHex = value; - } } //Fixed & Ufixed @@ -337,119 +161,30 @@ export namespace Values { export type FixedResult = Errors.FixedErrorResult; export type UfixedResult = Errors.UfixedErrorResult; - //Function for wrapping a value as an ElementaryValue - //WARNING: this function does not check its inputs! Please check before using! - //How to use: - //numbers may be BN, number, or numeric string - //strings should be given as strings. duh. - //bytes should be given as hex strings beginning with "0x" - //addresses are like bytes; checksum case is not required - //booleans may be given either as booleans, or as string "true" or "false" - //[NOTE: in the future this function will: - //1. be moved to truffle-codec/lib/encode, - //2. check its inputs, - //3. take a slightly different input format] - export function wrapElementaryValue(value: any, definition: AstDefinition): ElementaryValue { - //force location to undefined, force address to nonpayable - //(we force address to nonpayable since address payable can't be declared - //as a mapping key type) - let dataType = Types.definitionToType(definition, null, null); - switch(dataType.typeClass) { - case "string": - return new StringValue(dataType, new StringValueInfoValid(value)); - case "bytes": - switch(dataType.kind) { - case "static": - return new BytesStaticValue(dataType, value); - case "dynamic": - return new BytesDynamicValue(dataType, value); - } - case "address": - return new AddressValue(dataType, value); - case "int": - if(value instanceof BN) { - value = value.clone(); - } - else { - value = new BN(value); - } - return new IntValue(dataType, value); - case "uint": - if(value instanceof BN) { - value = value.clone(); - } - else { - value = new BN(value); - } - return new UintValue(dataType, value); - case "bool": - if(typeof value === "string") { - value = value !== "false"; - } - return new BoolValue(dataType, value); - } - } - /* * SECTION 3: CONTAINER TYPES (including magic) */ - //this function will be used in the future for displaying circular - //structures - function formatCircular(loopLength: number, options: InspectOptions) { - return options.stylize(`[Circular (=up ${this.loopLength})]`, "special"); - } - //Arrays export type ArrayResult = ArrayValue | Errors.ArrayErrorResult; - export class ArrayValue { + export interface ArrayValue { type: Types.ArrayType; kind: "value"; reference?: number; //will be used in the future for circular values value: Result[]; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - if(this.reference !== undefined) { - return formatCircular(this.reference, options); - } - return util.inspect(this.value, options) - } - nativize(): any { - return this.value.map(element => element.nativize()); - } - constructor(arrayType: Types.ArrayType, value: Result[], reference?: number) { - this.type = arrayType; - this.kind = "value"; - this.value = value; - this.reference = reference; - } } //Mappings export type MappingResult = MappingValue | Errors.MappingErrorResult; - export class MappingValue { + export interface MappingValue { type: Types.MappingType; kind: "value"; //note that since mappings live in storage, a circular //mapping is impossible value: KeyValuePair[]; //order is irrelevant //note that key is not allowed to be an error! - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return util.inspect(new Map(this.value.map( - ({key, value}) => [key, value] - )), options); - } - nativize(): any { - return Object.assign({}, ...this.value.map(({key, value}) => - ({[key.toString()]: value.nativize()}) - )); - } - constructor(mappingType: Types.MappingType, value: KeyValuePair[]) { - this.type = mappingType; - this.kind = "value"; - this.value = value; - } } export interface KeyValuePair { @@ -460,33 +195,11 @@ export namespace Values { //Structs export type StructResult = StructValue | Errors.StructErrorResult; - export class StructValue { + export interface StructValue { type: Types.StructType; kind: "value"; reference?: number; //will be used in the future for circular values value: NameValuePair[]; //these should be stored in order! - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - if(this.reference !== undefined) { - return formatCircular(this.reference, options); - } - return util.inspect( - Object.assign({}, ...this.value.map( - ({name, value}) => ({[name]: value}) - )), - options - ); - } - nativize(): any { - return Object.assign({}, ...this.value.map( - ({name, value}) => ({[name]: value.nativize()}) - )); - } - constructor(structType: Types.StructType, value: NameValuePair[], reference?: number) { - this.type = structType; - this.kind = "value"; - this.value = value; - this.reference = reference; - } } export interface NameValuePair { @@ -497,26 +210,13 @@ export namespace Values { //Magic variables export type MagicResult = MagicValue | Errors.MagicErrorResult; - export class MagicValue { + export interface MagicValue { type: Types.MagicType; kind: "value"; //a magic variable can't be circular, duh! value: { [field: string]: Result }; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return util.inspect(this.value, options); - } - nativize(): any { - return Object.assign({}, ...Object.entries(this.value).map( - ([key, value]) => ({[key]: value.nativize()}) - )); - } - constructor(magicType: Types.MagicType, value: {[field: string]: Result}) { - this.type = magicType; - this.kind = "value"; - this.value = value; - } } /* @@ -527,32 +227,13 @@ export namespace Values { //Enums export type EnumResult = EnumValue | Errors.EnumErrorResult; - export class EnumValue { + export interface EnumValue { type: Types.EnumType; kind: "value"; value: { name: string; numericAsBN: BN; }; - fullName(): string { - switch(this.type.kind) { - case "local": - return `${this.type.definingContractName}.${this.type.typeName}.${this.value.name}`; - case "global": - return `${this.type.typeName}.${this.value.name}`; - } - } - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return this.fullName(); - } - nativize(): any { - return this.fullName(); - } - constructor(enumType: Types.EnumType, numeric: BN, name: string) { - this.type = enumType; - this.kind = "value"; - this.value = { name, numericAsBN: numeric }; - } }; /* @@ -563,21 +244,10 @@ export namespace Values { export type ContractResult = ContractValue | Errors.ContractErrorResult; //Contract values have a special new type as their value: ContractValueInfo. - export class ContractValue { + export interface ContractValue { type: Types.ContractType; kind: "value"; value: ContractValueInfo; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return util.inspect(this.value, options); - } - nativize(): any { - return this.value.nativize(); - } - constructor(contractType: Types.ContractType, value: ContractValueInfo) { - this.type = contractType; - this.kind = "value"; - this.value = value; - } } //There are two types -- one for contracts whose class we can identify, and one @@ -585,45 +255,21 @@ export namespace Values { export type ContractValueInfo = ContractValueInfoKnown | ContractValueInfoUnknown; //when we can identify the class - export class ContractValueInfoKnown { + export interface ContractValueInfoKnown { kind: "known"; address: string; //should be formatted as address //NOT an AddressResult, note rawAddress?: string; class: Types.ContractType; //may have more optional members defined later, but I'll leave these out for now - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return options.stylize(this.address, "number") + ` (${this.class.typeName})`; - } - nativize(): any { - return `${this.class.typeName}(${this.address})`; - } - constructor(address: string, contractClass: Types.ContractType, rawAddress?: string) { - this.kind = "known"; - this.address = address; - this.class = contractClass; - this.rawAddress = rawAddress; - } } //when we can't - export class ContractValueInfoUnknown { + export interface ContractValueInfoUnknown { kind: "unknown"; address: string; //should be formatted as address //NOT an AddressResult, note rawAddress?: string; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - debug("options: %O", options); - return options.stylize(this.address, "number") + " of unknown class"; - } - nativize(): any { - return this.address; - } - constructor(address: string, rawAddress?: string) { - this.kind = "unknown"; - this.address = address; - this.rawAddress = rawAddress; - } } /* @@ -633,21 +279,10 @@ export namespace Values { //external functions export type FunctionExternalResult = FunctionExternalValue | Errors.FunctionExternalErrorResult; - export class FunctionExternalValue { + export interface FunctionExternalValue { type: Types.FunctionExternalType; kind: "value"; value: FunctionExternalValueInfo; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return util.inspect(this.value, options); - } - nativize(): any { - return this.value.nativize(); - } - constructor(functionType: Types.FunctionExternalType, value: FunctionExternalValueInfo) { - this.type = functionType; - this.kind = "value"; - this.value = value; - } } //External function values come in 3 types: @@ -657,79 +292,26 @@ export namespace Values { | FunctionExternalValueInfoUnknown; //can't determine class //known function of known class - export class FunctionExternalValueInfoKnown { + export interface FunctionExternalValueInfoKnown { kind: "known"; contract: ContractValueInfoKnown; selector: string; //formatted as a bytes4 abi: AbiUtils.FunctionAbiEntry; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - //first, let's inspect that contract, but w/o color - let contractString = util.inspect(this.contract, { ...cleanStylize(options), colors: false }); - let firstLine = `[Function: ${this.abi.name} of`; - let secondLine = `${contractString}]`; - let breakingSpace = firstLine.length >= options.breakLength ? "\n" : " "; - //now, put it together - return options.stylize(firstLine + breakingSpace + secondLine, "special"); - } - nativize(): any { - return `${this.contract.nativize()}.${this.abi.name}` - } - constructor(contract: ContractValueInfoKnown, selector: string, abi: AbiUtils.FunctionAbiEntry) { - this.kind = "known"; - this.contract = contract; - this.selector = selector; - this.abi = abi; - } + //may have more optional fields added later, I'll leave these out for now } //known class but can't locate function - export class FunctionExternalValueInfoInvalid { + export interface FunctionExternalValueInfoInvalid { kind: "invalid"; contract: ContractValueInfoKnown; selector: string; //formatted as a bytes4 - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - //first, let's inspect that contract, but w/o color - let contractString = util.inspect(this.contract, { ...cleanStylize(options), colors: false }); - let selectorString = `Unknown selector 0x${this.selector}`; - let firstLine = `[Function: ${selectorString} of`; - let secondLine = `${contractString}]`; - let breakingSpace = firstLine.length >= options.breakLength ? "\n" : " "; - //now, put it together - return options.stylize(firstLine + breakingSpace + secondLine, "special"); - } - nativize(): any { - return `${this.contract.nativize()}.call(${this.selector}...)` - } - constructor(contract: ContractValueInfoKnown, selector: string) { - this.kind = "invalid"; - this.contract = contract; - this.selector = selector; - } } //can't even locate class - export class FunctionExternalValueInfoUnknown { + export interface FunctionExternalValueInfoUnknown { kind: "unknown"; contract: ContractValueInfoUnknown; selector: string; //formatted as a bytes4 - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - //first, let's inspect that contract, but w/o color - let contractString = util.inspect(this.contract, { ...cleanStylize(options), colors: false }); - let selectorString = `Unknown selector 0x${this.selector}`; - let firstLine = `[Function: ${selectorString} of`; - let secondLine = `${contractString}]`; - let breakingSpace = firstLine.length >= options.breakLength ? "\n" : " "; - //now, put it together - return options.stylize(firstLine + breakingSpace + secondLine, "special"); - } - nativize(): any { - return `${this.contract.nativize()}.call(${this.selector}...)` - } - constructor(contract: ContractValueInfoUnknown, selector: string) { - this.kind = "unknown"; - this.contract = contract; - this.selector = selector; - } } /* @@ -739,21 +321,10 @@ export namespace Values { //Internal functions export type FunctionInternalResult = FunctionInternalValue | Errors.FunctionInternalErrorResult; - export class FunctionInternalValue { + export interface FunctionInternalValue { type: Types.FunctionInternalType; kind: "value"; value: FunctionInternalValueInfo; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return util.inspect(this.value, options); - } - nativize(): any { - return this.value.nativize(); - } - constructor(functionType: Types.FunctionInternalType, value: FunctionInternalValueInfo) { - this.type = functionType; - this.kind = "value"; - this.value = value; - } } //these also come in 3 types @@ -763,7 +334,7 @@ export namespace Values { | FunctionInternalValueInfoUnknown; //decoding not supported in this context //actual function - export class FunctionInternalValueInfoKnown { + export interface FunctionInternalValueInfoKnown { kind: "function" context: Types.ContractType; deployedProgramCounter: number; @@ -771,79 +342,22 @@ export namespace Values { name: string; definedIn: Types.ContractType; mutability?: Mutability; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return options.stylize(`[Function: ${this.definedIn.typeName}.${this.name}]`, "special"); - } - nativize(): any { - return `${this.definedIn.typeName}.${this.name}`; - } - constructor( - context: Types.ContractType, - deployedProgramCounter: number, - constructorProgramCounter: number, - name: string, - definedIn: Types.ContractType, - mutability?: Mutability - ) { - this.kind = "function"; - this.context = context; - this.deployedProgramCounter = deployedProgramCounter; - this.constructorProgramCounter = constructorProgramCounter; - this.name = name; - this.definedIn = definedIn; - this.mutability = mutability; - } + //may have more optional fields added later } //default value - export class FunctionInternalValueInfoException { + export interface FunctionInternalValueInfoException { kind: "exception" context: Types.ContractType; deployedProgramCounter: number; constructorProgramCounter: number; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return this.deployedProgramCounter === 0 - ? options.stylize(`[Function: ]`, "special") - : options.stylize(`[Function: assert(false)]`, "special"); - } - nativize(): any { - return this.deployedProgramCounter === 0 - ? `` - : `assert(false)`; - } - constructor( - context: Types.ContractType, - deployedProgramCounter: number, - constructorProgramCounter: number - ) { - this.kind = "exception"; - this.context = context; - this.deployedProgramCounter = deployedProgramCounter; - this.constructorProgramCounter = constructorProgramCounter; - } } //value returned to indicate that decoding is not supported outside the debugger - export class FunctionInternalValueInfoUnknown { + export interface FunctionInternalValueInfoUnknown { kind: "unknown" context: Types.ContractType; deployedProgramCounter: number; constructorProgramCounter: number; - [util.inspect.custom](depth: number | null, options: InspectOptions): string { - return options.stylize(`[Function: decoding not supported (raw info: deployed PC=${this.deployedProgramCounter}, constructor PC=${this.constructorProgramCounter})]`, "special"); - } - nativize(): any { - return ``; - } - constructor( - context: Types.ContractType, - deployedProgramCounter: number, - constructorProgramCounter: number - ) { - this.kind = "unknown"; - this.context = context; - this.deployedProgramCounter = deployedProgramCounter; - this.constructorProgramCounter = constructorProgramCounter; - } } } diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index 4f4cd02b344..21d65d7dde2 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -213,7 +213,10 @@ export function abiSizeForType(dataType: CodecUtils.Types.Type, allocations?: Al const allocation = allocations[dataType.id]; if(!allocation) { throw new CodecUtils.Errors.DecodingError( - new CodecUtils.Errors.UserDefinedTypeNotFoundError(dataType) + { + kind: "UserDefinedTypeNotFoundError", + type: dataType + } ); } return allocation.length; @@ -235,7 +238,10 @@ export function isTypeDynamic(dataType: CodecUtils.Types.Type, allocations?: All const allocation = allocations[dataType.id]; if(!allocation) { throw new CodecUtils.Errors.DecodingError( - new CodecUtils.Errors.UserDefinedTypeNotFoundError(dataType) + { + kind: "UserDefinedTypeNotFoundError", + type: dataType + } ); } return allocation.dynamic; diff --git a/packages/truffle-codec/lib/allocate/storage.ts b/packages/truffle-codec/lib/allocate/storage.ts index 51aff423fdb..e9868a5e162 100644 --- a/packages/truffle-codec/lib/allocate/storage.ts +++ b/packages/truffle-codec/lib/allocate/storage.ts @@ -368,7 +368,10 @@ export function storageSizeForType(dataType: CodecUtils.Types.Type, userDefinedT let fullType = CodecUtils.Types.fullType(dataType, userDefinedTypes); if(!fullType.options) { throw new CodecUtils.Errors.DecodingError( - new CodecUtils.Errors.UserDefinedTypeNotFoundError(dataType) + { + kind: "UserDefinedTypeNotFoundError", + type: dataType + } ); } return {bytes: Math.ceil(Math.log2(fullType.options.length) / 8)}; @@ -417,7 +420,10 @@ export function storageSizeForType(dataType: CodecUtils.Types.Type, userDefinedT let allocation = allocations[dataType.id]; if(!allocation) { throw new CodecUtils.Errors.DecodingError( - new CodecUtils.Errors.UserDefinedTypeNotFoundError(dataType) + { + kind: "UserDefinedTypeNotFoundError", + type: dataType + } ); } return allocation.size; diff --git a/packages/truffle-codec/lib/decode/abi.ts b/packages/truffle-codec/lib/decode/abi.ts index 2aeeb19f174..4c828ffe71f 100644 --- a/packages/truffle-codec/lib/decode/abi.ts +++ b/packages/truffle-codec/lib/decode/abi.ts @@ -3,7 +3,7 @@ const debug = debugModule("codec:decode:abi"); import read from "../read"; import * as CodecUtils from "truffle-codec-utils"; -import { Types, Values, Errors } from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import decodeValue from "./value"; import { AbiDataPointer, DataPointer } from "../types/pointer"; import { AbiMemberAllocation } from "../types/allocation"; @@ -24,7 +24,11 @@ export default function* decodeAbi(dataType: Types.Type, pointer: AbiDataPointer if(options.strictAbiMode) { throw new StopDecodingError(error.error); } - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } if(dynamic) { return yield* decodeAbiReferenceByAddress(dataType, pointer, info, options); @@ -57,7 +61,11 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin if(strict) { throw new StopDecodingError(error.error); } - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } let startPosition = CodecUtils.Conversion.toBN(rawValue).toNumber() + base; @@ -71,7 +79,11 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin if(strict) { throw new StopDecodingError(error.error); } - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } if(!dynamic) { //this will only come up when called from stack.ts let size: number; @@ -82,7 +94,11 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin if(strict) { throw new StopDecodingError(error.error); } - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } let staticPointer = { location, @@ -109,7 +125,11 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin if(strict) { throw new StopDecodingError(error.error); } - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } let lengthAsBN = CodecUtils.Conversion.toBN(rawLength); if(strict && lengthAsBN.gtn(info.state[location].length)) { @@ -117,9 +137,11 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin //just to prevent huge numbers from DOSing us, other errors will still //be caught regardless throw new StopDecodingError( - new Errors.OverlongArrayOrStringError( - lengthAsBN, info.state[location].length - ) + { + kind: "OverlongArrayOrStringError", + lengthAsBN, + dataLength: info.state[location].length + } ); } length = lengthAsBN.toNumber(); @@ -148,7 +170,11 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin if(strict) { throw new StopDecodingError(error.error); } - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } let lengthAsBN = CodecUtils.Conversion.toBN(rawLength); if(strict && lengthAsBN.gtn(info.state[location].length)) { @@ -156,9 +182,11 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin //just to prevent huge numbers from DOSing us, other errors will still //be caught regardless throw new StopDecodingError( - new Errors.OverlongArrayOrStringError( - lengthAsBN, info.state[location].length - ) + { + kind: "OverlongArrayOrStringError", + lengthAsBN, + dataLength: info.state[location].length + } ); } length = lengthAsBN.toNumber(); @@ -182,7 +210,11 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin if(strict) { throw new StopDecodingError(error.error); } - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } let decodedChildren: Values.Result[] = []; @@ -199,7 +231,11 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin )) ); //pointer base is always start of list, never the length } - return new Values.ArrayValue(dataType, decodedChildren); + return { + type: dataType, + kind: "value", + value: decodedChildren + }; case "struct": return yield* decodeAbiStructByPosition(dataType, location, startPosition, info, options); @@ -224,7 +260,11 @@ export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer if(options.strictAbiMode) { throw new StopDecodingError(error.error); } - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } let decodedChildren: Values.Result[] = []; @@ -241,7 +281,11 @@ export function* decodeAbiReferenceStatic(dataType: Types.ReferenceType, pointer )) ); } - return new Values.ArrayValue(dataType, decodedChildren); + return { + type: dataType, + kind: "value", + value: decodedChildren + }; case "struct": return yield* decodeAbiStructByPosition(dataType, location, pointer.start, info, options); @@ -259,11 +303,18 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc const typeId = dataType.id; const structAllocation = allocations[typeId]; if(!structAllocation) { - let error = new Errors.UserDefinedTypeNotFoundError(dataType); + let error = { + kind: "UserDefinedTypeNotFoundError" as "UserDefinedTypeNotFoundError", + type: dataType + }; if(options.strictAbiMode) { throw new StopDecodingError(error); } - return new Errors.StructErrorResult(dataType, error); + return { + type: dataType, + kind: "error", + error + }; } let decodedMembers: Values.NameValuePair[] = []; @@ -279,11 +330,18 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc let memberName = memberAllocation.definition.name; let storedType = userDefinedTypes[typeId]; if(!storedType) { - let error = new Errors.UserDefinedTypeNotFoundError(dataType); + let error = { + kind: "UserDefinedTypeNotFoundError" as "UserDefinedTypeNotFoundError", + type: dataType + }; if(options.strictAbiMode) { throw new StopDecodingError(error); } - return new Errors.StructErrorResult(dataType, error); + return { + type: dataType, + kind: "error", + error + }; } let storedMemberType = storedType.memberTypes[index].type; let memberType = Types.specifyLocation(storedMemberType, typeLocation); @@ -294,5 +352,9 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc //note that the base option is only needed in the dynamic case, but we're being indiscriminate }); } - return new Values.StructValue(dataType, decodedMembers); + return { + type: dataType, + kind: "value", + value: decodedMembers + }; } diff --git a/packages/truffle-codec/lib/decode/constant.ts b/packages/truffle-codec/lib/decode/constant.ts index bebc19d30a3..1083108f878 100644 --- a/packages/truffle-codec/lib/decode/constant.ts +++ b/packages/truffle-codec/lib/decode/constant.ts @@ -2,7 +2,7 @@ import debugModule from "debug"; const debug = debugModule("codec:decode:constant"); import * as CodecUtils from "truffle-codec-utils"; -import { Types, Values, Errors } from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import read from "../read"; import decodeValue from "./value"; import { ConstantDefinitionPointer} from "../types/pointer"; @@ -27,14 +27,21 @@ export default function* decodeConstant(dataType: Types.Type, pointer: ConstantD word = yield* read(pointer, info.state); } catch(error) { //error: Errors.DecodingError - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } //not bothering to check padding; shouldn't be necessary let bytes = word.slice(CodecUtils.EVM.WORD_SIZE - size); - return new Values.BytesStaticValue( - dataType, - CodecUtils.Conversion.toHexString(bytes) - ); //we'll skip including a raw value, as that would be meaningless + return { + type: dataType, + kind: "value", + value: { + asHex: CodecUtils.Conversion.toHexString(bytes) + } + }; //we'll skip including a raw value, as that would be meaningless } //otherwise, as mentioned, just dispatch to decodeValue diff --git a/packages/truffle-codec/lib/decode/event.ts b/packages/truffle-codec/lib/decode/event.ts index 87ff79f939c..24d1249dba9 100644 --- a/packages/truffle-codec/lib/decode/event.ts +++ b/packages/truffle-codec/lib/decode/event.ts @@ -3,7 +3,7 @@ const debug = debugModule("codec:decode:event"); import decodeValue from "./value"; import read from "../read"; -import { Types, Values, Errors, Conversion as ConversionUtils } from "truffle-codec-utils"; +import { Types, Values, Conversion as ConversionUtils } from "truffle-codec-utils"; import { EventTopicPointer } from "../types/pointer"; import { EvmInfo, DecoderOptions } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; @@ -12,25 +12,18 @@ import { StopDecodingError } from "../types/errors"; export default function* decodeTopic(dataType: Types.Type, pointer: EventTopicPointer, info: EvmInfo, options: DecoderOptions = {}): IterableIterator { if(Types.isReferenceType(dataType)) { //we cannot decode reference types "stored" in topics; we have to just return an error - let bytes: Uint8Array; - try { - bytes = yield* read(pointer, info.state); - } - catch(error) { //error: Values.DecodingError - if(options.strictAbiMode) { - throw new StopDecodingError(error.error); - } - return Errors.makeGenericErrorResult(dataType, error.error); - } + let bytes: Uint8Array = yield* read(pointer, info.state); let raw: string = ConversionUtils.toHexString(bytes); //NOTE: even in strict mode we want to just return this, not throw an error here - return Errors.makeGenericErrorResult( - dataType, - new Errors.IndexedReferenceTypeError( - dataType, + return { + type: dataType, + kind: "error", + error: { + kind: "IndexedReferenceTypeError", + type: dataType, raw - ) - ); + } + }; } //otherwise, dispatch to decodeValue return yield* decodeValue(dataType, pointer, info, options); diff --git a/packages/truffle-codec/lib/decode/memory.ts b/packages/truffle-codec/lib/decode/memory.ts index 1711776a4ea..55cacd6d883 100644 --- a/packages/truffle-codec/lib/decode/memory.ts +++ b/packages/truffle-codec/lib/decode/memory.ts @@ -3,7 +3,7 @@ const debug = debugModule("codec:decode:memory"); import read from "../read"; import * as CodecUtils from "truffle-codec-utils"; -import { Types, Values, Errors } from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import decodeValue from "./value"; import { MemoryPointer, DataPointer } from "../types/pointer"; import { MemoryMemberAllocation } from "../types/allocation"; @@ -27,7 +27,11 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p rawValue = yield* read(pointer, state); } catch(error) { //error: Errors.DecodingError - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } let startPosition = CodecUtils.Conversion.toBN(rawValue).toNumber(); @@ -47,7 +51,11 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p }, state); } catch(error) { //error: Errors.DecodingError - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } length = CodecUtils.Conversion.toBN(rawLength).toNumber(); @@ -55,7 +63,7 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p location: "memory", start: startPosition + CodecUtils.EVM.WORD_SIZE, length - } + }; return yield* decodeValue(dataType, childPointer, info); @@ -71,7 +79,11 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p }, state); } catch(error) { //error: Errors.DecodingError - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } length = CodecUtils.Conversion.toBN(rawLength).toNumber(); startPosition += CodecUtils.EVM.WORD_SIZE; //increment startPosition @@ -98,7 +110,11 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p ); } - return new Values.ArrayValue(dataType, decodedChildren); + return { + type: dataType, + kind: "value", + value: decodedChildren + }; case "struct": const { allocations: { memory: allocations }, userDefinedTypes } = info; @@ -106,10 +122,14 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p const typeId = dataType.id; const structAllocation = allocations[typeId]; if(!structAllocation) { - return new Errors.StructErrorResult( - dataType, - new Errors.UserDefinedTypeNotFoundError(dataType) - ); + return { + type: dataType, + kind: "error", + error: { + kind: "UserDefinedTypeNotFoundError", + type: dataType + } + }; } debug("structAllocation %O", structAllocation); @@ -127,10 +147,14 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p let memberName = memberAllocation.definition.name; let storedType = userDefinedTypes[typeId]; if(!storedType) { - return new Errors.StructErrorResult( - dataType, - new Errors.UserDefinedTypeNotFoundError(dataType) - ); + return { + type: dataType, + kind: "error", + error: { + kind: "UserDefinedTypeNotFoundError", + type: dataType + } + }; } let storedMemberType = storedType.memberTypes[index].type; let memberType = Types.specifyLocation(storedMemberType, "memory"); @@ -140,10 +164,18 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p value: (yield* decodeMemory(memberType, childPointer, info)) }); } - return new Values.StructValue(dataType, decodedMembers); + return { + type: dataType, + kind: "value", + value: decodedMembers + }; case "mapping": //a mapping in memory is always empty - return new Values.MappingValue(dataType, []); + return { + type: dataType, + kind: "value", + value: [] + }; } } diff --git a/packages/truffle-codec/lib/decode/special.ts b/packages/truffle-codec/lib/decode/special.ts index f0a4a964fc9..ca59ccc2493 100644 --- a/packages/truffle-codec/lib/decode/special.ts +++ b/packages/truffle-codec/lib/decode/special.ts @@ -24,69 +24,77 @@ export function* decodeMagic(dataType: Types.MagicType, pointer: SpecialPointer, switch(pointer.special) { case "msg": - return new Values.MagicValue(dataType, { - data: (yield* decodeValue( - { - typeClass: "bytes", - kind: "dynamic", - location: "calldata" - }, - { - location: "calldata", - start: 0, - length: state.calldata.length - }, - info - )), - sig: (yield* decodeValue( - { - typeClass: "bytes", - kind: "static", - length: CodecUtils.EVM.SELECTOR_SIZE - }, - { - location: "calldata", - start: 0, - length: CodecUtils.EVM.SELECTOR_SIZE, - }, - info - )), - sender: (yield* decodeValue( - { - typeClass: "address", - payable: true - }, - {location: "special", special: "sender"}, - info - )), - value: (yield* decodeValue( - { - typeClass: "uint", - bits: 256 - }, - {location: "special", special: "value"}, - info - )) - }); + return { + type: dataType, + kind: "value", + value: { + data: (yield* decodeValue( + { + typeClass: "bytes", + kind: "dynamic", + location: "calldata" + }, + { + location: "calldata", + start: 0, + length: state.calldata.length + }, + info + )), + sig: (yield* decodeValue( + { + typeClass: "bytes", + kind: "static", + length: CodecUtils.EVM.SELECTOR_SIZE + }, + { + location: "calldata", + start: 0, + length: CodecUtils.EVM.SELECTOR_SIZE, + }, + info + )), + sender: (yield* decodeValue( + { + typeClass: "address", + payable: true + }, + {location: "special", special: "sender"}, + info + )), + value: (yield* decodeValue( + { + typeClass: "uint", + bits: 256 + }, + {location: "special", special: "value"}, + info + )) + } + }; case "tx": - return new Values.MagicValue(dataType, { - origin: (yield* decodeValue( - { - typeClass: "address", - payable: true - }, - {location: "special", special: "origin"}, - info - )), - gasprice: (yield* decodeValue( - { - typeClass: "uint", - bits: 256 - }, - {location: "special", special: "gasprice"}, - info - )) - }); + return { + type: dataType, + kind: "value", + value: { + origin: (yield* decodeValue( + { + typeClass: "address", + payable: true + }, + {location: "special", special: "origin"}, + info + )), + gasprice: (yield* decodeValue( + { + typeClass: "uint", + bits: 256 + }, + {location: "special", special: "gasprice"}, + info + )) + } + }; case "block": let block: {[field: string]: Values.Result} = { coinbase: (yield* decodeValue( @@ -111,6 +119,10 @@ export function* decodeMagic(dataType: Types.MagicType, pointer: SpecialPointer, info )); } - return new Values.MagicValue(dataType, block); + return { + type: dataType, + kind: "value", + value: block + }; } } diff --git a/packages/truffle-codec/lib/decode/stack.ts b/packages/truffle-codec/lib/decode/stack.ts index 6f02b2492f5..9235e6b718e 100644 --- a/packages/truffle-codec/lib/decode/stack.ts +++ b/packages/truffle-codec/lib/decode/stack.ts @@ -2,7 +2,7 @@ import debugModule from "debug"; const debug = debugModule("codec:decode:stack"); import * as CodecUtils from "truffle-codec-utils"; -import { Types, Values, Errors } from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import read from "../read"; import decodeValue from "./value"; import { decodeExternalFunction, checkPaddingLeft } from "./value"; @@ -19,7 +19,11 @@ export default function* decodeStack(dataType: Types.Type, pointer: StackPointer rawValue = yield* read(pointer, info.state); } catch(error) { //error: Errors.DecodingError - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } const literalPointer: StackLiteralPointer = { location: "stackliteral", literal: rawValue }; return yield* decodeLiteral(dataType, literalPointer, info); @@ -87,19 +91,22 @@ export function* decodeLiteral(dataType: Types.Type, pointer: StackLiteralPointe let selectorWord = pointer.literal.slice(-CodecUtils.EVM.WORD_SIZE); if(!checkPaddingLeft(address, CodecUtils.EVM.ADDRESS_SIZE) ||!checkPaddingLeft(selectorWord, CodecUtils.EVM.SELECTOR_SIZE)) { - return new Errors.FunctionExternalErrorResult( - dataType, - new Errors.FunctionExternalStackPaddingError( - CodecUtils.Conversion.toHexString(address), - CodecUtils.Conversion.toHexString(selectorWord) - ) - ); + return { + type: dataType, + kind: "error", + error: { + kind: "FunctionExternalStackPaddingError", + rawAddress: CodecUtils.Conversion.toHexString(address), + rawSelector: CodecUtils.Conversion.toHexString(selectorWord) + } + }; } let selector = selectorWord.slice(-CodecUtils.EVM.SELECTOR_SIZE); - return new Values.FunctionExternalValue( - dataType, - (yield* decodeExternalFunction(address, selector, info)) - ); + return { + type: dataType, + kind: "value", + value: (yield* decodeExternalFunction(address, selector, info)) + }; } //finally, if none of the above hold, we can just dispatch to decodeValue. diff --git a/packages/truffle-codec/lib/decode/storage.ts b/packages/truffle-codec/lib/decode/storage.ts index 09b0c3f1078..138e1f12ba4 100644 --- a/packages/truffle-codec/lib/decode/storage.ts +++ b/packages/truffle-codec/lib/decode/storage.ts @@ -3,7 +3,7 @@ const debug = debugModule("codec:decode:storage"); import read from "../read"; import * as CodecUtils from "truffle-codec-utils"; -import { Types, Values, Errors } from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import decodeValue from "./value"; import { StoragePointer, DataPointer } from "../types/pointer"; import { EvmInfo } from "../types/evm"; @@ -34,7 +34,11 @@ export function* decodeStorageReferenceByAddress(dataType: Types.ReferenceType, rawValue = yield* read(pointer, info.state); } catch(error) { //error: Errors.DecodingError - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } const startOffset = CodecUtils.Conversion.toBN(rawValue); let rawSize: StorageTypes.StorageLength; @@ -42,7 +46,11 @@ export function* decodeStorageReferenceByAddress(dataType: Types.ReferenceType, rawSize = storageSizeForType(dataType, info.userDefinedTypes, allocations); } catch(error) { //error: Errors.DecodingError - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } //we *know* the type being decoded must be sized in words, because it's a //reference type, but TypeScript doesn't, so we'll have to use a type @@ -81,12 +89,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: case "dynamic": debug("dynamic array"); debug("type %O", dataType); - try { - data = yield* read(pointer, state); - } - catch(error) { //error: Errors.DecodingError - return Errors.makeGenericErrorResult(dataType, error.error); - } + data = yield* read(pointer, state); length = CodecUtils.Conversion.toBN(data).toNumber(); break; case "static": @@ -102,7 +105,11 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: baseSize = storageSizeForType(dataType.baseType, info.userDefinedTypes, allocations); } catch(error) { //error: Errors.DecodingError - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } debug("baseSize %o", baseSize); @@ -191,17 +198,16 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: ); } - return new Values.ArrayValue(dataType, decodedChildren); + return { + type: dataType, + kind: "value", + value: decodedChildren + }; } case "bytes": case "string": { - try { - data = yield* read(pointer, state); - } - catch(error) { //error: Errors.DecodingError - return Errors.makeGenericErrorResult(dataType, error.error); - } + data = yield* read(pointer, state); debug("data %O", data); let lengthByte = data[CodecUtils.EVM.WORD_SIZE - 1]; @@ -241,10 +247,14 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: const typeId = dataType.id; const structAllocation = allocations[typeId]; if(!structAllocation) { - return new Errors.StructErrorResult( - dataType, - new Errors.UserDefinedTypeNotFoundError(dataType) - ); + return { + type: dataType, + kind: "error", + error: { + kind: "UserDefinedTypeNotFoundError", + type: dataType + } + }; } let decodedMembers: Values.NameValuePair[] = []; @@ -279,10 +289,14 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: let memberName = memberAllocation.definition.name; let storedType = info.userDefinedTypes[typeId]; if(!storedType) { - return new Errors.StructErrorResult( - dataType, - new Errors.UserDefinedTypeNotFoundError(dataType) - ); + return { + type: dataType, + kind: "error", + error: { + kind: "UserDefinedTypeNotFoundError", + type: dataType + } + }; } let storedMemberType = storedType.memberTypes[index].type; let memberType = Types.specifyLocation(storedMemberType, "storage"); @@ -297,7 +311,11 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: }); } - return new Values.StructValue(dataType, decodedMembers); + return { + type: dataType, + kind: "value", + value: decodedMembers + }; } case "mapping": { @@ -310,7 +328,11 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: valueSize = storageSizeForType(valueType, info.userDefinedTypes, allocations); } catch(error) { //error: Errors.DecodingError - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } let decodedEntries: Values.KeyValuePair[] = []; @@ -379,7 +401,11 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: }); } - return new Values.MappingValue(dataType, decodedEntries); + return { + type: dataType, + kind: "value", + value: decodedEntries + }; } } } diff --git a/packages/truffle-codec/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts index 75e2f5cf21f..5dc08db180e 100644 --- a/packages/truffle-codec/lib/decode/value.ts +++ b/packages/truffle-codec/lib/decode/value.ts @@ -3,7 +3,7 @@ const debug = debugModule("codec:decode:value"); import read from "../read"; import * as CodecUtils from "truffle-codec-utils"; -import { Types, Values, Errors } from "truffle-codec-utils"; +import { Types, Values } from "truffle-codec-utils"; import BN from "bn.js"; import utf8 from "utf8"; import { DataPointer } from "../types/pointer"; @@ -25,7 +25,11 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, if(strict) { throw new StopDecodingError(error.error); } - return Errors.makeGenericErrorResult(dataType, error.error); + return { + type: dataType, + kind: "error", + error: error.error + }; } rawBytes = bytes; @@ -36,142 +40,238 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, case "bool": { if(!checkPaddingLeft(bytes, 1)) { - let error = new Errors.BoolPaddingError(CodecUtils.Conversion.toHexString(bytes)); + let error = { + kind: "BoolPaddingError" as "BoolPaddingError", + raw: CodecUtils.Conversion.toHexString(bytes) + }; if(strict) { throw new StopDecodingError(error); } - return new Errors.BoolErrorResult(dataType, error); + return { + type: dataType, + kind: "error", + error + }; } const numeric = CodecUtils.Conversion.toBN(bytes); if(numeric.eqn(0)) { - return new Values.BoolValue(dataType, false); + return { + type: dataType, + kind: "value", + value: { asBool: false } + }; } else if(numeric.eqn(1)) { - return new Values.BoolValue(dataType, true); + return { + type: dataType, + kind: "value", + value: { asBool: true } + }; } else { - let error = new Errors.BoolOutOfRangeError(numeric); + let error = { + kind: "BoolOutOfRangeError" as "BoolOutOfRangeError", + rawAsNumber: numeric.toNumber() //cannot fail, it's only 1 byte + }; if(strict) { throw new StopDecodingError(error); } - return new Errors.BoolErrorResult(dataType, error); + return { + type: dataType, + kind: "error", + error + }; } } case "uint": //first, check padding (if needed) if(!permissivePadding && !checkPaddingLeft(bytes, dataType.bits/8)) { - let error = new Errors.UintPaddingError(CodecUtils.Conversion.toHexString(bytes)); + let error = { + kind: "UintPaddingError" as "UintPaddingError", + raw: CodecUtils.Conversion.toHexString(bytes) + }; if(strict) { throw new StopDecodingError(error); } - return new Errors.UintErrorResult(dataType, error); + return { + type: dataType, + kind: "error", + error + }; } //now, truncate to appropriate length (keeping the bytes on the right) bytes = bytes.slice(-dataType.bits/8); - return new Values.UintValue( - dataType, - CodecUtils.Conversion.toBN(bytes), - CodecUtils.Conversion.toBN(rawBytes) - ); + return { + type: dataType, + kind: "value", + value: { + asBN: CodecUtils.Conversion.toBN(bytes), + rawAsBN: CodecUtils.Conversion.toBN(rawBytes) + } + }; case "int": //first, check padding (if needed) if(!permissivePadding && !checkPaddingSigned(bytes, dataType.bits/8)) { - let error = new Errors.IntPaddingError(CodecUtils.Conversion.toHexString(bytes)); + let error = { + kind: "IntPaddingError" as "IntPaddingError", + raw: CodecUtils.Conversion.toHexString(bytes) + }; if(strict) { throw new StopDecodingError(error); } - return new Errors.IntErrorResult(dataType, error); + return { + type: dataType, + kind: "error", + error + }; } //now, truncate to appropriate length (keeping the bytes on the right) bytes = bytes.slice(-dataType.bits/8); - return new Values.IntValue( - dataType, - CodecUtils.Conversion.toSignedBN(bytes), - CodecUtils.Conversion.toSignedBN(rawBytes) - ); + return { + type: dataType, + kind: "value", + value: { + asBN: CodecUtils.Conversion.toSignedBN(bytes), + rawAsBN: CodecUtils.Conversion.toSignedBN(rawBytes) + } + }; case "address": if(!permissivePadding && !checkPaddingLeft(bytes, CodecUtils.EVM.ADDRESS_SIZE)) { - let error = new Errors.AddressPaddingError(CodecUtils.Conversion.toHexString(bytes)); + let error = { + kind: "AddressPaddingError" as "AddressPaddingError", + raw: CodecUtils.Conversion.toHexString(bytes) + }; if(strict) { throw new StopDecodingError(error); } - return new Errors.AddressErrorResult(dataType, error); + return { + type: dataType, + kind: "error", + error + }; } - return new Values.AddressValue( - dataType, - CodecUtils.Conversion.toAddress(bytes), - CodecUtils.Conversion.toHexString(rawBytes) - ); + return { + type: dataType, + kind: "value", + value: { + asAddress: CodecUtils.Conversion.toAddress(bytes), + rawAsHex: CodecUtils.Conversion.toHexString(rawBytes) + } + }; case "contract": if(!permissivePadding && !checkPaddingLeft(bytes, CodecUtils.EVM.ADDRESS_SIZE)) { - let error = new Errors.ContractPaddingError(CodecUtils.Conversion.toHexString(bytes)); + let error = { + kind: "ContractPaddingError" as "ContractPaddingError", + raw: CodecUtils.Conversion.toHexString(bytes) + }; if(strict) { throw new StopDecodingError(error); } - return new Errors.ContractErrorResult(dataType, error); + return { + type: dataType, + kind: "error", + error + }; } const fullType = Types.fullType(dataType, info.userDefinedTypes); const contractValueInfo = (yield* decodeContract(bytes, info)); - return new Values.ContractValue(fullType, contractValueInfo); + return { + type: fullType, + kind: "value", + value: contractValueInfo + }; case "bytes": switch(dataType.kind) { case "static": //first, check padding (if needed) if(!permissivePadding && !checkPaddingRight(bytes, dataType.length)) { - let error = new Errors.BytesPaddingError(CodecUtils.Conversion.toHexString(bytes)); + let error = { + kind: "BytesPaddingError" as "BytesPaddingError", + raw: CodecUtils.Conversion.toHexString(bytes) + }; if(strict) { throw new StopDecodingError(error); } - return new Errors.BytesStaticErrorResult(dataType, error); + return { + type: dataType, + kind: "error", + error + }; } //now, truncate to appropriate length bytes = bytes.slice(0, dataType.length); - return new Values.BytesStaticValue( - dataType, - CodecUtils.Conversion.toHexString(bytes), - CodecUtils.Conversion.toHexString(rawBytes) - ); + return { + type: dataType, + kind: "value", + value: { + asHex: CodecUtils.Conversion.toHexString(bytes), + rawAsHex: CodecUtils.Conversion.toHexString(rawBytes) + } + }; case "dynamic": //no need to check padding here - return new Values.BytesDynamicValue(dataType, CodecUtils.Conversion.toHexString(bytes)); + return { + type: dataType, + kind: "value", + value: { + asHex: CodecUtils.Conversion.toHexString(bytes), + } + }; } case "string": //there is no padding check for strings - return new Values.StringValue(dataType, decodeString(bytes)); + return { + type: dataType, + kind: "value", + value: decodeString(bytes) + }; case "function": switch(dataType.visibility) { case "external": if(!checkPaddingRight(bytes, CodecUtils.EVM.ADDRESS_SIZE + CodecUtils.EVM.SELECTOR_SIZE)) { - let error = new Errors.FunctionExternalNonStackPaddingError(CodecUtils.Conversion.toHexString(bytes)); + let error = { + kind: "FunctionExternalNonStackPaddingError" as "FunctionExternalNonStackPaddingError", + raw: CodecUtils.Conversion.toHexString(bytes) + }; if(strict) { throw new StopDecodingError(error); } - return new Errors.FunctionExternalErrorResult(dataType, error); + return { + type: dataType, + kind: "error", + error + }; } const address = bytes.slice(0, CodecUtils.EVM.ADDRESS_SIZE); const selector = bytes.slice(CodecUtils.EVM.ADDRESS_SIZE, CodecUtils.EVM.ADDRESS_SIZE + CodecUtils.EVM.SELECTOR_SIZE); - return new Values.FunctionExternalValue(dataType, - (yield* decodeExternalFunction(address, selector, info)) - ); + return { + type: dataType, + kind: "value", + value: (yield* decodeExternalFunction(address, selector, info)) + }; case "internal": if(strict) { //internal functions don't go in the ABI! //this should never happen, but just to be sure... throw new StopDecodingError( - new Errors.InternalFunctionInABIError() + { kind: "InternalFunctionInABIError" } ); } if(!checkPaddingLeft(bytes, 2 * CodecUtils.EVM.PC_SIZE)) { - return new Errors.FunctionInternalErrorResult( - dataType, - new Errors.FunctionInternalPaddingError(CodecUtils.Conversion.toHexString(bytes)) - ); + return { + type: dataType, + kind: "error", + error: { + kind: "FunctionInternalPaddingError", + raw: CodecUtils.Conversion.toHexString(bytes) + } + }; } const deployedPc = bytes.slice(-CodecUtils.EVM.PC_SIZE); const constructorPc = bytes.slice(-CodecUtils.EVM.PC_SIZE * 2, -CodecUtils.EVM.PC_SIZE); @@ -183,51 +283,81 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, const numeric = CodecUtils.Conversion.toBN(bytes); const fullType = Types.fullType(dataType, info.userDefinedTypes); if(!fullType.options) { - let error = new Errors.EnumNotFoundDecodingError(fullType, numeric); + let error = { + kind: "EnumNotFoundDecodingError" as "EnumNotFoundDecodingError", + type: fullType, + rawAsBN: numeric + }; if(strict) { throw new StopDecodingError(error); } - return new Errors.EnumErrorResult(fullType, error); + return { + type: fullType, + kind: "error", + error + }; } const numOptions = fullType.options.length; const numBytes = Math.ceil(Math.log2(numOptions) / 8); if(!checkPaddingLeft(bytes, numBytes)) { - let error = new Errors.EnumPaddingError(fullType, CodecUtils.Conversion.toHexString(bytes)); + let error = { + kind: "EnumPaddingError" as "EnumPaddingError", + type: fullType, + raw: CodecUtils.Conversion.toHexString(bytes) + }; if(strict) { throw new StopDecodingError(error); } - return new Errors.EnumErrorResult(fullType, error); + return { + type: fullType, + kind: "error", + error + }; } if(numeric.ltn(numOptions)) { const name = fullType.options[numeric.toNumber()]; - return new Values.EnumValue(fullType, numeric, name); + return { + type: fullType, + kind: "value", + value: { + name, + numericAsBN: numeric + } + }; } else { - let error = new Errors.EnumOutOfRangeError(fullType, numeric); + let error = { + kind: "EnumOutOfRangeError" as "EnumOutOfRangeError", + type: fullType, + rawAsBN: numeric + }; if(strict) { throw new StopDecodingError(error); } - return new Errors.EnumErrorResult(fullType, error); - } - } - - case "fixed": { - //skipping padding check as we don't support this anyway - const hex = CodecUtils.Conversion.toHexString(bytes); - let error = new Errors.FixedPointNotYetSupportedError(hex); - if(strict) { - throw new StopDecodingError(error); + return { + type: fullType, + kind: "error", + error + }; } - return new Errors.FixedErrorResult(dataType, error); } + //will have to split these once we actually support fixed-point + case "fixed": case "ufixed": { //skipping padding check as we don't support this anyway const hex = CodecUtils.Conversion.toHexString(bytes); - let error = new Errors.FixedPointNotYetSupportedError(hex); + let error = { + kind: "FixedPointNotYetSupportedError" as "FixedPointNotYetSupportedError", + raw: hex + }; if(strict) { throw new StopDecodingError(error); } - return new Errors.UfixedErrorResult(dataType, error); + return { + type: dataType, + kind: "error", + error + }; } } } @@ -239,13 +369,19 @@ export function decodeString(bytes: Uint8Array): Values.StringValueInfo { try { //this will throw an error if we have malformed UTF-8 let correctlyEncodedString = utf8.decode(badlyEncodedString); - return new Values.StringValueInfoValid(correctlyEncodedString); + return { + kind: "valid", + asString: correctlyEncodedString + }; } catch(_) { //we're going to ignore the precise error and just assume it's because //the string was malformed (what else could it be?) let hexString = CodecUtils.Conversion.toHexString(bytes); - return new Values.StringValueInfoMalformed(hexString); + return { + kind: "malformed", + asHex: hexString + }; } } @@ -260,14 +396,19 @@ export function* decodeContract(addressBytes: Uint8Array, info: EvmInfo): Iterab let code = CodecUtils.Conversion.toHexString(codeBytes); let context = CodecUtils.Contexts.findDecoderContext(info.contexts, code); if(context !== null && context.contractName !== undefined) { - return new Values.ContractValueInfoKnown( + return { + kind: "known", address, - CodecUtils.Contexts.contextToType(context), - rawAddress - ); + rawAddress, + class: CodecUtils.Contexts.contextToType(context) + }; } else { - return new Values.ContractValueInfoUnknown(address, rawAddress); + return { + kind: "unknown", + address, + rawAddress + }; } } @@ -277,7 +418,11 @@ export function* decodeExternalFunction(addressBytes: Uint8Array, selectorBytes: let contract = (yield* decodeContract(addressBytes, info)); let selector = CodecUtils.Conversion.toHexString(selectorBytes); if(contract.kind === "unknown") { - return new Values.FunctionExternalValueInfoUnknown(contract, selector) + return { + kind: "unknown", + contract, + selector + }; } let contractId = ( contract.class).id; //sorry! will be fixed soon! let context = Object.values(info.contexts).find( @@ -287,9 +432,18 @@ export function* decodeExternalFunction(addressBytes: Uint8Array, selectorBytes: ? context.abi[selector] : undefined; if(abiEntry === undefined) { - return new Values.FunctionExternalValueInfoInvalid(contract, selector) + return { + kind: "invalid", + contract, + selector + }; } - return new Values.FunctionExternalValueInfoKnown(contract, selector, abiEntry) + return { + kind: "known", + contract, + selector, + abi: abiEntry + }; } //this one works a bit differently -- in order to handle errors, it *does* return a FunctionInternalResult @@ -307,31 +461,55 @@ export function decodeInternalFunction(dataType: Types.FunctionInternalType, dep //before anything else: do we even have an internal functions table? //if not, we'll just return the info we have without really attemting to decode if(!info.internalFunctionsTable) { - return new Values.FunctionInternalValue( - dataType, - new Values.FunctionInternalValueInfoUnknown(context, deployedPc, constructorPc) - ); + return { + type: dataType, + kind: "value", + value: { + kind: "unknown", + context, + deployedProgramCounter: deployedPc, + constructorProgramCounter: constructorPc + } + }; } //also before we continue: is the PC zero? if so let's just return that if(deployedPc === 0 && constructorPc === 0) { - return new Values.FunctionInternalValue( - dataType, - new Values.FunctionInternalValueInfoException(context, deployedPc, constructorPc) - ); + return { + type: dataType, + kind: "value", + value: { + kind: "exception", + context, + deployedProgramCounter: deployedPc, + constructorProgramCounter: constructorPc + } + }; } //another check: is only the deployed PC zero? if(deployedPc === 0 && constructorPc !== 0) { - return new Errors.FunctionInternalErrorResult( - dataType, - new Errors.MalformedInternalFunctionError(context, constructorPc) - ); + return { + type: dataType, + kind: "error", + error: { + kind: "MalformedInternalFunctionError", + context, + deployedProgramCounter: 0, + constructorProgramCounter: constructorPc + } + }; } //one last pre-check: is this a deployed-format pointer in a constructor? if(info.currentContext.isConstructor && constructorPc === 0) { - return new Errors.FunctionInternalErrorResult( - dataType, - new Errors.DeployedFunctionInConstructorError(context, deployedPc) - ); + return { + type: dataType, + kind: "error", + error: { + kind: "DeployedFunctionInConstructorError", + context, + deployedProgramCounter: deployedPc, + constructorProgramCounter: 0 + } + }; } //otherwise, we get our function let pc = info.currentContext.isConstructor @@ -340,16 +518,28 @@ export function decodeInternalFunction(dataType: Types.FunctionInternalType, dep let functionEntry = info.internalFunctionsTable[pc]; if(!functionEntry) { //if it's not zero and there's no entry... error! - return new Errors.FunctionInternalErrorResult( - dataType, - new Errors.NoSuchInternalFunctionError(context, deployedPc, constructorPc) - ); + return { + type: dataType, + kind: "error", + error: { + kind: "NoSuchInternalFunctionError", + context, + deployedProgramCounter: deployedPc, + constructorProgramCounter: constructorPc + } + }; } if(functionEntry.isDesignatedInvalid) { - return new Values.FunctionInternalValue( - dataType, - new Values.FunctionInternalValueInfoException(context, deployedPc, constructorPc) - ); + return { + type: dataType, + kind: "value", + value: { + kind: "exception", + context, + deployedProgramCounter: deployedPc, + constructorProgramCounter: constructorPc + } + }; } let name = functionEntry.name; let mutability = functionEntry.mutability; @@ -361,10 +551,19 @@ export function decodeInternalFunction(dataType: Types.FunctionInternalType, dep contractKind: functionEntry.contractKind, payable: functionEntry.contractPayable }; - return new Values.FunctionInternalValue( - dataType, - new Values.FunctionInternalValueInfoKnown(context, deployedPc, constructorPc, name, definedIn, mutability) - ); + return { + type: dataType, + kind: "value", + value: { + kind: "function", + context, + deployedProgramCounter: deployedPc, + constructorProgramCounter: constructorPc, + name, + definedIn, + mutability + } + }; } function checkPaddingRight(bytes: Uint8Array, length: number): boolean { diff --git a/packages/truffle-codec/lib/encode/abi.ts b/packages/truffle-codec/lib/encode/abi.ts index d76c2a78281..6ef25ce4f7a 100644 --- a/packages/truffle-codec/lib/encode/abi.ts +++ b/packages/truffle-codec/lib/encode/abi.ts @@ -1,4 +1,4 @@ -import { Types, Values, Conversion as ConversionUtils, EVM as EVMUtils } from "truffle-codec-utils"; +import { Values, Conversion as ConversionUtils, EVM as EVMUtils } from "truffle-codec-utils"; import { AbiAllocations } from "../types/allocation"; import { isTypeDynamic, abiSizeForType } from "../allocate/abi"; import sum from "lodash.sum"; @@ -101,7 +101,7 @@ export function encodeAbi(input: Values.Result, allocations?: AbiAllocations): U } } -function stringToBytes(input: string): Uint8Array { +export function stringToBytes(input: string): Uint8Array { input = utf8.encode(input); let bytes = new Uint8Array(input.length); for(let i = 0; i < input.length; i++) { diff --git a/packages/truffle-codec/lib/read/constant.ts b/packages/truffle-codec/lib/read/constant.ts index 4daa6344958..1a015fee396 100644 --- a/packages/truffle-codec/lib/read/constant.ts +++ b/packages/truffle-codec/lib/read/constant.ts @@ -24,7 +24,10 @@ export function readDefinition(definition: CodecUtils.AstDefinition): Uint8Array //handle right now. sorry. debug("unsupported constant definition type"); throw new Errors.DecodingError( - new Errors.UnsupportedConstantError(definition) + { + kind: "UnsupportedConstantError", + definition + } ); } } diff --git a/packages/truffle-codec/lib/read/index.ts b/packages/truffle-codec/lib/read/index.ts index 0a2f4d0594c..f3f247589f8 100644 --- a/packages/truffle-codec/lib/read/index.ts +++ b/packages/truffle-codec/lib/read/index.ts @@ -27,30 +27,20 @@ export default function* read(pointer: Pointer.DataPointer, state: EvmState): It return bytes.readBytes(state.eventdata, pointer.start, pointer.length); case "stackliteral": + //nothing to do, just return it return pointer.literal; case "definition": return constant.readDefinition(pointer.definition); case "special": - //not bothering with error handling on this oen as I don't expect errors + //this one is simple enough to inline + //not bothering with error handling on this one as I don't expect errors return state.specials[pointer.special]; case "eventtopic": - return readTopic(state.eventtopics, pointer.topic); - - //...and in the case of "abi", which shouldn't happen, we'll just fall off - //the end and cause a problem :P - } -} - -//this one is simple enough I'm keeping it in the same file -function readTopic(topics: Uint8Array[], index: number) { - let topic = topics[index]; - if(topic === undefined) { - throw new Errors.DecodingError( - new Errors.ReadErrorTopic(index) - ); + //this one is simple enough to inline as well; similarly not bothering + //with error handling + return state.eventtopics[pointer.topic]; } - return topic; } diff --git a/packages/truffle-codec/lib/read/stack.ts b/packages/truffle-codec/lib/read/stack.ts index 071fd904442..164c130fe98 100644 --- a/packages/truffle-codec/lib/read/stack.ts +++ b/packages/truffle-codec/lib/read/stack.ts @@ -6,7 +6,11 @@ import * as CodecUtils from "truffle-codec-utils"; export function readStack(stack: Uint8Array[], from: number, to: number): Uint8Array { if(from < 0 || to >= stack.length) { throw new CodecUtils.Errors.DecodingError( - new CodecUtils.Errors.ReadErrorStack(from, to) + { + kind: "ReadErrorStack", + from, + to + } ); } //unforunately, Uint8Arrays don't support concat; if they did the rest of diff --git a/packages/truffle-codec/lib/read/storage.ts b/packages/truffle-codec/lib/read/storage.ts index 50af05e6626..9dc102f0545 100644 --- a/packages/truffle-codec/lib/read/storage.ts +++ b/packages/truffle-codec/lib/read/storage.ts @@ -5,6 +5,7 @@ import * as CodecUtils from "truffle-codec-utils"; import { Slot, Range } from "../types/storage"; import { WordMapping } from "../types/evm"; import { DecoderRequest } from "../types/request"; +import { mappingKeyAsHex, keyInfoForPrinting } from "../encode/key"; import BN from "bn.js"; /** @@ -18,7 +19,7 @@ import BN from "bn.js"; export function slotAddress(slot: Slot): BN { if (slot.key !== undefined && slot.path !== undefined) { // mapping reference - return CodecUtils.EVM.keccak256(slot.key.toSoliditySha3Input(), slotAddress(slot.path)).add(slot.offset); + return CodecUtils.EVM.keccak256(mappingKeyAsHex(slot.key), slotAddress(slot.path)).add(slot.offset); } else if (slot.path !== undefined) { const pathAddress = slotAddress(slot.path); @@ -33,8 +34,8 @@ export function slotAddress(slot: Slot): BN { export function slotAddressPrintout(slot: Slot): string { if (slot.key !== undefined && slot.path !== undefined) { // mapping reference - let {type: keyEncoding} = slot.key.toSoliditySha3Input(); - return "keccak(" + slot.key.toString() + " as " + keyEncoding + ", " + slotAddressPrintout(slot.path) + ") + " + slot.offset.toString(); + let {type: keyEncoding, value: keyValue} = keyInfoForPrinting(slot.key); + return "keccak(" + keyValue + " as " + keyEncoding + ", " + slotAddressPrintout(slot.path) + ") + " + slot.offset.toString(); } else if (slot.path !== undefined) { const pathAddressPrintout = slotAddressPrintout(slot.path); @@ -95,16 +96,13 @@ export function* readRange(storage: WordMapping, range: Range): IterableIterator debug("readRange %o", range); let { from, to, length } = range; - if (typeof length === "undefined" && !to || length && to) { - throw new Error("must specify exactly one `to`|`length`"); - } from = { slot: from.slot, index: from.index || 0 }; - if (typeof length !== "undefined") { + if (length !== undefined) { to = { slot: { path: from.slot.path || undefined, diff --git a/packages/truffle-codec/lib/types/storage.ts b/packages/truffle-codec/lib/types/storage.ts index 8f5a3f020bc..dc0552b3bb6 100644 --- a/packages/truffle-codec/lib/types/storage.ts +++ b/packages/truffle-codec/lib/types/storage.ts @@ -2,6 +2,7 @@ import debugModule from "debug"; const debug = debugModule("codec:types:storage"); import * as CodecUtils from "truffle-codec-utils"; +import { encodeMappingKey } from "../encode/key"; import BN from "bn.js"; export type StorageLength = {bytes: number} | {words: number}; @@ -53,7 +54,7 @@ export function equalSlots(slot1: Slot | undefined, slot2: Slot | undefined): bo if(!equalSlots(slot1.path, slot2.path)) { return false; } - //to compare keys, we'll just compare their toSoliditySha3Input (HACK?) + //to compare keys, we'll just compare their hex encodings //(yes, that leaves some wiggle room, as it could consider different //*types* of keys to be equal, but if keys are the only difference then //that should determine those types, so it shouldn't be a problem) @@ -62,8 +63,8 @@ export function equalSlots(slot1: Slot | undefined, slot2: Slot | undefined): bo return !slot1.key && !slot2.key; } //if they do have keys, though... - let { type: type1, value: value1 } = slot1.key.toSoliditySha3Input(); - let { type: type2, value: value2 } = slot2.key.toSoliditySha3Input(); - return type1 === type2 && value1.toString() === value2.toString(); //HACK: since values may be - //BNs, we compare by toString instead of by the value itself + return CodecUtils.EVM.equalData( + encodeMappingKey(slot1.key), + encodeMappingKey(slot2.key) + ); } diff --git a/packages/truffle-debug-utils/index.js b/packages/truffle-debug-utils/index.js index 23a11e879bc..546b2fc2e43 100644 --- a/packages/truffle-debug-utils/index.js +++ b/packages/truffle-debug-utils/index.js @@ -5,6 +5,7 @@ var async = require("async"); var debug = require("debug")("lib:debug"); var BN = require("bn.js"); var util = require("util"); +var CodecUtils = require("truffle-codec-utils"); var commandReference = { "o": "step over", @@ -318,7 +319,7 @@ var DebugUtils = { formatValue: function(value, indent = 0) { return util - .inspect(value, { + .inspect(new CodecUtils.ResultInspector(value), { colors: true, depth: null, maxArrayLength: null, diff --git a/packages/truffle-debugger/test/data/codex.js b/packages/truffle-debugger/test/data/codex.js index 533e3e7e270..88baff3c6d5 100644 --- a/packages/truffle-debugger/test/data/codex.js +++ b/packages/truffle-debugger/test/data/codex.js @@ -2,6 +2,7 @@ import debugModule from "debug"; const debug = debugModule("test:data:codex"); import { assert } from "chai"; +import { Conversion as ConversionUtils } from "truffle-codec-utils"; import Ganache from "ganache-core"; @@ -126,7 +127,7 @@ describe("Codex", function() { await session.continueUntilBreakpoint(); //run till end debug("made it to end of transaction"); - const surface = (await session.variable("surface")).nativize(); + const surface = ConversionUtils.nativize(await session.variable("surface")); assert.equal(surface["ping"], 1); }); @@ -147,7 +148,7 @@ describe("Codex", function() { await session.continueUntilBreakpoint(); //run till end - const x = (await session.variable("x")).nativize(); + const x = ConversionUtils.nativize(await session.variable("x")); assert.equal(x, 1); }); @@ -168,7 +169,7 @@ describe("Codex", function() { await session.continueUntilBreakpoint(); //run till end - const x = (await session.variable("x")).nativize(); + const x = ConversionUtils.nativize(await session.variable("x")); assert.equal(x, 1); }); diff --git a/packages/truffle-debugger/test/data/helpers.js b/packages/truffle-debugger/test/data/helpers.js index 35b0b990204..23e7292fc38 100644 --- a/packages/truffle-debugger/test/data/helpers.js +++ b/packages/truffle-debugger/test/data/helpers.js @@ -4,6 +4,7 @@ const debug = debugModule("test:data:decode"); import Ganache from "ganache-core"; import { assert } from "chai"; import changeCase from "change-case"; +import { Conversion as ConversionUtils } from "truffle-codec-utils"; import { prepareContracts } from "test/helpers"; @@ -84,9 +85,7 @@ async function prepareDebugger(testName, sources) { } async function decode(name) { - let result = await this.session.variable(name); - - return result.nativize(); + return ConversionUtils.nativize(await this.session.variable(name)); } export function describeDecoding(testName, fixtures, selector, generateSource) { diff --git a/packages/truffle-debugger/test/data/ids.js b/packages/truffle-debugger/test/data/ids.js index b32668453a0..70d61ffb4cc 100644 --- a/packages/truffle-debugger/test/data/ids.js +++ b/packages/truffle-debugger/test/data/ids.js @@ -10,6 +10,7 @@ import Debugger from "lib/debugger"; import trace from "lib/trace/selectors"; import solidity from "lib/solidity/selectors"; +import { Conversion as ConversionUtils } from "truffle-codec-utils"; const __FACTORIAL = ` pragma solidity ^0.5.0; @@ -214,7 +215,7 @@ describe("Variable IDs", function() { await session.continueUntilBreakpoint(); while (!session.view(trace.finished)) { - values.push((await session.variable("nbang")).nativize()); + values.push(ConversionUtils.nativize(await session.variable("nbang"))); await session.continueUntilBreakpoint(); } diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 8d39d550da9..82564424bf6 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -2,7 +2,7 @@ import debugModule from "debug"; const debug = debugModule("decoder:contract"); import * as CodecUtils from "truffle-codec-utils"; -import { Types, Values } from "truffle-codec-utils"; +import { Types, Values, wrapElementaryViaDefinition } from "truffle-codec-utils"; import AsyncEventEmitter from "async-eventemitter"; import Web3 from "web3"; import { ContractObject } from "truffle-contract-schema/spec"; @@ -543,7 +543,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { break; case "mapping": let keyDefinition = parentDefinition.keyType || parentDefinition.typeName.keyType; - key = Values.wrapElementaryValue(rawIndex, keyDefinition); + key = wrapElementaryViaDefinition(rawIndex, keyDefinition); definition = parentDefinition.valueType || parentDefinition.typeName.valueType; slot = { path: parentSlot, diff --git a/packages/truffle-decoder/test/test/wire-test.js b/packages/truffle-decoder/test/test/wire-test.js index 1a62af42c72..bfe459f7a84 100644 --- a/packages/truffle-decoder/test/test/wire-test.js +++ b/packages/truffle-decoder/test/test/wire-test.js @@ -3,6 +3,7 @@ const assert = require("chai").assert; const BN = require("bn.js"); const TruffleDecoder = require("../../../truffle-decoder"); +const ConversionUtils = require("../../../truffle-codec-utils").Conversion; const WireTest = artifacts.require("WireTest"); const WireTestParent = artifacts.require("WireTestParent"); @@ -95,15 +96,18 @@ contract("WireTest", _accounts => { assert.strictEqual(constructorDecoding.class.typeName, "WireTest"); assert.lengthOf(constructorDecoding.arguments, 3); assert.strictEqual(constructorDecoding.arguments[0].name, "status"); - assert.strictEqual(constructorDecoding.arguments[0].value.nativize(), true); + assert.strictEqual( + ConversionUtils.nativize(constructorDecoding.arguments[0].value), + true + ); assert.strictEqual(constructorDecoding.arguments[1].name, "info"); assert.strictEqual( - constructorDecoding.arguments[1].value.nativize(), + ConversionUtils.nativize(constructorDecoding.arguments[1].value), "0xdeadbeef" ); assert.strictEqual(constructorDecoding.arguments[2].name, "whoknows"); assert.strictEqual( - constructorDecoding.arguments[2].value.nativize(), + ConversionUtils.nativize(constructorDecoding.arguments[2].value), "WireTest.Ternary.MaybeSo" ); @@ -113,17 +117,17 @@ contract("WireTest", _accounts => { assert.lengthOf(emitStuffDecoding.arguments, 3); assert.strictEqual(emitStuffDecoding.arguments[0].name, "p"); assert.deepEqual( - emitStuffDecoding.arguments[0].value.nativize(), + ConversionUtils.nativize(emitStuffDecoding.arguments[0].value), emitStuffArgs[0] ); assert.strictEqual(emitStuffDecoding.arguments[1].name, "precompiles"); assert.deepEqual( - emitStuffDecoding.arguments[1].value.nativize(), + ConversionUtils.nativize(emitStuffDecoding.arguments[1].value), emitStuffArgs[1] ); assert.strictEqual(emitStuffDecoding.arguments[2].name, "strings"); assert.deepEqual( - emitStuffDecoding.arguments[2].value.nativize(), + ConversionUtils.nativize(emitStuffDecoding.arguments[2].value), emitStuffArgs[2] ); @@ -133,12 +137,12 @@ contract("WireTest", _accounts => { assert.lengthOf(moreStuffDecoding.arguments, 2); assert.strictEqual(moreStuffDecoding.arguments[0].name, "notThis"); assert.strictEqual( - moreStuffDecoding.arguments[0].value.nativize(), + ConversionUtils.nativize(moreStuffDecoding.arguments[0].value), `WireTest(${moreStuffArgs[0]})` ); assert.strictEqual(moreStuffDecoding.arguments[1].name, "bunchOfInts"); assert.deepEqual( - moreStuffDecoding.arguments[1].value.nativize(), + ConversionUtils.nativize(moreStuffDecoding.arguments[1].value), moreStuffArgs[1] ); @@ -148,7 +152,7 @@ contract("WireTest", _accounts => { assert.lengthOf(inheritedDecoding.arguments, 1); assert.isUndefined(inheritedDecoding.arguments[0].name); assert.deepEqual( - inheritedDecoding.arguments[0].value.nativize(), + ConversionUtils.nativize(inheritedDecoding.arguments[0].value), inheritedArg ); @@ -165,12 +169,12 @@ contract("WireTest", _accounts => { assert.lengthOf(getterDecoding1.arguments, 2); assert.isUndefined(getterDecoding1.arguments[0].name); assert.strictEqual( - getterDecoding1.arguments[0].value.nativize(), + ConversionUtils.nativize(getterDecoding1.arguments[0].value), getter1Args[0] ); assert.isUndefined(getterDecoding1.arguments[1].name); assert.strictEqual( - getterDecoding1.arguments[1].value.nativize(), + ConversionUtils.nativize(getterDecoding1.arguments[1].value), getter1Args[1] ); @@ -180,12 +184,12 @@ contract("WireTest", _accounts => { assert.lengthOf(getterDecoding2.arguments, 2); assert.isUndefined(getterDecoding2.arguments[0].name); assert.strictEqual( - getterDecoding2.arguments[0].value.nativize(), + ConversionUtils.nativize(getterDecoding2.arguments[0].value), getter2Args[0] ); assert.isUndefined(getterDecoding2.arguments[1].name); assert.strictEqual( - getterDecoding2.arguments[1].value.nativize(), + ConversionUtils.nativize(getterDecoding2.arguments[1].value), getter2Args[1] ); @@ -274,17 +278,17 @@ contract("WireTest", _accounts => { assert.lengthOf(constructorEventDecoding.arguments, 3); assert.strictEqual(constructorEventDecoding.arguments[0].name, "bit"); assert.strictEqual( - constructorEventDecoding.arguments[0].value.nativize(), + ConversionUtils.nativize(constructorEventDecoding.arguments[0].value), true ); assert.isUndefined(constructorEventDecoding.arguments[1].name); assert.strictEqual( - constructorEventDecoding.arguments[1].value.nativize(), + ConversionUtils.nativize(constructorEventDecoding.arguments[1].value), "0xdeadbeef" ); assert.isUndefined(constructorEventDecoding.arguments[2].name); assert.strictEqual( - constructorEventDecoding.arguments[2].value.nativize(), + ConversionUtils.nativize(constructorEventDecoding.arguments[2].value), "WireTest.Ternary.MaybeSo" ); @@ -294,17 +298,17 @@ contract("WireTest", _accounts => { assert.lengthOf(emitStuffEventDecoding.arguments, 3); assert.isUndefined(emitStuffEventDecoding.arguments[0].name); assert.deepEqual( - emitStuffEventDecoding.arguments[0].value.nativize(), + ConversionUtils.nativize(emitStuffEventDecoding.arguments[0].value), emitStuffArgs[0] ); assert.isUndefined(emitStuffEventDecoding.arguments[1].name); assert.deepEqual( - emitStuffEventDecoding.arguments[1].value.nativize(), + ConversionUtils.nativize(emitStuffEventDecoding.arguments[1].value), emitStuffArgs[1] ); assert.isUndefined(emitStuffEventDecoding.arguments[2].name); assert.deepEqual( - emitStuffEventDecoding.arguments[2].value.nativize(), + ConversionUtils.nativize(emitStuffEventDecoding.arguments[2].value), emitStuffArgs[2] ); @@ -314,12 +318,12 @@ contract("WireTest", _accounts => { assert.lengthOf(moreStuffEventDecoding.arguments, 2); assert.isUndefined(moreStuffEventDecoding.arguments[0].name); assert.strictEqual( - moreStuffEventDecoding.arguments[0].value.nativize(), + ConversionUtils.nativize(moreStuffEventDecoding.arguments[0].value), `WireTest(${moreStuffArgs[0]})` ); assert.strictEqual(moreStuffEventDecoding.arguments[1].name, "data"); assert.deepEqual( - moreStuffEventDecoding.arguments[1].value.nativize(), + ConversionUtils.nativize(moreStuffEventDecoding.arguments[1].value), moreStuffArgs[1] ); @@ -334,27 +338,26 @@ contract("WireTest", _accounts => { assert.lengthOf(indexTestEventDecoding.arguments, 5); assert.isUndefined(indexTestEventDecoding.arguments[0].name); assert.strictEqual( - indexTestEventDecoding.arguments[0].value.nativize(), + ConversionUtils.nativize(indexTestEventDecoding.arguments[0].value), indexTestArgs[0] ); assert.isUndefined(indexTestEventDecoding.arguments[1].name); assert.deepEqual( - indexTestEventDecoding.arguments[1].value.nativize(), + ConversionUtils.nativize(indexTestEventDecoding.arguments[1].value), indexTestArgs[1] ); assert.isUndefined(indexTestEventDecoding.arguments[2].name); assert.deepEqual( - indexTestEventDecoding.arguments[2].value.nativize(), + ConversionUtils.nativize(indexTestEventDecoding.arguments[2].value), indexTestArgs[2] ); assert.isUndefined(indexTestEventDecoding.arguments[3].name); assert.isUndefined( - indexTestEventDecoding.arguments[3].value.nativize(), //can't decode indexed reference type! - indexTestArgs[3] + ConversionUtils.nativize(indexTestEventDecoding.arguments[3].value) //can't decode indexed reference type! ); assert.isUndefined(indexTestEventDecoding.arguments[4].name); assert.deepEqual( - indexTestEventDecoding.arguments[4].value.nativize(), + ConversionUtils.nativize(indexTestEventDecoding.arguments[4].value), indexTestArgs[4] ); @@ -367,7 +370,7 @@ contract("WireTest", _accounts => { assert.lengthOf(libraryTestEventDecoding.arguments, 1); assert.isUndefined(libraryTestEventDecoding.arguments[0].name); assert.strictEqual( - libraryTestEventDecoding.arguments[0].value.nativize(), + ConversionUtils.nativize(libraryTestEventDecoding.arguments[0].value), libraryTestArg ); @@ -376,7 +379,7 @@ contract("WireTest", _accounts => { assert.lengthOf(dangerEventDecoding.arguments, 1); assert.isUndefined(dangerEventDecoding.arguments[0].name); assert.strictEqual( - dangerEventDecoding.arguments[0].value.nativize(), + ConversionUtils.nativize(dangerEventDecoding.arguments[0].value), `WireTest(${address}).danger` ); }); @@ -426,10 +429,12 @@ contract("WireTest", _accounts => { ); assert.lengthOf(ambiguityTestContractDecoding.arguments, 2); assert.isUndefined( - ambiguityTestContractDecoding.arguments[0].value.nativize() + ConversionUtils.nativize(ambiguityTestContractDecoding.arguments[0].value) ); assert.deepEqual( - ambiguityTestContractDecoding.arguments[1].value.nativize(), + ConversionUtils.nativize( + ambiguityTestContractDecoding.arguments[1].value + ), [32, 3, 17, 18, 19] ); @@ -441,11 +446,11 @@ contract("WireTest", _accounts => { ); assert.lengthOf(ambiguityTestLibraryDecoding.arguments, 2); assert.deepEqual( - ambiguityTestLibraryDecoding.arguments[0].value.nativize(), + ConversionUtils.nativize(ambiguityTestLibraryDecoding.arguments[0].value), [17, 18, 19] ); assert.isUndefined( - ambiguityTestLibraryDecoding.arguments[1].value.nativize() + ConversionUtils.nativize(ambiguityTestLibraryDecoding.arguments[1].value) ); for (let decoding of unambiguousDecodings) { @@ -455,46 +460,46 @@ contract("WireTest", _accounts => { assert.strictEqual(unambiguousDecodings[0].class.typeName, "WireTest"); assert.lengthOf(unambiguousDecodings[0].arguments, 2); - assert.isUndefined(unambiguousDecodings[0].arguments[0].value.nativize()); - assert.deepEqual(unambiguousDecodings[0].arguments[1].value.nativize(), [ - 32, - 1e12, - 17, - 18, - 19 - ]); + assert.isUndefined( + ConversionUtils.nativize(unambiguousDecodings[0].arguments[0].value) + ); + assert.deepEqual( + ConversionUtils.nativize(unambiguousDecodings[0].arguments[1].value), + [32, 1e12, 17, 18, 19] + ); assert.strictEqual(unambiguousDecodings[1].class.typeName, "WireTest"); assert.lengthOf(unambiguousDecodings[1].arguments, 2); - assert.isUndefined(unambiguousDecodings[1].arguments[0].value.nativize()); - assert.deepEqual(unambiguousDecodings[1].arguments[1].value.nativize(), [ - 32, - 3, - 257, - 257, - 257 - ]); + assert.isUndefined( + ConversionUtils.nativize(unambiguousDecodings[1].arguments[0].value) + ); + assert.deepEqual( + ConversionUtils.nativize(unambiguousDecodings[1].arguments[1].value), + [32, 3, 257, 257, 257] + ); assert.strictEqual(unambiguousDecodings[2].class.typeName, "WireTest"); assert.lengthOf(unambiguousDecodings[2].arguments, 2); - assert.isUndefined(unambiguousDecodings[2].arguments[0].value.nativize()); - assert.deepEqual(unambiguousDecodings[2].arguments[1].value.nativize(), [ - 64, - 0, - 2, - 1, - 1 - ]); + assert.isUndefined( + ConversionUtils.nativize(unambiguousDecodings[2].arguments[0].value) + ); + assert.deepEqual( + ConversionUtils.nativize(unambiguousDecodings[2].arguments[1].value), + [64, 0, 2, 1, 1] + ); assert.strictEqual( unambiguousDecodings[3].class.typeName, "WireTestLibrary" ); assert.lengthOf(unambiguousDecodings[3].arguments, 2); - assert.deepEqual(unambiguousDecodings[3].arguments[0].value.nativize(), [ - 107 - ]); - assert.isUndefined(unambiguousDecodings[3].arguments[1].value.nativize()); + assert.deepEqual( + ConversionUtils.nativize(unambiguousDecodings[3].arguments[0].value), + [107] + ); + assert.isUndefined( + ConversionUtils.nativize(unambiguousDecodings[3].arguments[1].value) + ); }); it("Handles anonymous events", async () => { @@ -532,7 +537,7 @@ contract("WireTest", _accounts => { assert.lengthOf(anonymousTestEvents[0].decodings[0].arguments, 4); assert.deepEqual( anonymousTestEvents[0].decodings[0].arguments.map(({ value }) => - value.nativize() + ConversionUtils.nativize(value) ), [257, 1, 1, 1] ); @@ -547,7 +552,7 @@ contract("WireTest", _accounts => { assert.lengthOf(anonymousTestEvents[1].decodings[0].arguments, 4); assert.deepEqual( anonymousTestEvents[1].decodings[0].arguments.map(({ value }) => - value.nativize() + ConversionUtils.nativize(value) ), [1, 2, 3, 4] ); @@ -560,7 +565,7 @@ contract("WireTest", _accounts => { assert.lengthOf(anonymousTestEvents[1].decodings[1].arguments, 4); assert.deepEqual( anonymousTestEvents[1].decodings[1].arguments.map(({ value }) => - value.nativize() + ConversionUtils.nativize(value) ), [1, 2, 3, 4] ); @@ -575,7 +580,7 @@ contract("WireTest", _accounts => { assert.lengthOf(anonymousTestEvents[2].decodings[0].arguments, 3); assert.deepEqual( anonymousTestEvents[2].decodings[0].arguments.map(({ value }) => - value.nativize() + ConversionUtils.nativize(value) ), [1, 2, 3] ); @@ -590,7 +595,7 @@ contract("WireTest", _accounts => { assert.deepEqual( anonymousTestEvents[2].decodings[1].arguments .slice(1) - .map(({ value }) => value.nativize()), + .map(({ value }) => ConversionUtils.nativize(value)), [1, 2, 3] ); assert( @@ -611,7 +616,9 @@ contract("WireTest", _accounts => { ); assert.lengthOf(anonymousTestEvents[3].decodings[0].arguments, 1); assert.strictEqual( - anonymousTestEvents[3].decodings[0].arguments[0].value.nativize(), + ConversionUtils.nativize( + anonymousTestEvents[3].decodings[0].arguments[0].value + ), "0xfe" ); @@ -625,7 +632,9 @@ contract("WireTest", _accounts => { assert.strictEqual(specifiedNameDecoding.class.typeName, "WireTestLibrary"); assert.lengthOf(specifiedNameDecoding.arguments, 4); assert.deepEqual( - specifiedNameDecoding.arguments.map(({ value }) => value.nativize()), + specifiedNameDecoding.arguments.map(({ value }) => + ConversionUtils.nativize(value) + ), [1, 2, 3, 4] ); }); From a6a718abf5ecf974a658ff33da77845164c8fb0b Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 11 Jul 2019 22:47:39 -0400 Subject: [PATCH 68/89] Fix bugs related to compiler location This commit fixes a bug where the debugger couldn't find the compiler for interfaces or abstract contracts. It also fixes an oversight where prerelease versions of 0.5.0 wouldn't be counted as 0.5.0. --- packages/truffle-codec-utils/src/definition.ts | 5 ++++- packages/truffle-debugger/lib/data/selectors/index.js | 8 +++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/truffle-codec-utils/src/definition.ts b/packages/truffle-codec-utils/src/definition.ts index 21b766f96b7..440d9e3f1d6 100644 --- a/packages/truffle-codec-utils/src/definition.ts +++ b/packages/truffle-codec-utils/src/definition.ts @@ -143,7 +143,10 @@ export namespace Definition { if(compiler === null) { return false; } - if(semver.satisfies(compiler.version, ">=0.5.0", {includePrerelease: true})) { + if(semver.satisfies(compiler.version, "~0.5 || >=0.5.0", {includePrerelease: true})) { + //note that we use ~0.5 || >=0.5.0 to make sure we include prerelease versions of + //0.5.0 (no, that is *not* what the includePrerelease flag doesn; that allows + //prerelease versions to be included *at all*) return typeIdentifier(definition) === "t_address_payable"; } else { diff --git a/packages/truffle-debugger/lib/data/selectors/index.js b/packages/truffle-debugger/lib/data/selectors/index.js index 7258a819ea6..458d2b200f5 100644 --- a/packages/truffle-debugger/lib/data/selectors/index.js +++ b/packages/truffle-debugger/lib/data/selectors/index.js @@ -150,16 +150,14 @@ const data = createSelectorTree({ userDefinedTypes: { //user-defined types for passing to the decoder _: createLeaf( - ["../referenceDeclarations", "../scopes/inlined", "../contexts"], - (referenceDeclarations, scopes, contexts) => { - const types = ["ContractDefinition", "SourceUnit"]; - //SourceUnit included as fallback + ["../referenceDeclarations", "/info/scopes", solidity.info.sources], + (referenceDeclarations, scopes, sources) => { return Object.assign( {}, ...Object.entries(referenceDeclarations).map(([id, node]) => ({ [id]: CodecUtils.Types.definitionToStoredType( node, - contexts[findAncestorOfType(node, types, scopes).id].compiler, + sources[scopes[node.id].sourceId].compiler, referenceDeclarations ) })) From e2dfe4e81adf5c2e182dfcf2396b81e902afbda3 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Fri, 12 Jul 2019 00:46:52 -0400 Subject: [PATCH 69/89] Add files I forgot to commit (oops!) --- packages/truffle-codec-utils/src/wrap.ts | 86 ++++++++++++++++++ packages/truffle-codec/lib/encode/key.ts | 107 +++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 packages/truffle-codec-utils/src/wrap.ts create mode 100644 packages/truffle-codec/lib/encode/key.ts diff --git a/packages/truffle-codec-utils/src/wrap.ts b/packages/truffle-codec-utils/src/wrap.ts new file mode 100644 index 00000000000..167ed2144d5 --- /dev/null +++ b/packages/truffle-codec-utils/src/wrap.ts @@ -0,0 +1,86 @@ +import debugModule from "debug"; +const debug = debugModule("codec-utils:wrap"); + +import Web3 from "web3"; +import BN from "bn.js"; +import { AstDefinition } from "./ast"; +import { Types } from "./types/types"; +import { Values } from "./types/values"; + +//Function for wrapping a value as an ElementaryValue +//WARNING: this function does not check its inputs! Please check before using! +//How to use: +//numbers may be BN, number, or numeric string +//strings should be given as strings. duh. +//bytes should be given as hex strings beginning with "0x" +//addresses are like bytes; checksum case is not required +//booleans may be given either as booleans, or as string "true" or "false" +//[NOTE: in the future this function will: +//1. check its inputs, +//2. take a slightly different input format, +//3. also be named differently and... it'll be different :P ] +export function wrapElementaryViaDefinition(value: any, definition: AstDefinition): Values.ElementaryValue { + //force location to undefined, force address to nonpayable + //(we force address to nonpayable since address payable can't be declared + //as a mapping key type) + let dataType = Types.definitionToType(definition, null, null); + return wrapElementaryValue(value, dataType); +} + +export function wrapElementaryValue(value: any, dataType: Types.Type): Values.ElementaryValue { + switch(dataType.typeClass) { + case "string": + return { + type: dataType, + kind: "value", + value: { + kind: "valid", + asString: value + } + }; + case "bytes": + //NOTE: in the future should add padding for static case + return { //TS is so bad at unions + type: dataType, + kind: "value", + value: { + asHex: value + } + }; + case "address": + value = Web3.utils.toChecksumAddress(value); + return { + type: dataType, + kind: "value", + value: { + asAddress: value + } + }; + case "uint": + case "int": + if(value instanceof BN) { + value = value.clone(); + } + else { + value = new BN(value); + } + return { //TS remains bad at unions + type: dataType, + kind: "value", + value: { + asBN: value + } + }; + case "bool": + if(typeof value === "string") { + value = value !== "false"; + } + return { + type: dataType, + kind: "value", + value: { + asBool: value + } + }; + } +} diff --git a/packages/truffle-codec/lib/encode/key.ts b/packages/truffle-codec/lib/encode/key.ts new file mode 100644 index 00000000000..f63bafdfbba --- /dev/null +++ b/packages/truffle-codec/lib/encode/key.ts @@ -0,0 +1,107 @@ +import { Values, Conversion as ConversionUtils, EVM as EVMUtils } from "truffle-codec-utils"; +import { stringToBytes } from "./abi"; + +//UGH -- it turns out TypeScript can't handle nested tagged unions +//see: https://github.com/microsoft/TypeScript/issues/18758 +//so, I'm just going to have to throw in a bunch of type coercions >_> + +export function encodeMappingKey(input: Values.ElementaryValue): Uint8Array { + let bytes: Uint8Array; + //TypeScript can at least infer in the rest of this that we're looking + //at a value, not an error! But that's hardly enough... + switch(input.type.typeClass) { + case "uint": + case "int": + return ConversionUtils.toBytes((input).value.asBN, EVMUtils.WORD_SIZE); + case "bool": { + bytes = new Uint8Array(EVMUtils.WORD_SIZE); //is initialized to zeroes + if((input).value.asBool) { + bytes[EVMUtils.WORD_SIZE - 1] = 1; + } + return bytes; + } + case "bytes": + bytes = ConversionUtils.toBytes((input).value.asHex); + switch(input.type.kind) { + case "static": + let padded = new Uint8Array(EVMUtils.WORD_SIZE); //initialized to zeroes + padded.set(bytes); + return padded; + case "dynamic": + return bytes; //NO PADDING IS USED + } + case "address": + return ConversionUtils.toBytes((input).value.asAddress, EVMUtils.WORD_SIZE); + case "string": { + let coercedInput: Values.StringValue = input; + switch(coercedInput.value.kind) { //NO PADDING IS USED + case "valid": + return stringToBytes(coercedInput.value.asString); + case "malformed": + return ConversionUtils.toBytes(coercedInput.value.asHex); + } + } + //fixed and ufixed are skipped for now + } +} + +export function mappingKeyAsHex(input: Values.ElementaryValue): string { + return ConversionUtils.toHexString(encodeMappingKey(input)); +} + +//this is like the old toSoliditySha3Input, but for debugging purposes ONLY +//it will NOT produce correct input to soliditySha3 +//please use mappingKeyAsHex instead if you wish to encode a mapping key. +export function keyInfoForPrinting(input: Values.ElementaryValue): {type: string, value: string} { + switch(input.type.typeClass) { + case "uint": + return { + type: "uint", + value: (input).value.asBN.toString() + }; + case "int": + return { + type: "int", + value: (input).value.asBN.toString() + }; + case "bool": + //this is the case that won't work as valid input to soliditySha3 :) + return { + type: "uint", + value: (input).value.asBool.toString() + }; + case "bytes": + switch(input.type.kind) { + case "static": + return { + type: "bytes32", + value: (input).value.asHex + }; + case "dynamic": + return { + type: "bytes", + value: (input).value.asHex + }; + } + case "address": + return { + type: "address", + value: (input).value.asAddress + }; + case "string": + let coercedInput: Values.StringValue = input; + switch(coercedInput.value.kind) { + case "valid": + return { + type: "string", + value: coercedInput.value.asString + }; + case "malformed": + return { + type: "bytes", + value: coercedInput.value.asHex + }; + } + //fixed and ufixed are skipped for now + } +} From 2e9fdd5bcacf63f808ff997d8d85ff66051745a9 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Fri, 12 Jul 2019 15:29:28 -0400 Subject: [PATCH 70/89] Add decodingMode; add tuples (sort of); add optional hints; shrink try in event decoder --- .../truffle-codec-utils/src/types/errors.ts | 9 ++ .../truffle-codec-utils/src/types/types.ts | 21 +++- .../truffle-codec-utils/src/types/values.ts | 14 +++ packages/truffle-codec/lib/encode/abi.ts | 2 + .../truffle-codec/lib/interface/decoding.ts | 119 +++++++++--------- packages/truffle-codec/lib/types/wire.ts | 8 ++ 6 files changed, 115 insertions(+), 58 deletions(-) diff --git a/packages/truffle-codec-utils/src/types/errors.ts b/packages/truffle-codec-utils/src/types/errors.ts index 2e5bb9434aa..1498e5a1834 100644 --- a/packages/truffle-codec-utils/src/types/errors.ts +++ b/packages/truffle-codec-utils/src/types/errors.ts @@ -202,6 +202,15 @@ export namespace Errors { export type StructError = never; + //Tuples + export interface TupleErrorResult { + type: Types.TupleType; + kind: "error"; + error: GenericError | TupleError; + } + + export type TupleError = never; + //Magic variables export interface MagicErrorResult { type: Types.MagicType; diff --git a/packages/truffle-codec-utils/src/types/types.ts b/packages/truffle-codec-utils/src/types/types.ts index 7d9317a66f9..2e46781a7cc 100644 --- a/packages/truffle-codec-utils/src/types/types.ts +++ b/packages/truffle-codec-utils/src/types/types.ts @@ -33,11 +33,14 @@ export namespace Types { export type Type = UintType | IntType | BoolType | BytesType | AddressType | FixedType | UfixedType | StringType | ArrayType | MappingType | FunctionType - | StructType | EnumType | ContractType | MagicType; + | StructType | EnumType | ContractType | MagicType | TupleType; export interface UintType { typeClass: "uint"; bits: number; + enumTypeNameHint?: string; + enumKindHint?: "local" | "global"; + enumDefiningContractHint?: string; } export interface IntType { @@ -66,6 +69,7 @@ export namespace Types { export interface AddressType { typeClass: "address"; payable: boolean; + contractTypeNameHint?: string; } export interface StringType { @@ -159,7 +163,7 @@ export namespace Types { typeName: string; definingContractName: string; definingContract?: ContractTypeNative; - memberTypes?: {name: string, type: Type}[]; //these should be in order + memberTypes?: NameTypePair[]; //these should be in order location?: Ast.Location; } @@ -172,6 +176,19 @@ export namespace Types { location?: Ast.Location; } + export interface OptionallyNamedType { + name?: string; + type: Type; + } + + export interface TupleType { + typeClass: "tuple"; + memberTypes: OptionallyNamedType[]; + structTypeNameHint?: string; + structKindHint?: "local" | "global"; + structDefiningContractHint?: string; + } + export type EnumType = EnumTypeLocal | EnumTypeGlobal; export interface EnumTypeLocal { diff --git a/packages/truffle-codec-utils/src/types/values.ts b/packages/truffle-codec-utils/src/types/values.ts index f9815748290..ca5d7ec3405 100644 --- a/packages/truffle-codec-utils/src/types/values.ts +++ b/packages/truffle-codec-utils/src/types/values.ts @@ -207,6 +207,20 @@ export namespace Values { value: Result; } + //Tuples + export type TupleResult = TupleValue | Errors.TupleErrorResult; + + export interface TupleValue { + type: Types.TupleType; + kind: "value"; + value: OptionallyNamedValue[]; + } + + export interface OptionallyNamedValue { + name?: string; + value: Result; + } + //Magic variables export type MagicResult = MagicValue | Errors.MagicErrorResult; diff --git a/packages/truffle-codec/lib/encode/abi.ts b/packages/truffle-codec/lib/encode/abi.ts index 6ef25ce4f7a..7685db1ed13 100644 --- a/packages/truffle-codec/lib/encode/abi.ts +++ b/packages/truffle-codec/lib/encode/abi.ts @@ -8,6 +8,8 @@ import utf8 from "utf8"; //see: https://github.com/microsoft/TypeScript/issues/18758 //so, I'm just going to have to throw in a bunch of type coercions >_> +//NOTE: Tuple (as opposed to struct) is not supported yet! +//Coming soon though! export function encodeAbi(input: Values.Result, allocations?: AbiAllocations): Uint8Array | undefined { //errors can't be encoded if(input.kind === "error") { diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index f3e33c4ea39..8463d8c6c3b 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -24,7 +24,8 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator (yield* decode( + allocationAttempts: for(const allocation of possibleAllocationsTotal) { + //first: do a name check so we can skip decoding if name is wrong + if(targetName !== undefined && allocation.definition.name !== targetName) { + continue; + } + const id = allocation.contractId; + const attemptContext = info.contexts[id]; + const contractType = CodecUtils.Contexts.contextToType(attemptContext); + //you can't map with a generator, so we have to do this map manually + let decodedArguments: AbiArgument[] = []; + for(const argumentAllocation of allocation.arguments) { + let value; + try { + value = (yield* decode( Types.definitionToType(argumentAllocation.definition, attemptContext.compiler), argumentAllocation.pointer, info, { strictAbiMode: true } //turns on STRICT MODE to cause more errors to be thrown )); - const name = argumentAllocation.definition.name; - const indexed = argumentAllocation.pointer.location === "eventtopic"; - decodedArguments.push( - name //deliberate general falsiness test - ? { name, indexed, value } - : { indexed, value } - ); } - debug("decodedArguments: %O", decodedArguments); - //OK, so, having decoded the result, the question is: does it reencode to the original? - //first, we have to filter out the indexed arguments, and also get rid of the name information - const nonIndexedValues = decodedArguments - .filter(argument => !argument.indexed) - .map(argument => argument.value); - //now, we can encode! - debug("nonIndexedValues: %O", nonIndexedValues); - const reEncodedData = encodeTupleAbi(nonIndexedValues, info.allocations.abi); - //are they equal? - const encodedData = info.state.eventdata; //again, not great to read this directly, but oh well - if(CodecUtils.EVM.equalData(encodedData, reEncodedData)) { - if(allocation.definition.anonymous) { - decodings.push({ - kind: "anonymous", - class: contractType, - name: allocation.definition.name, - arguments: decodedArguments - }); - } - else { - decodings.push({ - kind: "event", - class: contractType, - name: allocation.definition.name, - arguments: decodedArguments, - selector - }); - } + catch(_) { + continue allocationAttempts; //if an error occurred, this isn't a valid decoding! } - //otherwise, just move on + const name = argumentAllocation.definition.name; + const indexed = argumentAllocation.pointer.location === "eventtopic"; + decodedArguments.push( + name //deliberate general falsiness test + ? { name, indexed, value } + : { indexed, value } + ); } - catch(error) { - continue; //if an error occurred, this isn't a valid decoding! + debug("decodedArguments: %O", decodedArguments); + //OK, so, having decoded the result, the question is: does it reencode to the original? + //first, we have to filter out the indexed arguments, and also get rid of the name information + const nonIndexedValues = decodedArguments + .filter(argument => !argument.indexed) + .map(argument => argument.value); + //now, we can encode! + debug("nonIndexedValues: %O", nonIndexedValues); + const reEncodedData = encodeTupleAbi(nonIndexedValues, info.allocations.abi); + //are they equal? + const encodedData = info.state.eventdata; //again, not great to read this directly, but oh well + if(CodecUtils.EVM.equalData(encodedData, reEncodedData)) { + if(allocation.definition.anonymous) { + decodings.push({ + kind: "anonymous", + class: contractType, + name: allocation.definition.name, + arguments: decodedArguments, + decodingMode: "full" + }); + } + else { + decodings.push({ + kind: "event", + class: contractType, + name: allocation.definition.name, + arguments: decodedArguments, + selector, + decodingMode: "full" + }); + } } + //otherwise, just move on } return decodings; } diff --git a/packages/truffle-codec/lib/types/wire.ts b/packages/truffle-codec/lib/types/wire.ts index b3bf8dfcbb9..b0e74953412 100644 --- a/packages/truffle-codec/lib/types/wire.ts +++ b/packages/truffle-codec/lib/types/wire.ts @@ -3,12 +3,15 @@ import * as CodecUtils from "truffle-codec-utils"; export type CalldataDecoding = FunctionDecoding | ConstructorDecoding | FallbackDecoding | UnknownDecoding; export type LogDecoding = EventDecoding | AnonymousDecoding; +export type DecodingMode = "full" | "abi"; + export interface FunctionDecoding { kind: "function"; class: CodecUtils.Types.ContractType; arguments: AbiArgument[]; name: string; selector: string; + decodingMode: DecodingMode; } export interface ConstructorDecoding { @@ -16,16 +19,19 @@ export interface ConstructorDecoding { class: CodecUtils.Types.ContractType; arguments: AbiArgument[]; bytecode: string; + decodingMode: DecodingMode; } export interface FallbackDecoding { kind: "fallback"; class: CodecUtils.Types.ContractType; data: string; + decodingMode: DecodingMode; } export interface UnknownDecoding { kind: "unknown"; + decodingMode: DecodingMode; } export interface EventDecoding { @@ -34,6 +40,7 @@ export interface EventDecoding { arguments: AbiArgument[]; name: string; selector: string; + decodingMode: DecodingMode; } export interface AnonymousDecoding { @@ -41,6 +48,7 @@ export interface AnonymousDecoding { class: CodecUtils.Types.ContractType; arguments: AbiArgument[]; name: string; + decodingMode: DecodingMode; } export interface AbiArgument { From f68962d3b14cbb63d6fd0cc7f15a92bbf4197ba5 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Mon, 15 Jul 2019 17:30:11 -0400 Subject: [PATCH 71/89] DecodeLog takes options rather than name, unknown includes data, fix stupid non-use of yield* --- .../truffle-codec/lib/interface/decoding.ts | 120 +++++++++--------- packages/truffle-codec/lib/types/wire.ts | 1 + packages/truffle-decoder/lib/contract.ts | 14 +- packages/truffle-decoder/lib/wire.ts | 14 +- 4 files changed, 82 insertions(+), 67 deletions(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index f3e33c4ea39..83ba77ec409 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -24,7 +24,8 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator read( + let rawSelector = (yield* read( { location: "calldata", start: 0, length: CodecUtils.EVM.SELECTOR_SIZE }, info.state - ).next().value; //no requests should occur, we can just get the first value + )); selector = CodecUtils.Conversion.toHexString(rawSelector); allocation = allocations.functionAllocations[selector]; } @@ -92,6 +93,10 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator { const allocations = info.allocations.event; debug("event allocations: %O", allocations); @@ -102,12 +107,12 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName?: string const topicsCount = info.state.eventtopics.length; //yeah, it's not great to read directly from the state like this (bypassing read), but what are you gonna do? if(topicsCount > 0) { - rawSelector = read( + rawSelector = (yield* read( { location: "eventtopic", topic: 0 }, info.state - ).next().value; //no requests should occur, we can just get the first value + )); selector = CodecUtils.Conversion.toHexString(rawSelector); ({ contract: contractAllocations, library: libraryAllocations } = allocations[topicsCount].bySelector[selector] || {contract: {}, library: {}}); } @@ -151,67 +156,68 @@ export function* decodeEvent(info: EvmInfo, address: string, targetName?: string const possibleAnonymousAllocations = possibleContractAnonymousAllocations.concat(possibleLibraryAnonymousAllocations); const possibleAllocationsTotal = possibleAllocations.concat(possibleAnonymousAllocations); let decodings: LogDecoding[] = []; - for(const allocation of possibleAllocationsTotal) { - try { - //first: do a name check so we can skip decoding if name is wrong - if(targetName !== undefined && allocation.definition.name !== targetName) { - continue; - } - const id = allocation.contractId; - const attemptContext = info.contexts[id]; - const contractType = CodecUtils.Contexts.contextToType(attemptContext); - //you can't map with a generator, so we have to do this map manually - let decodedArguments: AbiArgument[] = []; - for(const argumentAllocation of allocation.arguments) { - const value = (yield* decode( + allocationAttempts: for(const allocation of possibleAllocationsTotal) { + //first: do a name check so we can skip decoding if name is wrong + if(targetName !== undefined && allocation.definition.name !== targetName) { + continue; + } + const id = allocation.contractId; + const attemptContext = info.contexts[id]; + const contractType = CodecUtils.Contexts.contextToType(attemptContext); + //you can't map with a generator, so we have to do this map manually + let decodedArguments: AbiArgument[] = []; + for(const argumentAllocation of allocation.arguments) { + let value: Values.Result; + try { + value = (yield* decode( Types.definitionToType(argumentAllocation.definition, attemptContext.compiler), argumentAllocation.pointer, info, { strictAbiMode: true } //turns on STRICT MODE to cause more errors to be thrown )); - const name = argumentAllocation.definition.name; - const indexed = argumentAllocation.pointer.location === "eventtopic"; - decodedArguments.push( - name //deliberate general falsiness test - ? { name, indexed, value } - : { indexed, value } - ); } - debug("decodedArguments: %O", decodedArguments); - //OK, so, having decoded the result, the question is: does it reencode to the original? - //first, we have to filter out the indexed arguments, and also get rid of the name information - const nonIndexedValues = decodedArguments - .filter(argument => !argument.indexed) - .map(argument => argument.value); - //now, we can encode! - debug("nonIndexedValues: %O", nonIndexedValues); - const reEncodedData = encodeTupleAbi(nonIndexedValues, info.allocations.abi); - //are they equal? - const encodedData = info.state.eventdata; //again, not great to read this directly, but oh well - if(CodecUtils.EVM.equalData(encodedData, reEncodedData)) { - if(allocation.definition.anonymous) { - decodings.push({ - kind: "anonymous", - class: contractType, - name: allocation.definition.name, - arguments: decodedArguments - }); - } - else { - decodings.push({ - kind: "event", - class: contractType, - name: allocation.definition.name, - arguments: decodedArguments, - selector - }); - } + catch(_) { + continue allocationAttempts; //if an error occurs, it's not a valid decoding } - //otherwise, just move on + const name = argumentAllocation.definition.name; + const indexed = argumentAllocation.pointer.location === "eventtopic"; + decodedArguments.push( + name //deliberate general falsiness test + ? { name, indexed, value } + : { indexed, value } + ); } - catch(error) { - continue; //if an error occurred, this isn't a valid decoding! + debug("decodedArguments: %O", decodedArguments); + //OK, so, having decoded the result, the question is: does it reencode to the original? + //first, we have to filter out the indexed arguments, and also get rid of the name information + const nonIndexedValues = decodedArguments + .filter(argument => !argument.indexed) + .map(argument => argument.value); + //now, we can encode! + debug("nonIndexedValues: %O", nonIndexedValues); + const reEncodedData = encodeTupleAbi(nonIndexedValues, info.allocations.abi); + //are they equal? + const encodedData = info.state.eventdata; //again, not great to read this directly, but oh well + if(CodecUtils.EVM.equalData(encodedData, reEncodedData)) { + if(allocation.definition.anonymous) { + decodings.push({ + kind: "anonymous", + class: contractType, + name: allocation.definition.name, + arguments: decodedArguments + }); + } + else { + decodings.push({ + kind: "event", + class: contractType, + name: allocation.definition.name, + arguments: decodedArguments, + selector + }); + } } + //otherwise, just move on } return decodings; } diff --git a/packages/truffle-codec/lib/types/wire.ts b/packages/truffle-codec/lib/types/wire.ts index b3bf8dfcbb9..1c248ed89ec 100644 --- a/packages/truffle-codec/lib/types/wire.ts +++ b/packages/truffle-codec/lib/types/wire.ts @@ -26,6 +26,7 @@ export interface FallbackDecoding { export interface UnknownDecoding { kind: "unknown"; + data: string; } export interface EventDecoding { diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 8d39d550da9..cbb6382a0b6 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -410,7 +410,9 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } //NOTE: will only work with logs for this address! - public async decodeLog(log: Log, name?: string): Promise { + //NOTE: options is mostly meant for internal use (when called from events()), + //but hey, you can pass it if you really want + public async decodeLog(log: Log, options: DecoderTypes.EventOptions = {}): Promise { if(log.address !== this.contractAddress) { throw new DecoderTypes.EventOrTransactionIsNotForThisContractError(log.address, this.contractAddress); } @@ -427,7 +429,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contextsById }; - const decoder = Codec.decodeEvent(info, log.address, name); + const decoder = Codec.decodeEvent(info, log.address, options.name); let result = decoder.next(); while(!result.done) { @@ -449,8 +451,10 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } //NOTE: will only work with logs for this address! - public async decodeLogs(logs: Log[], name?: string): Promise { - return await Promise.all(logs.map(log => this.decodeLog(log, name))); + //NOTE: options is mostly meant for internal use (when called from events()), + //but hey, you can pass it if you really want + public async decodeLogs(logs: Log[], options: DecoderTypes.EventOptions = {}): Promise { + return await Promise.all(logs.map(log => this.decodeLog(log, options))); } public async events(options: DecoderTypes.EventOptions = {}): Promise { @@ -463,7 +467,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { toBlock, }); - let events = await this.decodeLogs(logs, name); + let events = await this.decodeLogs(logs, options); //if a target name was specified, we'll restrict to events that decoded //to something with that name. (note that only decodings with that name diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 48906a950c9..2188d750ce5 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -178,7 +178,9 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - public async decodeLog(log: Log, name?: string): Promise { + //NOTE: options is mostly meant for internal use (when called from events()), + //but hey, you can pass it if you really want + public async decodeLog(log: Log, options: DecoderTypes.EventOptions = {}): Promise { const block = log.blockNumber; const data = CodecUtils.Conversion.toBytes(log.data); const topics = log.topics.map(CodecUtils.Conversion.toBytes); @@ -192,7 +194,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { allocations: this.allocations, contexts: this.contextsById }; - const decoder = Codec.decodeEvent(info, log.address, name); + const decoder = Codec.decodeEvent(info, log.address, options.name); let result = decoder.next(); while(!result.done) { @@ -213,8 +215,10 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - public async decodeLogs(logs: Log[], name?: string): Promise { - return await Promise.all(logs.map(log => this.decodeLog(log, name))); + //NOTE: options is mostly meant for internal use (when called from events()), + //but hey, you can pass it if you really want + public async decodeLogs(logs: Log[], options: DecoderTypes.EventOptions = {}): Promise { + return await Promise.all(logs.map(log => this.decodeLog(log, options))); } public async events(options: DecoderTypes.EventOptions = {}): Promise { @@ -226,7 +230,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { toBlock, }); - let events = await this.decodeLogs(logs, name); + let events = await this.decodeLogs(logs, options); debug("events: %o", events); //if a target name was specified, we'll restrict to events that decoded From c75dabc21f8a5c82a67756ebdfa89e3988116fa8 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Mon, 15 Jul 2019 18:26:34 -0400 Subject: [PATCH 72/89] Use replacement characters in nativize & viewer; add strict-mode error for overlarge pointer --- .../truffle-codec-utils/src/conversion.ts | 3 ++- .../truffle-codec-utils/src/types/errors.ts | 11 +++++++-- .../truffle-codec-utils/src/types/inspect.ts | 5 ++-- packages/truffle-codec/lib/decode/abi.ts | 23 +++++++++++++++---- packages/truffle-codec/lib/decode/value.ts | 4 ++++ 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/packages/truffle-codec-utils/src/conversion.ts b/packages/truffle-codec-utils/src/conversion.ts index 80a448465b2..0a6d7eee4f3 100644 --- a/packages/truffle-codec-utils/src/conversion.ts +++ b/packages/truffle-codec-utils/src/conversion.ts @@ -188,7 +188,8 @@ export namespace Conversion { case "valid": return coercedResult.value.asString; case "malformed": - return coercedResult.value.asHex; //WARNING + // this will turn malformed utf-8 into replacement characters (U+FFFD) (WARNING) + return Buffer.from(coercedResult.value.asHex, 'hex').toString(); } } //fixed and ufixed are skipped for now diff --git a/packages/truffle-codec-utils/src/types/errors.ts b/packages/truffle-codec-utils/src/types/errors.ts index 1498e5a1834..a7d5f74e0e8 100644 --- a/packages/truffle-codec-utils/src/types/errors.ts +++ b/packages/truffle-codec-utils/src/types/errors.ts @@ -387,16 +387,23 @@ export namespace Errors { } /* SECTION 9: Internal use errors */ + /* you should never see these returned. + * they are only for internal use. */ - export type InternalUseError = OverlongArrayOrStringError | InternalFunctionInABIError; + export type InternalUseError = OverlongArrayOrStringError | PointerTooLargeError | InternalFunctionInABIError; - //you should never see this returned. this is only for internal use. export interface OverlongArrayOrStringError { kind: "OverlongArrayOrStringError"; lengthAsBN: BN; dataLength: number; } + export interface PointerTooLargeError { + kind: "PointerTooLargeError"; + pointerAsBN: BN; + dataLength: number; + } + //this one should never come up at all, but just to be sure... export interface InternalFunctionInABIError { kind: "InternalFunctionInABIError"; diff --git a/packages/truffle-codec-utils/src/types/inspect.ts b/packages/truffle-codec-utils/src/types/inspect.ts index 9d032a81b32..ef4ebdedf42 100644 --- a/packages/truffle-codec-utils/src/types/inspect.ts +++ b/packages/truffle-codec-utils/src/types/inspect.ts @@ -44,7 +44,7 @@ export class ResultInspector { case "static": return options.stylize(hex, "number"); case "dynamic": - return styleHexString(hex, options); + return options.stylize(`hex'${hex.slice(2)}'`, "string"); } case "address": return options.stylize((this.result).value.asAddress, "number"); @@ -54,7 +54,8 @@ export class ResultInspector { case "valid": return util.inspect(coercedResult.value.asString, options); case "malformed": - return styleHexString(coercedResult.value.asHex, options) + " (malformed)"; + //note: this will turn malformed utf-8 into replacement characters (U+FFFD) + return util.inspect(Buffer.from(coercedResult.value.asHex, 'hex').toString()); } } case "array": { diff --git a/packages/truffle-codec/lib/decode/abi.ts b/packages/truffle-codec/lib/decode/abi.ts index 4c828ffe71f..95c99f7b1b3 100644 --- a/packages/truffle-codec/lib/decode/abi.ts +++ b/packages/truffle-codec/lib/decode/abi.ts @@ -68,7 +68,20 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin }; } - let startPosition = CodecUtils.Conversion.toBN(rawValue).toNumber() + base; + let rawValueAsBN = CodecUtils.Conversion.toBN(rawValue); + if(strict && rawValueAsBN.gtn(state[location].length)) { + //why is this check here?? + //it's really just to protect us against the toNumber() + //conversion :) + throw new StopDecodingError( + { + kind: "PointerTooLargeError", + pointerAsBN: rawValueAsBN, + dataLength: state[location].length + } + ); + } + let startPosition = rawValueAsBN.toNumber() + base; debug("startPosition %d", startPosition); let dynamic: boolean; @@ -132,7 +145,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin }; } let lengthAsBN = CodecUtils.Conversion.toBN(rawLength); - if(strict && lengthAsBN.gtn(info.state[location].length)) { + if(strict && lengthAsBN.gtn(state[location].length)) { //you may notice that the comparison is a bit crude; that's OK, this is //just to prevent huge numbers from DOSing us, other errors will still //be caught regardless @@ -140,7 +153,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin { kind: "OverlongArrayOrStringError", lengthAsBN, - dataLength: info.state[location].length + dataLength: state[location].length } ); } @@ -177,7 +190,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin }; } let lengthAsBN = CodecUtils.Conversion.toBN(rawLength); - if(strict && lengthAsBN.gtn(info.state[location].length)) { + if(strict && lengthAsBN.gtn(state[location].length)) { //you may notice that the comparison is a bit crude; that's OK, this is //just to prevent huge numbers from DOSing us, other errors will still //be caught regardless @@ -185,7 +198,7 @@ export function* decodeAbiReferenceByAddress(dataType: Types.ReferenceType, poin { kind: "OverlongArrayOrStringError", lengthAsBN, - dataLength: info.state[location].length + dataLength: state[location].length } ); } diff --git a/packages/truffle-codec/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts index 5dc08db180e..2eff80e6077 100644 --- a/packages/truffle-codec/lib/decode/value.ts +++ b/packages/truffle-codec/lib/decode/value.ts @@ -369,6 +369,10 @@ export function decodeString(bytes: Uint8Array): Values.StringValueInfo { try { //this will throw an error if we have malformed UTF-8 let correctlyEncodedString = utf8.decode(badlyEncodedString); + //NOTE: we don't use node's builtin Buffer class to do the UTF-8 decoding + //here, because that handles malformed UTF-8 by means of replacement characters + //(U+FFFD). That loses information. So we use the utf8 package instead, + //and... well, see the catch block below. return { kind: "valid", asString: correctlyEncodedString From a01ae977792874ccd7caf0c0a2bac3c0d353ff55 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Mon, 15 Jul 2019 18:48:14 -0400 Subject: [PATCH 73/89] Fix incorrect use of Buffer.from() --- packages/truffle-codec-utils/src/conversion.ts | 3 ++- packages/truffle-codec-utils/src/types/inspect.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/truffle-codec-utils/src/conversion.ts b/packages/truffle-codec-utils/src/conversion.ts index 0a6d7eee4f3..458a4e852d1 100644 --- a/packages/truffle-codec-utils/src/conversion.ts +++ b/packages/truffle-codec-utils/src/conversion.ts @@ -189,7 +189,8 @@ export namespace Conversion { return coercedResult.value.asString; case "malformed": // this will turn malformed utf-8 into replacement characters (U+FFFD) (WARNING) - return Buffer.from(coercedResult.value.asHex, 'hex').toString(); + // note we need to cut off the 0x prefix + return Buffer.from(coercedResult.value.asHex.slice(2), 'hex').toString(); } } //fixed and ufixed are skipped for now diff --git a/packages/truffle-codec-utils/src/types/inspect.ts b/packages/truffle-codec-utils/src/types/inspect.ts index ef4ebdedf42..eda02ebe549 100644 --- a/packages/truffle-codec-utils/src/types/inspect.ts +++ b/packages/truffle-codec-utils/src/types/inspect.ts @@ -55,7 +55,8 @@ export class ResultInspector { return util.inspect(coercedResult.value.asString, options); case "malformed": //note: this will turn malformed utf-8 into replacement characters (U+FFFD) - return util.inspect(Buffer.from(coercedResult.value.asHex, 'hex').toString()); + //note we need to cut off the 0x prefix + return util.inspect(Buffer.from(coercedResult.value.asHex.slice(2), 'hex').toString()); } } case "array": { From 63d732ffe53fa700e1df75f4037609831e105f0f Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Mon, 15 Jul 2019 20:03:26 -0400 Subject: [PATCH 74/89] Add missing return; add infinite loop comments --- packages/truffle-codec-utils/src/conversion.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/truffle-codec-utils/src/conversion.ts b/packages/truffle-codec-utils/src/conversion.ts index 458a4e852d1..64b619c26c5 100644 --- a/packages/truffle-codec-utils/src/conversion.ts +++ b/packages/truffle-codec-utils/src/conversion.ts @@ -194,18 +194,18 @@ export namespace Conversion { } } //fixed and ufixed are skipped for now - case "array": //WARNING: circular case not handled + case "array": //WARNING: circular case not handled; will loop infinitely return (result).value.map(nativize); case "mapping": return Object.assign({}, ...(result).value.map( ({key, value}) => ({[nativize(key).toString()]: nativize(value)}) )); - case "struct": //WARNING: circular case not handled + case "struct": //WARNING: circular case not handled; will loop infinitely return Object.assign({}, ...(result).value.map( ({name, value}) => ({[name]: nativize(value)}) )); case "magic": - Object.assign({}, ...Object.entries((result).value).map( + return Object.assign({}, ...Object.entries((result).value).map( ([key, value]) => ({[key]: nativize(value)}) )); case "enum": From a49a63aa3a53c375b7848dc529f43074df4dea11 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 17 Jul 2019 17:35:32 -0400 Subject: [PATCH 75/89] Make type hints and type IDs just strings --- packages/truffle-codec-utils/src/contexts.ts | 2 +- .../truffle-codec-utils/src/types/types.ts | 50 +++++++++++-------- packages/truffle-codec/lib/allocate/abi.ts | 4 +- .../truffle-codec/lib/allocate/storage.ts | 2 +- packages/truffle-codec/lib/decode/abi.ts | 2 +- packages/truffle-codec/lib/decode/memory.ts | 2 +- packages/truffle-codec/lib/decode/storage.ts | 2 +- packages/truffle-codec/lib/decode/value.ts | 11 +--- 8 files changed, 38 insertions(+), 37 deletions(-) diff --git a/packages/truffle-codec-utils/src/contexts.ts b/packages/truffle-codec-utils/src/contexts.ts index 101584a5b72..0c3c8a94029 100644 --- a/packages/truffle-codec-utils/src/contexts.ts +++ b/packages/truffle-codec-utils/src/contexts.ts @@ -196,7 +196,7 @@ export namespace Contexts { return { typeClass: "contract", kind: "native", - id: context.contractId, + id: context.contractId.toString(), typeName: context.contractName, contractKind: context.contractKind, payable: context.payable diff --git a/packages/truffle-codec-utils/src/types/types.ts b/packages/truffle-codec-utils/src/types/types.ts index 2e46781a7cc..08e7d6337cb 100644 --- a/packages/truffle-codec-utils/src/types/types.ts +++ b/packages/truffle-codec-utils/src/types/types.ts @@ -23,6 +23,8 @@ const debug = debugModule("codec-utils:types:types"); //NOTE: not all of these optional fields are actually implemented. Some are //just intended for the future. +//ALSO NOTE: IDs are strings even though they're currently numeric because +//that might change in the future. import BN from "bn.js"; import * as Ast from "../ast"; @@ -38,18 +40,18 @@ export namespace Types { export interface UintType { typeClass: "uint"; bits: number; - enumTypeNameHint?: string; - enumKindHint?: "local" | "global"; - enumDefiningContractHint?: string; + typeHint?: string; } export interface IntType { typeClass: "int"; bits: number; + typeHint?: string; } export interface BoolType { typeClass: "bool"; + typeHint?: string; } export type BytesType = BytesTypeStatic | BytesTypeDynamic; @@ -58,35 +60,40 @@ export namespace Types { typeClass: "bytes"; kind: "static"; length: number; + typeHint?: string; } export interface BytesTypeDynamic { typeClass: "bytes"; kind: "dynamic"; location?: Ast.Location; + typeHint?: string; } export interface AddressType { typeClass: "address"; payable: boolean; - contractTypeNameHint?: string; + typeHint?: string; } export interface StringType { typeClass: "string"; location?: Ast.Location; + typeHint?: string; } export interface FixedType { typeClass: "fixed"; bits: number; places: number; + typeHint?: string; } export interface UfixedType { typeClass: "ufixed"; bits: number; places: number; + typeHint?: string; } export type ArrayType = ArrayTypeStatic | ArrayTypeDynamic; @@ -97,6 +104,7 @@ export namespace Types { baseType: Type; length: BN; location?: Ast.Location; + typeHint?: string; } export interface ArrayTypeDynamic { @@ -104,6 +112,7 @@ export namespace Types { kind: "dynamic"; baseType: Type; location?: Ast.Location; + typeHint?: string; } export type ElementaryType = UintType | IntType | BoolType | BytesType | FixedType @@ -144,6 +153,7 @@ export namespace Types { visibility: "external"; kind: "general"; //we do not presently support bound functions + typeHint?: string; } export type ContractDefinedType = StructTypeLocal | EnumTypeLocal; @@ -159,7 +169,7 @@ export namespace Types { export interface StructTypeLocal { typeClass: "struct"; kind: "local"; - id: number; + id: string; typeName: string; definingContractName: string; definingContract?: ContractTypeNative; @@ -170,7 +180,7 @@ export namespace Types { export interface StructTypeGlobal { typeClass: "struct"; kind: "global"; - id: number; + id: string; typeName: string; memberTypes?: NameTypePair[]; //these should be in order location?: Ast.Location; @@ -184,9 +194,7 @@ export namespace Types { export interface TupleType { typeClass: "tuple"; memberTypes: OptionallyNamedType[]; - structTypeNameHint?: string; - structKindHint?: "local" | "global"; - structDefiningContractHint?: string; + typeHint?: string; } export type EnumType = EnumTypeLocal | EnumTypeGlobal; @@ -194,7 +202,7 @@ export namespace Types { export interface EnumTypeLocal { typeClass: "enum"; kind: "local"; - id: number; + id: string; typeName: string; definingContractName: string; definingContract?: ContractTypeNative; @@ -204,7 +212,7 @@ export namespace Types { export interface EnumTypeGlobal { typeClass: "enum"; kind: "global"; - id: number; + id: string; typeName: string; options?: string[]; //these should be in order } @@ -214,7 +222,7 @@ export namespace Types { export interface ContractTypeNative { typeClass: "contract"; kind: "native"; - id: number; + id: string; typeName: string; contractKind?: Ast.ContractKind; payable?: boolean; //will be useful in the future @@ -245,7 +253,7 @@ export namespace Types { export type ReferenceType = ArrayType | MappingType | StructType | StringType | BytesTypeDynamic; export interface TypesById { - [id: number]: UserDefinedType; + [id: string]: UserDefinedType; }; //NOTE: the following function will *not* work for arbitrary nodes! It will, @@ -431,7 +439,7 @@ export namespace Types { break; //to satisfy typescript } case "struct": { - let id = DefinitionUtils.typeId(definition); + let id = DefinitionUtils.typeId(definition).toString(); let qualifiedName = definition.typeName ? definition.typeName.name : definition.name; @@ -456,7 +464,7 @@ export namespace Types { }; } case "enum": { - let id = DefinitionUtils.typeId(definition); + let id = DefinitionUtils.typeId(definition).toString(); let qualifiedName = definition.typeName ? definition.typeName.name : definition.name; @@ -470,7 +478,7 @@ export namespace Types { }; } case "contract": { - let id = DefinitionUtils.typeId(definition); + let id = DefinitionUtils.typeId(definition).toString(); let typeName = definition.typeName ? definition.typeName.name : definition.name; @@ -499,7 +507,7 @@ export namespace Types { export function definitionToStoredType(definition: Ast.AstDefinition, compiler: CompilerVersion, referenceDeclarations?: Ast.AstReferences): UserDefinedType { switch(definition.nodeType) { case "StructDefinition": { - let id = definition.id; + let id = definition.id.toString(); let [definingContractName, typeName] = definition.canonicalName.split("."); let memberTypes: {name: string, type: Type}[] = definition.members.map( member => ({name: member.name, type: definitionToType(member, compiler, null)}) @@ -509,7 +517,7 @@ export namespace Types { let contractDefinition = Object.values(referenceDeclarations).find( node => node.nodeType === "ContractDefinition" && node.nodes.some( - (subNode: Ast.AstDefinition) => subNode.id === id + (subNode: Ast.AstDefinition) => subNode.id.toString() === id ) ); definingContract = definitionToStoredType(contractDefinition, compiler); //can skip reference declarations @@ -525,7 +533,7 @@ export namespace Types { }; } case "EnumDefinition": { - let id = definition.id; + let id = definition.id.toString(); let [definingContractName, typeName] = definition.canonicalName.split("."); let options = definition.members.map(member => member.name); let definingContract; @@ -533,7 +541,7 @@ export namespace Types { let contractDefinition = Object.values(referenceDeclarations).find( node => node.nodeType === "ContractDefinition" && node.nodes.some( - (subNode: Ast.AstDefinition) => subNode.id === id + (subNode: Ast.AstDefinition) => subNode.id.toString() === id ) ); definingContract = definitionToStoredType(contractDefinition, compiler); //can skip reference declarations @@ -549,7 +557,7 @@ export namespace Types { }; } case "ContractDefinition": { - let id = definition.id; + let id = definition.id.toString(); let typeName = definition.name; let contractKind = definition.contractKind; let payable = DefinitionUtils.isContractPayable(definition); diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index 21d65d7dde2..2ccd242f471 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -210,7 +210,7 @@ export function abiSizeForType(dataType: CodecUtils.Types.Type, allocations?: Al return length * baseSize; } case "struct": - const allocation = allocations[dataType.id]; + const allocation = allocations[parseInt(dataType.id)]; if(!allocation) { throw new CodecUtils.Errors.DecodingError( { @@ -235,7 +235,7 @@ export function isTypeDynamic(dataType: CodecUtils.Types.Type, allocations?: All case "array": return dataType.kind === "dynamic" || (dataType.length.gtn(0) && isTypeDynamic(dataType.baseType, allocations)); case "struct": - const allocation = allocations[dataType.id]; + const allocation = allocations[parseInt(dataType.id)]; if(!allocation) { throw new CodecUtils.Errors.DecodingError( { diff --git a/packages/truffle-codec/lib/allocate/storage.ts b/packages/truffle-codec/lib/allocate/storage.ts index e9868a5e162..b81a913faf4 100644 --- a/packages/truffle-codec/lib/allocate/storage.ts +++ b/packages/truffle-codec/lib/allocate/storage.ts @@ -417,7 +417,7 @@ export function storageSizeForType(dataType: CodecUtils.Types.Type, userDefinedT } } case "struct": - let allocation = allocations[dataType.id]; + let allocation = allocations[parseInt(dataType.id)]; if(!allocation) { throw new CodecUtils.Errors.DecodingError( { diff --git a/packages/truffle-codec/lib/decode/abi.ts b/packages/truffle-codec/lib/decode/abi.ts index 95c99f7b1b3..b30bbe98a07 100644 --- a/packages/truffle-codec/lib/decode/abi.ts +++ b/packages/truffle-codec/lib/decode/abi.ts @@ -314,7 +314,7 @@ function* decodeAbiStructByPosition(dataType: Types.StructType, location: AbiLoc : location; const typeId = dataType.id; - const structAllocation = allocations[typeId]; + const structAllocation = allocations[parseInt(typeId)]; if(!structAllocation) { let error = { kind: "UserDefinedTypeNotFoundError" as "UserDefinedTypeNotFoundError", diff --git a/packages/truffle-codec/lib/decode/memory.ts b/packages/truffle-codec/lib/decode/memory.ts index 55cacd6d883..30c3c330e02 100644 --- a/packages/truffle-codec/lib/decode/memory.ts +++ b/packages/truffle-codec/lib/decode/memory.ts @@ -120,7 +120,7 @@ export function* decodeMemoryReferenceByAddress(dataType: Types.ReferenceType, p const { allocations: { memory: allocations }, userDefinedTypes } = info; const typeId = dataType.id; - const structAllocation = allocations[typeId]; + const structAllocation = allocations[parseInt(typeId)]; if(!structAllocation) { return { type: dataType, diff --git a/packages/truffle-codec/lib/decode/storage.ts b/packages/truffle-codec/lib/decode/storage.ts index 138e1f12ba4..8309c7a614f 100644 --- a/packages/truffle-codec/lib/decode/storage.ts +++ b/packages/truffle-codec/lib/decode/storage.ts @@ -245,7 +245,7 @@ export function* decodeStorageReference(dataType: Types.ReferenceType, pointer: case "struct": { const typeId = dataType.id; - const structAllocation = allocations[typeId]; + const structAllocation = allocations[parseInt(typeId)]; if(!structAllocation) { return { type: dataType, diff --git a/packages/truffle-codec/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts index 2eff80e6077..6ef87a5fce2 100644 --- a/packages/truffle-codec/lib/decode/value.ts +++ b/packages/truffle-codec/lib/decode/value.ts @@ -454,14 +454,7 @@ export function* decodeExternalFunction(addressBytes: Uint8Array, selectorBytes: export function decodeInternalFunction(dataType: Types.FunctionInternalType, deployedPcBytes: Uint8Array, constructorPcBytes: Uint8Array, info: EvmInfo): Values.FunctionInternalResult { let deployedPc: number = CodecUtils.Conversion.toBN(deployedPcBytes).toNumber(); let constructorPc: number = CodecUtils.Conversion.toBN(constructorPcBytes).toNumber(); - let context: Types.ContractType = { - typeClass: "contract", - kind: "native", - id: info.currentContext.contractId, - typeName: info.currentContext.contractName, - contractKind: info.currentContext.contractKind, - payable: info.currentContext.payable - }; + let context: Types.ContractType = CodecUtils.Contexts.contextToType(info.currentContext); //before anything else: do we even have an internal functions table? //if not, we'll just return the info we have without really attemting to decode if(!info.internalFunctionsTable) { @@ -550,7 +543,7 @@ export function decodeInternalFunction(dataType: Types.FunctionInternalType, dep let definedIn: Types.ContractType = { typeClass: "contract", kind: "native", - id: functionEntry.contractId, + id: functionEntry.contractId.toString(), typeName: functionEntry.contractName, contractKind: functionEntry.contractKind, payable: functionEntry.contractPayable From d246e1d8db7efdc23bb475b52c81000a43493552 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 17 Jul 2019 18:26:44 -0400 Subject: [PATCH 76/89] Fix error in previous commit (sorry!) --- packages/truffle-codec/lib/decode/value.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/truffle-codec/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts index 6ef87a5fce2..5405bc5eee6 100644 --- a/packages/truffle-codec/lib/decode/value.ts +++ b/packages/truffle-codec/lib/decode/value.ts @@ -430,7 +430,7 @@ export function* decodeExternalFunction(addressBytes: Uint8Array, selectorBytes: } let contractId = ( contract.class).id; //sorry! will be fixed soon! let context = Object.values(info.contexts).find( - context => context.contractId === contractId + context => context.contractId.toString() === contractId //similarly! I hope! ); let abiEntry = context.abi !== undefined ? context.abi[selector] From 0afcbdfb1b3b4df2dbe5f9d322ecfe54e2e75370 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 18 Jul 2019 19:50:16 -0400 Subject: [PATCH 77/89] Factor out wire decoder from contract decoder --- packages/truffle-decoder/lib/contract.ts | 284 ++++------------------- packages/truffle-decoder/lib/index.ts | 17 +- packages/truffle-decoder/lib/wire.ts | 54 +++-- 3 files changed, 86 insertions(+), 269 deletions(-) diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index dd7832c60aa..68645731f54 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -8,6 +8,7 @@ import Web3 from "web3"; import { ContractObject } from "truffle-contract-schema/spec"; import BN from "bn.js"; import { Definition as DefinitionUtils, AbiUtils, EVM, AstDefinition, AstReferences } from "truffle-codec-utils"; +import TruffleWireDecoder from "./wire"; import { BlockType, Transaction } from "web3/eth/types"; import { Log } from "web3/types"; import { Provider } from "web3/providers"; @@ -23,17 +24,15 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { private contractNetwork: string; private contractAddress: string; private contractCode: string; - private relevantContracts: ContractObject[]; - - private contracts: DecoderTypes.ContractMapping = {}; - private contractNodes: AstReferences = {}; - private contexts: CodecUtils.Contexts.DecoderContexts = {}; - private contextsById: CodecUtils.Contexts.DecoderContextsById = {}; //deployed contexts only private context: CodecUtils.Contexts.DecoderContext; private constructorContext: CodecUtils.Contexts.DecoderContext; private contextHash: string; private constructorContextHash: string; + private contexts: CodecUtils.Contexts.DecoderContexts = {}; + private contextsById: CodecUtils.Contexts.DecoderContextsById = {}; //deployed contexts only + private constructorContextsById: CodecUtils.Contexts.DecoderContextsById = {}; + private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; private allocations: Codec.AllocationInfo; @@ -43,156 +42,69 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { private mappingKeys: Codec.Slot[] = []; private storageCache: DecoderTypes.StorageCache = {}; - private codeCache: DecoderTypes.CodeCache = {}; - constructor(contract: ContractObject, relevantContracts: ContractObject[], provider: Provider, address: string) { - super(); + private wireDecoder: TruffleWireDecoder; - this.web3 = new Web3(provider); + constructor(contract: ContractObject, wireDecoder: TruffleWireDecoder, address?: string) { + super(); this.contract = contract; - this.relevantContracts = relevantContracts; - if(address !== undefined) { this.contractAddress = address; } + this.wireDecoder = wireDecoder; + this.web3 = wireDecoder.getWeb3(); + this.contractNode = Utils.getContractNode(this.contract); if(this.contractNode === undefined) { throw new DecoderTypes.ContractBeingDecodedHasNoNodeError(); } - this.contracts[this.contractNode.id] = this.contract; - this.contractNodes[this.contractNode.id] = this.contractNode; - if(this.contract.deployedBytecode) { //just to be safe - const context = Utils.makeContext(this.contract, this.contractNode); - debug("adding context: %O", context); + ({ byHash: this.contexts, byId: this.contextsById, constructorsById: this.constructorContextsById } = this.wireDecoder.getContexts()); + + if(this.contract.deployedBytecode && this.contract.deployedBytecode !== "0x") { const hash = CodecUtils.Conversion.toHexString( CodecUtils.EVM.keccak256({type: "string", - value: context.binary + value: this.contract.deployedBytecode }) ); - debug("with hash: %s", hash); - this.context = context; this.contextHash = hash; - this.contexts[hash] = context; + this.context = this.contexts[hash]; } - if(this.contract.bytecode) { //now the constructor version - const constructorContext = Utils.makeContext(this.contract, this.contractNode, true); - debug("adding context: %O", constructorContext); + if(this.contract.bytecode && this.contract.bytecode !== "0x") { //now the constructor version const hash = CodecUtils.Conversion.toHexString( CodecUtils.EVM.keccak256({type: "string", - value: constructorContext.binary + value: this.contract.bytecode }) ); - debug("with hash: %s", hash); - this.constructorContext = constructorContext; this.constructorContextHash = hash; - this.contexts[hash] = constructorContext; - } - - for(let relevantContract of this.relevantContracts) { - let node: AstDefinition = Utils.getContractNode(relevantContract); - if(node !== undefined) { - this.contracts[node.id] = relevantContract; - this.contractNodes[node.id] = node; - if(relevantContract.deployedBytecode) { - const context = Utils.makeContext(relevantContract, node); - debug("adding context: %O", context); - const hash = CodecUtils.Conversion.toHexString( - CodecUtils.EVM.keccak256({type: "string", - value: context.binary - }) - ); - this.contexts[hash] = context; - } - } - } - - debug("contexts: %o", this.contexts); - this.contexts = CodecUtils.Contexts.normalizeContexts(this.contexts); - this.context = this.contexts[this.contextHash]; - this.constructorContext = this.contexts[this.constructorContextHash]; - this.contextsById = Object.assign({}, ...Object.values(this.contexts).filter( - ({isConstructor}) => !isConstructor - ).map(context => - ({[context.contractId]: context}) - )); - } - - public async init(): Promise { - this.contractNetwork = (await this.web3.eth.net.getId()).toString(); - if(this.contractAddress === undefined) { - this.contractAddress = this.contract.networks[this.contractNetwork].address; + this.constructorContext = this.contexts[hash]; } - debug("init called"); - [this.referenceDeclarations, this.userDefinedTypes] = this.getUserDefinedTypes(); - - let libraryAllocationInfo: Codec.ContractAllocationInfo[] = - Object.entries(this.contracts).filter( - ([id, _]) => this.contractNodes[parseInt(id)].contractKind === "library" - ). - map( - ([id, { abi }]) => ({ - abi: AbiUtils.schemaAbiToAbi(abi), - id: parseInt(id) - }) - ); + this.referenceDeclarations = this.wireDecoder.getReferenceDeclarations(); + this.userDefinedTypes = this.wireDecoder.getUserDefinedTypes(); this.allocations = {}; + this.allocations.abi = this.wireDecoder.getAbiAllocations(); this.allocations.storage = Codec.getStorageAllocations( this.referenceDeclarations, {[this.contractNode.id]: this.contractNode} ); - this.allocations.abi = Codec.getAbiAllocations(this.referenceDeclarations); - this.allocations.calldata = Codec.getCalldataAllocations( - [{ - abi: AbiUtils.schemaAbiToAbi(this.contract.abi), - id: this.contractNode.id, - constructorContext: this.constructorContext - }], - this.referenceDeclarations, - this.allocations.abi - ); - this.allocations.event = Codec.getEventAllocations( - [ - { - abi: AbiUtils.schemaAbiToAbi(this.contract.abi), - id: this.contractNode.id - }, - ...libraryAllocationInfo - ], - this.referenceDeclarations, - this.allocations.abi - ); debug("done with allocation"); this.stateVariableReferences = this.allocations.storage[this.contractNode.id].members; debug("stateVariableReferences %O", this.stateVariableReferences); - - this.contractCode = await this.web3.eth.getCode(this.contractAddress); } - private getUserDefinedTypes(): [AstReferences, Types.TypesById] { - let references: AstReferences = {}; - let types: Types.TypesById = {}; - for(const id in this.contracts) { - const compiler = this.contracts[id].compiler; - //first, add the contract itself - const contractNode = this.contractNodes[id]; - references[id] = contractNode; - types[id] = Types.definitionToStoredType(contractNode, compiler); - //now, add its struct and enum definitions - for(const node of contractNode.nodes) { - if(node.nodeType === "StructDefinition" || node.nodeType === "EnumDefinition") { - references[node.id] = node; - //HACK even though we don't have all the references, we only need one: - //the reference to the contract itself, which we just added, so we're good - types[node.id] = Types.definitionToStoredType(node, compiler, references); - } - } + public async init(): Promise { + this.contractNetwork = (await this.web3.eth.net.getId()).toString(); + if(this.contractAddress === undefined) { + this.contractAddress = this.contract.networks[this.contractNetwork].address; } - return [references, types]; + + this.contractCode = CodecUtils.Conversion.toHexString( + await this.getCode(this.contractAddress, await this.web3.eth.getBlockNumber()) + ); } private async decodeVariable(variable: Codec.StorageMemberAllocation, block: number): Promise { @@ -300,23 +212,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { } private async getCode(address: string, block: number): Promise { - //first, set up any preliminary layers as needed - if(this.codeCache[block] === undefined) { - this.codeCache[block] = {}; - } - //now, if we have it cached, just return it - if(this.codeCache[block][address] !== undefined) { - return this.codeCache[block][address]; - } - //otherwise, get it, cache it, and return it - let code = CodecUtils.Conversion.toBytes( - await this.web3.eth.getCode( - address, - block - ) - ); - this.codeCache[block][address] = code; - return code; + return await this.wireDecoder.getCode(address, block); } //EXAMPLE: to watch a.b.c[d][e], use watchMappingKey("a", "b", "c", d, e) @@ -362,123 +258,23 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { //all descendants, you'll need to alter watchMappingKey to use an if rather //than a while - //NOTE: will only work with transactions to-or-creating this address! public async decodeTransaction(transaction: Transaction): Promise { - if(transaction.to !== this.contractAddress) { - if(transaction.to !== null) { - throw new DecoderTypes.EventOrTransactionIsNotForThisContractError(transaction.to, this.contractAddress); - } - else { - //OK, it's not *to* this address, but maybe it *created* it? - const receipt = await this.web3.eth.getTransactionReceipt(transaction.hash); - if(receipt.contractAddress !== this.contractAddress) { - throw new DecoderTypes.EventOrTransactionIsNotForThisContractError(receipt.contractAddress, this.contractAddress); - } - } - } - const block = transaction.blockNumber; - const data = CodecUtils.Conversion.toBytes(transaction.input); - const info: Codec.EvmInfo = { - state: { - storage: {}, - calldata: data, - }, - userDefinedTypes: this.userDefinedTypes, - allocations: this.allocations, - contexts: this.contextsById, - currentContext: transaction.to === null ? this.constructorContext : this.context - }; - const decoder = Codec.decodeCalldata(info); - - let result = decoder.next(); - while(!result.done) { - let request = (result.value); - let response: Uint8Array; - //only code requests should occur here - if(Codec.isCodeRequest(request)) { - response = await this.getCode(request.address, block); - } - result = decoder.next(response); - } - //at this point, result.value holds the final value - const decoding = result.value; - - return { - ...transaction, - decoding - }; + return await this.wireDecoder.decodeTransaction(transaction); } - //NOTE: will only work with logs for this address! - //NOTE: options is mostly meant for internal use (when called from events()), - //but hey, you can pass it if you really want - public async decodeLog(log: Log, options: DecoderTypes.EventOptions = {}): Promise { - if(log.address !== this.contractAddress) { - throw new DecoderTypes.EventOrTransactionIsNotForThisContractError(log.address, this.contractAddress); - } - const block = log.blockNumber; - const data = CodecUtils.Conversion.toBytes(log.data); - const topics = log.topics.map(CodecUtils.Conversion.toBytes); - const info: Codec.EvmInfo = { - state: { - storage: {}, - eventdata: data, - eventtopics: topics - }, - userDefinedTypes: this.userDefinedTypes, - allocations: this.allocations, - contexts: this.contextsById - }; - const decoder = Codec.decodeEvent(info, log.address, options.name); - - let result = decoder.next(); - while(!result.done) { - let request = (result.value); - let response: Uint8Array; - //only code requests should occur here - if(Codec.isCodeRequest(request)) { - response = await this.getCode(request.address, block); - } - result = decoder.next(response); - } - //at this point, result.value holds the final value - const decodings = result.value; - - return { - ...log, - decodings - }; + public async decodeLog(log: Log): Promise { + return await this.wireDecoder.decodeLog(log); } - //NOTE: will only work with logs for this address! - //NOTE: options is mostly meant for internal use (when called from events()), - //but hey, you can pass it if you really want - public async decodeLogs(logs: Log[], options: DecoderTypes.EventOptions = {}): Promise { - return await Promise.all(logs.map(log => this.decodeLog(log, options))); + public async decodeLogs(logs: Log[]): Promise { + return await this.wireDecoder.decodeLogs(logs); } + //by default, will restrict to events from this address. + //but, you can explicitly pass another and it will work + //(or pass undefined to turn off address filtering) public async events(options: DecoderTypes.EventOptions = {}): Promise { - let { name, fromBlock, toBlock } = options; - //note: address option is ignored! - - const logs = await this.web3.eth.getPastLogs({ - address: this.contractAddress, - fromBlock, - toBlock, - }); - - let events = await this.decodeLogs(logs, options); - - //if a target name was specified, we'll restrict to events that decoded - //to something with that name. (note that only decodings with that name - //will have been returned from decodeLogs in the first place) - if(name !== null) { - events = events.filter( - event => event.decodings.length > 0 - ); - } - - return events; + return await this.wireDecoder.events({address: this.contractAddress, ...options}); } public onEvent(name: string, callback: Function): void { diff --git a/packages/truffle-decoder/lib/index.ts b/packages/truffle-decoder/lib/index.ts index cd7da6c4e81..31679c77277 100644 --- a/packages/truffle-decoder/lib/index.ts +++ b/packages/truffle-decoder/lib/index.ts @@ -7,13 +7,18 @@ import { Provider } from "web3/providers"; import { ContractObject } from "truffle-contract-schema/spec"; export async function forContract(contract: ContractObject, relevantContracts: ContractObject[], provider: Provider, address?: string): Promise { - let decoder = new TruffleContractDecoder(contract, relevantContracts, provider, address); - await decoder.init(); - return decoder; + let wireDecoder = new TruffleWireDecoder([contract, ...relevantContracts], provider); + let contractDecoder = new TruffleContractDecoder(contract, wireDecoder, address); + await contractDecoder.init(); + return contractDecoder; } export async function forProject(contracts: ContractObject[], provider: Provider): Promise { - let decoder = new TruffleWireDecoder(contracts, provider); - await decoder.init(); - return decoder; + return new TruffleWireDecoder(contracts, provider); +} + +export async function forContractWithDecoder(contract: ContractObject, decoder: TruffleWireDecoder, address?: string): Promise { + let contractDecoder = new TruffleContractDecoder(contract, decoder, address); + await contractDecoder.init(); + return contractDecoder; } diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index 2188d750ce5..e3a78d6a32e 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -2,7 +2,7 @@ import debugModule from "debug"; const debug = debugModule("decoder:wire"); import * as CodecUtils from "truffle-codec-utils"; -import { Types, Values } from "truffle-codec-utils"; +import { Types, Values, Contexts } from "truffle-codec-utils"; import AsyncEventEmitter from "async-eventemitter"; import Web3 from "web3"; import { ContractObject } from "truffle-contract-schema/spec"; @@ -22,9 +22,9 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { private contracts: DecoderTypes.ContractMapping = {}; private contractNodes: AstReferences = {}; - private contexts: CodecUtils.Contexts.DecoderContexts = {}; - private contextsById: CodecUtils.Contexts.DecoderContextsById = {}; //deployed contexts only - private constructorContextsById: CodecUtils.Contexts.DecoderContextsById = {}; + private contexts: Contexts.DecoderContexts = {}; + private contextsById: Contexts.DecoderContextsById = {}; //deployed contexts only + private constructorContextsById: Contexts.DecoderContextsById = {}; private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; @@ -42,7 +42,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { if(node !== undefined) { this.contracts[node.id] = contract; this.contractNodes[node.id] = node; - if(contract.deployedBytecode) { + if(contract.deployedBytecode && contract.deployedBytecode !== "0x") { const context = Utils.makeContext(contract, node); const hash = CodecUtils.Conversion.toHexString( CodecUtils.EVM.keccak256({type: "string", @@ -51,7 +51,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { ); this.contexts[hash] = context; } - if(contract.bytecode) { + if(contract.bytecode && contract.bytecode !== "0x") { const constructorContext = Utils.makeContext(contract, node, true); const hash = CodecUtils.Conversion.toHexString( CodecUtils.EVM.keccak256({type: "string", @@ -63,7 +63,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { } } - this.contexts = CodecUtils.Contexts.normalizeContexts(this.contexts); + this.contexts = Contexts.normalizeContexts(this.contexts); this.contextsById = Object.assign({}, ...Object.values(this.contexts).filter( ({isConstructor}) => !isConstructor ).map(context => @@ -74,13 +74,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { ).map(context => ({[context.contractId]: context}) )); - } - - public async init(): Promise { - //note: this doesn't need to be async, but is for consistency - - debug("init called"); - [this.referenceDeclarations, this.userDefinedTypes] = this.getUserDefinedTypes(); + ({definitions: this.referenceDeclarations, types: this.userDefinedTypes} = this.collectUserDefinedTypes()); let allocationInfo: Codec.ContractAllocationInfo[] = Object.entries(this.contracts).map( ([id, { abi }]) => ({ @@ -99,7 +93,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { debug("done with allocation"); } - private getUserDefinedTypes(): [AstReferences, Types.TypesById] { + private collectUserDefinedTypes(): {definitions: AstReferences, types: Types.TypesById} { let references: AstReferences = {}; let types: Types.TypesById = {}; for(const id in this.contracts) { @@ -118,10 +112,11 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { } } } - return [references, types]; + return {definitions: references, types}; } - private async getCode(address: string, block: number): Promise { + //for internal use + public async getCode(address: string, block: number): Promise { //first, set up any preliminary layers as needed if(this.codeCache[block] === undefined) { this.codeCache[block] = {}; @@ -256,7 +251,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { //and checks this against the known contexts to determine the contract type //however, if this fails and constructorBinary is passed in, it will then also //attempt to determine it from that - private async getContextByAddress(address: string, block: number, constructorBinary?: string): Promise { + private async getContextByAddress(address: string, block: number, constructorBinary?: string): Promise { let code: string; if(address !== null) { code = CodecUtils.Conversion.toHexString( @@ -267,6 +262,27 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { code = constructorBinary; } //otherwise... we have a problem - return CodecUtils.Contexts.findDecoderContext(this.contexts, code); + return Contexts.findDecoderContext(this.contexts, code); + } + + //the following functions are intended for internal use only + public getReferenceDeclarations(): AstReferences { + return this.referenceDeclarations; + } + + public getUserDefinedTypes(): Types.TypesById { + return this.userDefinedTypes; + } + + public getAbiAllocations(): Codec.AbiAllocations { + return this.allocations.abi; + } + + public getWeb3(): Web3 { + return this.web3; + } + + public getContexts(): {byHash: Contexts.DecoderContexts, byId: Contexts.DecoderContextsById, constructorsById: Contexts.DecoderContextsById } { + return {byHash: this.contexts, byId: this.contextsById, constructorsById: this.constructorContextsById}; } } From 9503dac7ae9923be9a89e5e00499bc03c0e3b12a Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 18 Jul 2019 19:56:39 -0400 Subject: [PATCH 78/89] Avoid including a duplicate of contract in contracts passed to wire decoder --- packages/truffle-decoder/lib/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/truffle-decoder/lib/index.ts b/packages/truffle-decoder/lib/index.ts index 31679c77277..765b6d99df1 100644 --- a/packages/truffle-decoder/lib/index.ts +++ b/packages/truffle-decoder/lib/index.ts @@ -7,7 +7,10 @@ import { Provider } from "web3/providers"; import { ContractObject } from "truffle-contract-schema/spec"; export async function forContract(contract: ContractObject, relevantContracts: ContractObject[], provider: Provider, address?: string): Promise { - let wireDecoder = new TruffleWireDecoder([contract, ...relevantContracts], provider); + let contracts = relevantContracts.includes(contract) + ? relevantContracts + : [contract, ...relevantContracts]; + let wireDecoder = new TruffleWireDecoder(contracts, provider); let contractDecoder = new TruffleContractDecoder(contract, wireDecoder, address); await contractDecoder.init(); return contractDecoder; From f3d74b534319660101231d79bb32c72338f26d37 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Fri, 19 Jul 2019 00:12:30 -0400 Subject: [PATCH 79/89] Add extra context so self-pointers can be recognized even w/o bytecode --- packages/truffle-codec-utils/src/contexts.ts | 2 +- packages/truffle-decoder/lib/contract.ts | 44 +++++++++++++++----- packages/truffle-decoder/lib/wire.ts | 35 +++++++++------- 3 files changed, 54 insertions(+), 27 deletions(-) diff --git a/packages/truffle-codec-utils/src/contexts.ts b/packages/truffle-codec-utils/src/contexts.ts index 0c3c8a94029..b82974a3897 100644 --- a/packages/truffle-codec-utils/src/contexts.ts +++ b/packages/truffle-codec-utils/src/contexts.ts @@ -12,7 +12,7 @@ import { CompilerVersion } from "./compiler"; export namespace Contexts { - export type Contexts = DecoderContexts | DebuggerContexts; + export type Contexts = DecoderContexts | DebuggerContexts | DecoderContextsById; export type Context = DecoderContext | DebuggerContext; diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 68645731f54..3f039d9a6f0 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -2,7 +2,7 @@ import debugModule from "debug"; const debug = debugModule("decoder:contract"); import * as CodecUtils from "truffle-codec-utils"; -import { Types, Values, wrapElementaryViaDefinition } from "truffle-codec-utils"; +import { Types, Values, wrapElementaryViaDefinition, Contexts } from "truffle-codec-utils"; import AsyncEventEmitter from "async-eventemitter"; import Web3 from "web3"; import { ContractObject } from "truffle-contract-schema/spec"; @@ -24,14 +24,15 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { private contractNetwork: string; private contractAddress: string; private contractCode: string; - private context: CodecUtils.Contexts.DecoderContext; - private constructorContext: CodecUtils.Contexts.DecoderContext; + private context: Contexts.DecoderContext; + private constructorContext: Contexts.DecoderContext; private contextHash: string; private constructorContextHash: string; - private contexts: CodecUtils.Contexts.DecoderContexts = {}; - private contextsById: CodecUtils.Contexts.DecoderContextsById = {}; //deployed contexts only - private constructorContextsById: CodecUtils.Contexts.DecoderContextsById = {}; + private contexts: Contexts.DecoderContexts = {}; + private contextsById: Contexts.DecoderContextsById = {}; //deployed contexts only + private constructorContextsById: Contexts.DecoderContextsById = {}; + private additionalContexts: Contexts.DecoderContextsById = {}; //for passing to wire decoder when contract has no deployedBytecode private referenceDeclarations: AstReferences; private userDefinedTypes: Types.TypesById; @@ -105,6 +106,29 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { this.contractCode = CodecUtils.Conversion.toHexString( await this.getCode(this.contractAddress, await this.web3.eth.getBlockNumber()) ); + + if(!this.contract.deployedBytecode || this.contract.deployedBytecode === "0x") { + //if this contract does *not* have the deployedBytecode field, then the decoder core + //has no way of knowing that contracts or function pointers with its address + //are of its class; this is an especial problem for function pointers, as it + //won't be able to determine what the selector points to. + //so, to get around this, we make an "additional context" for the contract, + //based on its *actual* deployed bytecode as pulled from the blockchain. + //This way the decoder core can recognize the address as the class, without us having + //to make serious modifications to contract decoding. And while sure this requires + //a little more work, I mean, it's all cached, so, no big deal. + let extraContext = Utils.makeContext(this.contract, this.contractNode); + //now override the binary + extraContext.binary = this.contractCode; + this.additionalContexts = {[extraContext.contractId]: extraContext}; + //the following line only has any effect if we're dealing with a library, + //since the code we pulled from the blockchain obviously does not have unresolved link references! + //(it's not strictly necessary even then, but, hey, why not?) + this.additionalContexts = Contexts.normalizeContexts(this.additionalContexts); + //again, since the code did not have unresolved link references, it is safe to just + //mash these together like I'm about to + this.contextsById = {...this.contextsById, ...this.additionalContexts}; + } } private async decodeVariable(variable: Codec.StorageMemberAllocation, block: number): Promise { @@ -259,22 +283,22 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { //than a while public async decodeTransaction(transaction: Transaction): Promise { - return await this.wireDecoder.decodeTransaction(transaction); + return await this.wireDecoder.decodeTransaction(transaction, this.additionalContexts); } public async decodeLog(log: Log): Promise { - return await this.wireDecoder.decodeLog(log); + return await this.wireDecoder.decodeLog(log, {}, this.additionalContexts); } public async decodeLogs(logs: Log[]): Promise { - return await this.wireDecoder.decodeLogs(logs); + return await this.wireDecoder.decodeLogs(logs, {}, this.additionalContexts); } //by default, will restrict to events from this address. //but, you can explicitly pass another and it will work //(or pass undefined to turn off address filtering) public async events(options: DecoderTypes.EventOptions = {}): Promise { - return await this.wireDecoder.events({address: this.contractAddress, ...options}); + return await this.wireDecoder.events({address: this.contractAddress, ...options}, this.additionalContexts); } public onEvent(name: string, callback: Function): void { diff --git a/packages/truffle-decoder/lib/wire.ts b/packages/truffle-decoder/lib/wire.ts index e3a78d6a32e..8c357d21cff 100644 --- a/packages/truffle-decoder/lib/wire.ts +++ b/packages/truffle-decoder/lib/wire.ts @@ -136,10 +136,11 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { return code; } - public async decodeTransaction(transaction: Transaction): Promise { + //NOTE: additionalContexts parameter is for internal use only. + public async decodeTransaction(transaction: Transaction, additionalContexts: Contexts.DecoderContextsById = {}): Promise { debug("transaction: %O", transaction); const block = transaction.blockNumber; - const context = await this.getContextByAddress(transaction.to, block, transaction.input); + const context = await this.getContextByAddress(transaction.to, block, transaction.input, additionalContexts); const data = CodecUtils.Conversion.toBytes(transaction.input); const info: Codec.EvmInfo = { @@ -149,7 +150,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }, userDefinedTypes: this.userDefinedTypes, allocations: this.allocations, - contexts: this.contextsById, + contexts: {...this.contextsById, ...additionalContexts}, currentContext: context }; const decoder = Codec.decodeCalldata(info); @@ -173,9 +174,9 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - //NOTE: options is mostly meant for internal use (when called from events()), - //but hey, you can pass it if you really want - public async decodeLog(log: Log, options: DecoderTypes.EventOptions = {}): Promise { + //NOTE: options is meant for internal use; do not rely on it + //NOTE: additionalContexts parameter is for internal use only. + public async decodeLog(log: Log, options: DecoderTypes.EventOptions = {}, additionalContexts: Contexts.DecoderContextsById = {}): Promise { const block = log.blockNumber; const data = CodecUtils.Conversion.toBytes(log.data); const topics = log.topics.map(CodecUtils.Conversion.toBytes); @@ -187,7 +188,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }, userDefinedTypes: this.userDefinedTypes, allocations: this.allocations, - contexts: this.contextsById + contexts: {...this.contextsById, ...additionalContexts} }; const decoder = Codec.decodeEvent(info, log.address, options.name); @@ -210,13 +211,14 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { }; } - //NOTE: options is mostly meant for internal use (when called from events()), - //but hey, you can pass it if you really want - public async decodeLogs(logs: Log[], options: DecoderTypes.EventOptions = {}): Promise { - return await Promise.all(logs.map(log => this.decodeLog(log, options))); + //NOTE: options is meant for internal use; do not rely on it + //NOTE: additionalContexts parameter is for internal use only. + public async decodeLogs(logs: Log[], options: DecoderTypes.EventOptions = {}, additionalContexts: Contexts.DecoderContextsById = {}): Promise { + return await Promise.all(logs.map(log => this.decodeLog(log, options, additionalContexts))); } - public async events(options: DecoderTypes.EventOptions = {}): Promise { + //NOTE: additionalContexts parameter is for internal use only. + public async events(options: DecoderTypes.EventOptions = {}, additionalContexts: Contexts.DecoderContextsById = {}): Promise { let { address, name, fromBlock, toBlock } = options; const logs = await this.web3.eth.getPastLogs({ @@ -225,7 +227,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { toBlock, }); - let events = await this.decodeLogs(logs, options); + let events = await this.decodeLogs(logs, options, additionalContexts); debug("events: %o", events); //if a target name was specified, we'll restrict to events that decoded @@ -251,7 +253,7 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { //and checks this against the known contexts to determine the contract type //however, if this fails and constructorBinary is passed in, it will then also //attempt to determine it from that - private async getContextByAddress(address: string, block: number, constructorBinary?: string): Promise { + private async getContextByAddress(address: string, block: number, constructorBinary?: string, additionalContexts: Contexts.DecoderContextsById = {}): Promise { let code: string; if(address !== null) { code = CodecUtils.Conversion.toHexString( @@ -261,8 +263,9 @@ export default class TruffleWireDecoder extends AsyncEventEmitter { else if(constructorBinary) { code = constructorBinary; } - //otherwise... we have a problem - return Contexts.findDecoderContext(this.contexts, code); + //if neither of these hold... we have a problem + let contexts = {...this.contexts, ...additionalContexts}; + return Contexts.findDecoderContext(contexts, code); } //the following functions are intended for internal use only From 4c7b96bdf83405046e52872c5fe3bf90cd3b4c2d Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 24 Jul 2019 23:18:01 -0400 Subject: [PATCH 80/89] Rename balance, nonce to balanceAsBN, nonceAsBN --- packages/truffle-decoder/lib/contract.ts | 4 ++-- packages/truffle-decoder/lib/types.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/truffle-decoder/lib/contract.ts b/packages/truffle-decoder/lib/contract.ts index 3f039d9a6f0..dbbf11e1b4d 100644 --- a/packages/truffle-decoder/lib/contract.ts +++ b/packages/truffle-decoder/lib/contract.ts @@ -171,8 +171,8 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { let result: DecoderTypes.ContractState = { name: this.contract.contractName, code: this.contractCode, - balance: new BN(await this.web3.eth.getBalance(this.contractAddress, blockNumber)), - nonce: new BN(await this.web3.eth.getTransactionCount(this.contractAddress, blockNumber)), + balanceAsBN: new BN(await this.web3.eth.getBalance(this.contractAddress, blockNumber)), + nonceAsBN: new BN(await this.web3.eth.getTransactionCount(this.contractAddress, blockNumber)), variables: {} }; diff --git a/packages/truffle-decoder/lib/types.ts b/packages/truffle-decoder/lib/types.ts index 0fc02cf4714..dee66b816f2 100644 --- a/packages/truffle-decoder/lib/types.ts +++ b/packages/truffle-decoder/lib/types.ts @@ -7,8 +7,8 @@ import { Log } from "web3/types"; export interface ContractState { name: string; - balance: BN; - nonce: BN; + balanceAsBN: BN; + nonceAsBN: BN; code: string; variables: { [name: string]: Values.Result From 784cd7879e7bff377127d152c9e5c25facd913e1 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 24 Jul 2019 23:25:24 -0400 Subject: [PATCH 81/89] Get rid of separate padding errors for bool, enum --- .../truffle-codec-utils/src/types/errors.ts | 17 ++-------- .../truffle-codec-utils/src/types/inspect.ts | 6 +--- packages/truffle-codec/lib/decode/value.ts | 31 +------------------ 3 files changed, 5 insertions(+), 49 deletions(-) diff --git a/packages/truffle-codec-utils/src/types/errors.ts b/packages/truffle-codec-utils/src/types/errors.ts index a7d5f74e0e8..edfd909d290 100644 --- a/packages/truffle-codec-utils/src/types/errors.ts +++ b/packages/truffle-codec-utils/src/types/errors.ts @@ -90,15 +90,10 @@ export namespace Errors { error: GenericError | BoolError; } - export type BoolError = BoolPaddingError | BoolOutOfRangeError; - - export interface BoolPaddingError { - raw: string; //should be hex string - kind: "BoolPaddingError"; - } + export type BoolError = BoolOutOfRangeError; export interface BoolOutOfRangeError { - rawAsNumber: number; + rawAsBN: BN; kind: "BoolOutOfRangeError"; } @@ -232,13 +227,7 @@ export namespace Errors { error: GenericError | EnumError; } - export type EnumError = EnumPaddingError | EnumOutOfRangeError | EnumNotFoundDecodingError; - - export interface EnumPaddingError { - kind: "EnumPaddingError"; - type: Types.EnumType; - raw: string; //should be hex string - } + export type EnumError = EnumOutOfRangeError | EnumNotFoundDecodingError; export interface EnumOutOfRangeError { kind: "EnumOutOfRangeError"; diff --git a/packages/truffle-codec-utils/src/types/inspect.ts b/packages/truffle-codec-utils/src/types/inspect.ts index eda02ebe549..3442c4d30a0 100644 --- a/packages/truffle-codec-utils/src/types/inspect.ts +++ b/packages/truffle-codec-utils/src/types/inspect.ts @@ -165,18 +165,14 @@ export class ResultInspector { return `Uint has extra leading bytes (padding error) (raw value ${errorResult.error.raw})`; case "IntPaddingError": return `Int out of range (padding error) (numeric value ${errorResult.error.raw})`; - case "BoolPaddingError": - return `Bool has extra leading bytes (padding error) (raw value ${errorResult.error.raw})`; case "BoolOutOfRangeError": - return `Invalid boolean (numeric value ${errorResult.error.rawAsNumber})`; + return `Invalid boolean (numeric value ${errorResult.error.rawAsBN.toString()})`; case "BytesPaddingError": return `Bytestring has extra trailing bytes (padding error) (raw value ${errorResult.error.raw})`; case "AddressPaddingError": return `Address has extra leading bytes (padding error) (raw value ${errorResult.error.raw})`; case "FixedPointNotYetSupportedError": return `Fixed-point decoding not yet supported (raw value: ${errorResult.error.raw})`; - case "EnumPaddingError": - return `${enumTypeName(errorResult.error.type)} has extra leading bytes (padding error) (raw value ${errorResult.error.raw})`; case "EnumOutOfRangeError": return `Invalid ${enumTypeName(errorResult.error.type)} (numeric value ${errorResult.error.rawAsBN.toString()})`; case "EnumNotFoundDecodingError": diff --git a/packages/truffle-codec/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts index 5405bc5eee6..9adec1e9791 100644 --- a/packages/truffle-codec/lib/decode/value.ts +++ b/packages/truffle-codec/lib/decode/value.ts @@ -39,20 +39,6 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, switch(dataType.typeClass) { case "bool": { - if(!checkPaddingLeft(bytes, 1)) { - let error = { - kind: "BoolPaddingError" as "BoolPaddingError", - raw: CodecUtils.Conversion.toHexString(bytes) - }; - if(strict) { - throw new StopDecodingError(error); - } - return { - type: dataType, - kind: "error", - error - }; - } const numeric = CodecUtils.Conversion.toBN(bytes); if(numeric.eqn(0)) { return { @@ -71,7 +57,7 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, else { let error = { kind: "BoolOutOfRangeError" as "BoolOutOfRangeError", - rawAsNumber: numeric.toNumber() //cannot fail, it's only 1 byte + rawAsBN: numeric }; if(strict) { throw new StopDecodingError(error); @@ -299,21 +285,6 @@ export default function* decodeValue(dataType: Types.Type, pointer: DataPointer, } const numOptions = fullType.options.length; const numBytes = Math.ceil(Math.log2(numOptions) / 8); - if(!checkPaddingLeft(bytes, numBytes)) { - let error = { - kind: "EnumPaddingError" as "EnumPaddingError", - type: fullType, - raw: CodecUtils.Conversion.toHexString(bytes) - }; - if(strict) { - throw new StopDecodingError(error); - } - return { - type: fullType, - kind: "error", - error - }; - } if(numeric.ltn(numOptions)) { const name = fullType.options[numeric.toNumber()]; return { From 63ada1286783d70dcb0cda47cdeb90057dbefc4a Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 25 Jul 2019 00:41:52 -0400 Subject: [PATCH 82/89] Replace name in decodings with abi --- packages/truffle-codec-utils/src/abi.ts | 32 ++++++++-- packages/truffle-codec/lib/allocate/abi.ts | 3 + .../truffle-codec/lib/interface/decoding.ts | 10 ++-- packages/truffle-codec/lib/interface/index.ts | 2 +- .../truffle-codec/lib/types/allocation.ts | 2 + .../lib/types/{wire.ts => decoding.ts} | 8 ++- .../truffle-decoder/test/test/wire-test.js | 59 ++++++++++++------- 7 files changed, 82 insertions(+), 34 deletions(-) rename packages/truffle-codec/lib/types/{wire.ts => decoding.ts} (83%) diff --git a/packages/truffle-codec-utils/src/abi.ts b/packages/truffle-codec-utils/src/abi.ts index 57165cb2ca8..b074c51b46e 100644 --- a/packages/truffle-codec-utils/src/abi.ts +++ b/packages/truffle-codec-utils/src/abi.ts @@ -59,6 +59,19 @@ export namespace AbiUtils { [selector: string]: FunctionAbiEntry } + export const DEFAULT_CONSTRUCTOR_ABI: ConstructorAbiEntry = { + type: "constructor", + inputs: [], + stateMutability: "nonpayable", + payable: false + }; + + export const DEFAULT_FALLBACK_ABI: FallbackAbiEntry = { + type: "fallback", + stateMutability: "nonpayable", + payable: false + }; + export function schemaAbiToAbi(abiLoose: SchemaAbi): Abi { return abiLoose.map( entry => entry.type @@ -86,10 +99,21 @@ export namespace AbiUtils { if(abi === undefined) { return undefined; } - return abi.some( - (abiEntry: AbiEntry) => - abiEntry.type === "fallback" && abiMutability(abiEntry) === "payable" - ); + return abiMutability(getFallbackEntry(abi)) === "payable"; + } + + //gets the fallback entry; if there isn't one, returns a default one + export function getFallbackEntry(abi: Abi): FallbackAbiEntry { + //no idea why TS's type inference is failing on this one... + return abi.find(abiEntry => abiEntry.type === "fallback") || DEFAULT_FALLBACK_ABI; + } + + export function fallbackAbiForPayability(payable: boolean): FallbackAbiEntry { + return { + type: "fallback", + stateMutability: payable ? "payable" : "nonpayable", + payable + }; } //shim for old abi versions diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index 2ccd242f471..f53a58b9e9e 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -339,6 +339,7 @@ function allocateCalldata( } return { definition: abiAllocation.definition, + abi: abiEntry, offset, arguments: argumentsAllocation }; @@ -419,6 +420,7 @@ function allocateEvent( //...and return return { definition: abiAllocation.definition, + abi: abiEntry, contractId, arguments: argumentsAllocation }; @@ -466,6 +468,7 @@ function defaultConstructorAllocation(constructorContext: CodecUtils.Contexts.De let offset = (rawLength - 2)/2; //number of bytes in 0x-prefixed bytestring return { offset, + abi: AbiUtils.DEFAULT_CONSTRUCTOR_ABI, arguments: [] as Allocations.CalldataArgumentAllocation[] }; } diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 30d80c21dcd..90bb54acdf8 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -7,7 +7,7 @@ import * as Pointer from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { DecoderRequest, GeneratorJunk } from "../types/request"; import { CalldataAllocation, EventAllocation, EventArgumentAllocation } from "../types/allocation"; -import { CalldataDecoding, LogDecoding, AbiArgument } from "../types/wire"; +import { CalldataDecoding, LogDecoding, AbiArgument } from "../types/decoding"; import { encodeTupleAbi } from "../encode/abi"; import read from "../read"; import decode from "../decode"; @@ -56,6 +56,7 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator { ); assert.strictEqual(emitStuffDecoding.kind, "function"); - assert.strictEqual(emitStuffDecoding.name, "emitStuff"); + assert.strictEqual(emitStuffDecoding.abi.name, "emitStuff"); assert.strictEqual(emitStuffDecoding.class.typeName, "WireTest"); assert.lengthOf(emitStuffDecoding.arguments, 3); assert.strictEqual(emitStuffDecoding.arguments[0].name, "p"); @@ -132,7 +132,7 @@ contract("WireTest", _accounts => { ); assert.strictEqual(moreStuffDecoding.kind, "function"); - assert.strictEqual(moreStuffDecoding.name, "moreStuff"); + assert.strictEqual(moreStuffDecoding.abi.name, "moreStuff"); assert.strictEqual(moreStuffDecoding.class.typeName, "WireTest"); assert.lengthOf(moreStuffDecoding.arguments, 2); assert.strictEqual(moreStuffDecoding.arguments[0].name, "notThis"); @@ -147,7 +147,7 @@ contract("WireTest", _accounts => { ); assert.strictEqual(inheritedDecoding.kind, "function"); - assert.strictEqual(inheritedDecoding.name, "inherited"); + assert.strictEqual(inheritedDecoding.abi.name, "inherited"); assert.strictEqual(inheritedDecoding.class.typeName, "WireTest"); //NOT WireTestParent assert.lengthOf(inheritedDecoding.arguments, 1); assert.isUndefined(inheritedDecoding.arguments[0].name); @@ -164,7 +164,7 @@ contract("WireTest", _accounts => { assert.isEmpty(defaultConstructorDecoding.arguments); assert.strictEqual(getterDecoding1.kind, "function"); - assert.strictEqual(getterDecoding1.name, "deepStruct"); + assert.strictEqual(getterDecoding1.abi.name, "deepStruct"); assert.strictEqual(getterDecoding1.class.typeName, "WireTest"); assert.lengthOf(getterDecoding1.arguments, 2); assert.isUndefined(getterDecoding1.arguments[0].name); @@ -179,7 +179,7 @@ contract("WireTest", _accounts => { ); assert.strictEqual(getterDecoding2.kind, "function"); - assert.strictEqual(getterDecoding2.name, "deepString"); + assert.strictEqual(getterDecoding2.abi.name, "deepString"); assert.strictEqual(getterDecoding2.class.typeName, "WireTest"); assert.lengthOf(getterDecoding2.arguments, 2); assert.isUndefined(getterDecoding2.arguments[0].name); @@ -274,7 +274,7 @@ contract("WireTest", _accounts => { assert.strictEqual(constructorEventDecoding.kind, "event"); assert.strictEqual(constructorEventDecoding.class.typeName, "WireTest"); - assert.strictEqual(constructorEventDecoding.name, "ConstructorEvent"); + assert.strictEqual(constructorEventDecoding.abi.name, "ConstructorEvent"); assert.lengthOf(constructorEventDecoding.arguments, 3); assert.strictEqual(constructorEventDecoding.arguments[0].name, "bit"); assert.strictEqual( @@ -293,7 +293,7 @@ contract("WireTest", _accounts => { ); assert.strictEqual(emitStuffEventDecoding.kind, "event"); - assert.strictEqual(emitStuffEventDecoding.name, "EmitStuff"); + assert.strictEqual(emitStuffEventDecoding.abi.name, "EmitStuff"); assert.strictEqual(emitStuffEventDecoding.class.typeName, "WireTest"); assert.lengthOf(emitStuffEventDecoding.arguments, 3); assert.isUndefined(emitStuffEventDecoding.arguments[0].name); @@ -313,7 +313,7 @@ contract("WireTest", _accounts => { ); assert.strictEqual(moreStuffEventDecoding.kind, "event"); - assert.strictEqual(moreStuffEventDecoding.name, "MoreStuff"); + assert.strictEqual(moreStuffEventDecoding.abi.name, "MoreStuff"); assert.strictEqual(moreStuffEventDecoding.class.typeName, "WireTest"); assert.lengthOf(moreStuffEventDecoding.arguments, 2); assert.isUndefined(moreStuffEventDecoding.arguments[0].name); @@ -328,12 +328,12 @@ contract("WireTest", _accounts => { ); assert.strictEqual(inheritedEventDecoding.kind, "event"); - assert.strictEqual(inheritedEventDecoding.name, "Done"); + assert.strictEqual(inheritedEventDecoding.abi.name, "Done"); assert.strictEqual(inheritedEventDecoding.class.typeName, "WireTest"); //NOT WireTestParent assert.isEmpty(inheritedEventDecoding.arguments); assert.strictEqual(indexTestEventDecoding.kind, "event"); - assert.strictEqual(indexTestEventDecoding.name, "HasIndices"); + assert.strictEqual(indexTestEventDecoding.abi.name, "HasIndices"); assert.strictEqual(indexTestEventDecoding.class.typeName, "WireTest"); assert.lengthOf(indexTestEventDecoding.arguments, 5); assert.isUndefined(indexTestEventDecoding.arguments[0].name); @@ -362,7 +362,7 @@ contract("WireTest", _accounts => { ); assert.strictEqual(libraryTestEventDecoding.kind, "event"); - assert.strictEqual(libraryTestEventDecoding.name, "LibraryEvent"); + assert.strictEqual(libraryTestEventDecoding.abi.name, "LibraryEvent"); assert.strictEqual( libraryTestEventDecoding.class.typeName, "WireTestLibrary" @@ -375,7 +375,7 @@ contract("WireTest", _accounts => { ); assert.strictEqual(dangerEventDecoding.kind, "event"); - assert.strictEqual(dangerEventDecoding.name, "Danger"); + assert.strictEqual(dangerEventDecoding.abi.name, "Danger"); assert.lengthOf(dangerEventDecoding.arguments, 1); assert.isUndefined(dangerEventDecoding.arguments[0].name); assert.strictEqual( @@ -422,7 +422,10 @@ contract("WireTest", _accounts => { ({ decodings }) => decodings[0] ); assert.strictEqual(ambiguityTestContractDecoding.kind, "event"); - assert.strictEqual(ambiguityTestContractDecoding.name, "AmbiguousEvent"); + assert.strictEqual( + ambiguityTestContractDecoding.abi.name, + "AmbiguousEvent" + ); assert.strictEqual( ambiguityTestContractDecoding.class.typeName, "WireTest" @@ -439,7 +442,7 @@ contract("WireTest", _accounts => { ); assert.strictEqual(ambiguityTestLibraryDecoding.kind, "event"); - assert.strictEqual(ambiguityTestLibraryDecoding.name, "AmbiguousEvent"); + assert.strictEqual(ambiguityTestLibraryDecoding.abi.name, "AmbiguousEvent"); assert.strictEqual( ambiguityTestLibraryDecoding.class.typeName, "WireTestLibrary" @@ -455,7 +458,7 @@ contract("WireTest", _accounts => { for (let decoding of unambiguousDecodings) { assert.strictEqual(decoding.kind, "event"); - assert.strictEqual(decoding.name, "AmbiguousEvent"); + assert.strictEqual(decoding.abi.name, "AmbiguousEvent"); } assert.strictEqual(unambiguousDecodings[0].class.typeName, "WireTest"); @@ -529,7 +532,10 @@ contract("WireTest", _accounts => { assert.lengthOf(anonymousTestEvents[0].decodings, 1); assert.strictEqual(anonymousTestEvents[0].decodings[0].kind, "anonymous"); - assert.strictEqual(anonymousTestEvents[0].decodings[0].name, "AnonUints"); + assert.strictEqual( + anonymousTestEvents[0].decodings[0].abi.name, + "AnonUints" + ); assert.strictEqual( anonymousTestEvents[0].decodings[0].class.typeName, "WireTest" @@ -544,7 +550,10 @@ contract("WireTest", _accounts => { assert.lengthOf(anonymousTestEvents[1].decodings, 2); assert.strictEqual(anonymousTestEvents[1].decodings[0].kind, "anonymous"); - assert.strictEqual(anonymousTestEvents[1].decodings[0].name, "AnonUints"); + assert.strictEqual( + anonymousTestEvents[1].decodings[0].abi.name, + "AnonUints" + ); assert.strictEqual( anonymousTestEvents[1].decodings[0].class.typeName, "WireTest" @@ -557,7 +566,10 @@ contract("WireTest", _accounts => { [1, 2, 3, 4] ); assert.strictEqual(anonymousTestEvents[1].decodings[1].kind, "anonymous"); - assert.strictEqual(anonymousTestEvents[1].decodings[1].name, "AnonUint8s"); + assert.strictEqual( + anonymousTestEvents[1].decodings[1].abi.name, + "AnonUint8s" + ); assert.strictEqual( anonymousTestEvents[1].decodings[1].class.typeName, "WireTestLibrary" @@ -572,7 +584,7 @@ contract("WireTest", _accounts => { assert.lengthOf(anonymousTestEvents[2].decodings, 2); assert.strictEqual(anonymousTestEvents[2].decodings[0].kind, "event"); - assert.strictEqual(anonymousTestEvents[2].decodings[0].name, "NonAnon"); + assert.strictEqual(anonymousTestEvents[2].decodings[0].abi.name, "NonAnon"); assert.strictEqual( anonymousTestEvents[2].decodings[0].class.typeName, "WireTest" @@ -586,7 +598,10 @@ contract("WireTest", _accounts => { ); let selector = anonymousTestEvents[2].decodings[0].selector; assert.strictEqual(anonymousTestEvents[2].decodings[1].kind, "anonymous"); - assert.strictEqual(anonymousTestEvents[2].decodings[1].name, "AnonUints"); + assert.strictEqual( + anonymousTestEvents[2].decodings[1].abi.name, + "AnonUints" + ); assert.strictEqual( anonymousTestEvents[2].decodings[1].class.typeName, "WireTest" @@ -607,7 +622,7 @@ contract("WireTest", _accounts => { assert.lengthOf(anonymousTestEvents[3].decodings, 1); assert.strictEqual(anonymousTestEvents[3].decodings[0].kind, "anonymous"); assert.strictEqual( - anonymousTestEvents[3].decodings[0].name, + anonymousTestEvents[3].decodings[0].abi.name, "ObviouslyAnon" ); assert.strictEqual( @@ -628,7 +643,7 @@ contract("WireTest", _accounts => { assert.lengthOf(specifiedNameEvent.decodings, 1); let specifiedNameDecoding = specifiedNameEvent.decodings[0]; assert.strictEqual(specifiedNameDecoding.kind, "anonymous"); - assert.strictEqual(specifiedNameDecoding.name, "AnonUint8s"); + assert.strictEqual(specifiedNameDecoding.abi.name, "AnonUint8s"); assert.strictEqual(specifiedNameDecoding.class.typeName, "WireTestLibrary"); assert.lengthOf(specifiedNameDecoding.arguments, 4); assert.deepEqual( From 781b86ba2f37244dd5eb3e033a13062bcb47fe93 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 25 Jul 2019 16:37:38 -0400 Subject: [PATCH 83/89] Fix out of date package name! --- packages/truffle-core/lib/debug/printer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/truffle-core/lib/debug/printer.js b/packages/truffle-core/lib/debug/printer.js index 45da9a97ad4..6f63ac3fd4e 100644 --- a/packages/truffle-core/lib/debug/printer.js +++ b/packages/truffle-core/lib/debug/printer.js @@ -5,7 +5,7 @@ const path = require("path"); const safeEval = require("safe-eval"); const DebugUtils = require("truffle-debug-utils"); -const DecodeUtils = require("truffle-decode-utils"); +const CodecUtils = require("truffle-codec-utils"); const selectors = require("truffle-debugger").selectors; const { session, solidity, trace, controller } = selectors; @@ -255,7 +255,7 @@ class DebugPrinter { //if we're not in the single-variable case, we'll need to do some //things to Javascriptify our variables so that the JS syntax for //using them is closer to the Solidity syntax - variables = DecodeUtils.Conversion.nativizeVariables(variables); + variables = CodecUtils.Conversion.nativizeVariables(variables); let context = Object.assign( { $: this.select }, From bb6438f8de6942ad4cddd6ccc1e40c843711201f Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 25 Jul 2019 19:43:28 -0400 Subject: [PATCH 84/89] Fix error with watch expression display --- packages/truffle-core/lib/debug/printer.js | 4 ++-- packages/truffle-debug-utils/index.js | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/truffle-core/lib/debug/printer.js b/packages/truffle-core/lib/debug/printer.js index 6f63ac3fd4e..8611a234f03 100644 --- a/packages/truffle-core/lib/debug/printer.js +++ b/packages/truffle-core/lib/debug/printer.js @@ -296,7 +296,7 @@ class DebugPrinter { try { let result = safeEval(expr, context); result = DebugUtils.cleanConstructors(result); //HACK - const formatted = DebugUtils.formatValue(result, indent); + const formatted = DebugUtils.formatValue(result, indent, true); this.config.logger.log(formatted); this.config.logger.log(); } catch (e) { @@ -316,7 +316,7 @@ class DebugPrinter { if (!suppress) { this.config.logger.log(e); } else { - this.config.logger.log(DebugUtils.formatValue(undefined)); + this.config.logger.log(DebugUtils.formatValue(undefined, indent, true)); } } } diff --git a/packages/truffle-debug-utils/index.js b/packages/truffle-debug-utils/index.js index 546b2fc2e43..798129b3882 100644 --- a/packages/truffle-debug-utils/index.js +++ b/packages/truffle-debug-utils/index.js @@ -317,16 +317,20 @@ var DebugUtils = { return formatted.join(OS.EOL); }, - formatValue: function(value, indent = 0) { + formatValue: function(value, indent = 0, nativized = false) { + let inspectOptions = { + colors: true, + depth: null, + maxArrayLength: null, + breakLength: 30 + }; + let valueToInspect = nativized + ? value + : new CodecUtils.ResultInspector(value); return util - .inspect(new CodecUtils.ResultInspector(value), { - colors: true, - depth: null, - maxArrayLength: null, - breakLength: 30 - }) + .inspect(valueToInspect, inspectOptions) .split(/\r?\n/g) - .map(function(line, i) { + .map((line, i) => { // don't indent first line const padding = i > 0 ? Array(indent).join(" ") : ""; return padding + line; From 9b78765da17df67f793ec4555a6c3814d210ffd2 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 30 Jul 2019 16:44:46 -0400 Subject: [PATCH 85/89] Remove no-longer-used lodash.isequal --- packages/truffle-decoder/package.json | 3 +-- yarn.lock | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/truffle-decoder/package.json b/packages/truffle-decoder/package.json index aa00fa34d20..a299bc607e0 100644 --- a/packages/truffle-decoder/package.json +++ b/packages/truffle-decoder/package.json @@ -31,11 +31,10 @@ "async-eventemitter": "^0.2.4", "bn.js": "^4.11.8", "debug": "^4.1.1", - "lodash.isequal": "^4.5.0", "json-schema-to-typescript": "^6.1.3", - "truffle-contract-schema": "^3.0.9", "truffle-codec": "^3.0.3", "truffle-codec-utils": "^1.0.14", + "truffle-contract-schema": "^3.0.9", "web3": "^1.2.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 921f5137aba..5a79d93ec97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8996,11 +8996,6 @@ lodash.isboolean@^3.0.3: resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= -lodash.isequal@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= - lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" From 2751d6d44b912dd6fd3745f6858245395c65c1f4 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Wed, 31 Jul 2019 02:04:26 -0400 Subject: [PATCH 86/89] Clean out-of-range bools used as mapping keys --- .../truffle-codec-utils/src/conversion.ts | 26 +++++++++++++++++++ .../truffle-debugger/lib/data/sagas/index.js | 7 +++++ 2 files changed, 33 insertions(+) diff --git a/packages/truffle-codec-utils/src/conversion.ts b/packages/truffle-codec-utils/src/conversion.ts index 64b619c26c5..f7bca755132 100644 --- a/packages/truffle-codec-utils/src/conversion.ts +++ b/packages/truffle-codec-utils/src/conversion.ts @@ -4,6 +4,7 @@ const debug = debugModule("codec-utils:conversion"); import BN from "bn.js"; import Web3 from "web3"; import { Constants } from "./constants"; +import { Types } from "./types/types"; import { Values } from "./types/values"; import { enumFullName } from "./types/inspect"; @@ -165,6 +166,31 @@ export namespace Conversion { )); } + //converts out of range booleans to true; something of a HACK + //NOTE: does NOT do this recursively inside structs, arrays, etc! + //I mean, those aren't elementary and therefore aren't in the domain + //anyway, but still + export function cleanBool(result: Values.ElementaryResult): Values.ElementaryResult { + switch(result.kind) { + case "value": + return result; + case "error": + switch(result.error.kind) { + case "BoolOutOfRangeError": + //return true + return { + type: result.type, + kind: "value", + value: { + asBool: true + } + }; + default: + return result; + } + } + } + //HACK! Avoid using! Only use this if: //1. you absolutely have to, or //2. it's just testing, not real code diff --git a/packages/truffle-debugger/lib/data/sagas/index.js b/packages/truffle-debugger/lib/data/sagas/index.js index 29ff02a0c0a..cc01e2f0ed3 100644 --- a/packages/truffle-debugger/lib/data/sagas/index.js +++ b/packages/truffle-debugger/lib/data/sagas/index.js @@ -502,6 +502,13 @@ function* variablesAndMappingsSaga() { } function* decodeMappingKeySaga(indexDefinition, keyDefinition) { + //something of a HACK -- cleans any out-of-range booleans + //resulting from the main mapping key decoding loop + let indexValue = yield* decodeMappingKeyCore(indexDefinition, keyDefinition); + return indexValue ? CodecUtils.Conversion.cleanBool(indexValue) : indexValue; +} + +function* decodeMappingKeyCore(indexDefinition, keyDefinition) { let scopes = yield select(data.views.scopes.inlined); let currentAssignments = yield select(data.proc.assignments); let currentDepth = yield select(data.current.functionDepth); From 3fb9352dbe892a9dcf835a3cb2c7302983514d7d Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 15 Aug 2019 22:21:07 -0400 Subject: [PATCH 87/89] Address PR comments --- packages/truffle-codec-utils/src/abi.ts | 4 +- .../truffle-codec-utils/src/definition.ts | 255 +----------------- .../truffle-codec-utils/src/definition2abi.ts | 251 +++++++++++++++++ packages/truffle-codec-utils/src/index.ts | 1 + .../truffle-codec-utils/src/types/inspect.ts | 2 +- .../truffle-codec-utils/src/types/types.ts | 12 +- packages/truffle-codec/lib/allocate/abi.ts | 2 +- packages/truffle-codec/lib/decode/abi.ts | 3 +- packages/truffle-codec/lib/decode/event.ts | 3 +- packages/truffle-codec/lib/decode/index.ts | 3 +- packages/truffle-codec/lib/decode/value.ts | 3 +- packages/truffle-codec/lib/types/evm.ts | 7 - packages/truffle-codec/lib/types/options.ts | 6 + 13 files changed, 284 insertions(+), 268 deletions(-) create mode 100644 packages/truffle-codec-utils/src/definition2abi.ts create mode 100644 packages/truffle-codec/lib/types/options.ts diff --git a/packages/truffle-codec-utils/src/abi.ts b/packages/truffle-codec-utils/src/abi.ts index b074c51b46e..9ce7589a8c5 100644 --- a/packages/truffle-codec-utils/src/abi.ts +++ b/packages/truffle-codec-utils/src/abi.ts @@ -4,7 +4,7 @@ const debug = debugModule("codec-utils:abi"); import { Abi as SchemaAbi } from "truffle-contract-schema/spec"; import { EVM as EVMUtils } from "./evm"; import { AstDefinition, AstReferences, Mutability } from "./ast"; -import { Definition as DefinitionUtils } from "./definition"; +import { definitionToAbi } from "./definition2abi"; import Web3 from "web3"; //NOTE: SchemaAbi is kind of loose and a pain to use. @@ -188,7 +188,7 @@ export namespace AbiUtils { } export function definitionMatchesAbi(abiEntry: AbiEntry, definition: AstDefinition, referenceDeclarations: AstReferences): boolean { - return abisMatch(abiEntry, DefinitionUtils.definitionToAbi(definition, referenceDeclarations)); + return abisMatch(abiEntry, definitionToAbi(definition, referenceDeclarations)); } export function topicsCount(abiEntry: EventAbiEntry): number { diff --git a/packages/truffle-codec-utils/src/definition.ts b/packages/truffle-codec-utils/src/definition.ts index 440d9e3f1d6..dfdf50d6fea 100644 --- a/packages/truffle-codec-utils/src/definition.ts +++ b/packages/truffle-codec-utils/src/definition.ts @@ -2,11 +2,9 @@ import debugModule from "debug"; const debug = debugModule("codec-utils:definition"); import { EVM as EVMUtils } from "./evm"; -import { AstDefinition, AstReferences, Scopes, Visibility, Mutability, Location, ContractKind } from "./ast"; +import { AstDefinition, Scopes, Visibility, Mutability, Location, ContractKind } from "./ast"; import { Contexts } from "./contexts"; import { CompilerVersion } from "./compiler"; -import { AbiUtils } from "./abi"; -import { UnknownUserDefinedTypeError } from "./errors"; import BN from "bn.js"; import cloneDeep from "lodash.clonedeep"; import semver from "semver"; @@ -31,6 +29,11 @@ export namespace Definition { return typeIdentifier(definition).match(/t_([^$_0-9]+)/)[1]; } + /** + * similar to typeClass, but includes any numeric qualifiers + * e.g.: + * `t_uint256` becomes `uint256` + */ export function typeClassLongForm(definition: AstDefinition): string { return typeIdentifier(definition).match(/t_([^$_]+)/)[1]; } @@ -417,250 +420,4 @@ export namespace Definition { } } - //section: converting a definition to an ABI entry! - - //the main function. just does some dispatch. - //returns undefined on bad input - export function definitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.AbiEntry | undefined { - switch(node.nodeType) { - case "FunctionDefinition": - if(node.visibility === "public" || node.visibility === "external") { - return functionDefinitionToAbi(node, referenceDeclarations); - } - else { - return undefined; - } - case "EventDefinition": - return eventDefinitionToAbi(node, referenceDeclarations); - case "VariableDeclaration": - if(node.visibility === "public") { - return getterDefinitionToAbi(node, referenceDeclarations); - } - else { - return undefined; - } - default: - return undefined; - } - } - - //note: not for FunctionTypeNames or VariableDeclarations - function functionDefinitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.FunctionAbiEntry | AbiUtils.ConstructorAbiEntry | AbiUtils.FallbackAbiEntry { - let kind = functionKind(node); - let stateMutability = mutability(node); - let payable = stateMutability === "payable"; - let constant = stateMutability === "view" || stateMutability == "pure"; - let inputs; - switch(kind) { - case "function": - let name = node.name; - let outputs = parametersToAbi(node.returnParameters.parameters, referenceDeclarations); - inputs = parametersToAbi(node.parameters.parameters, referenceDeclarations); - return { - type: "function", - name, - inputs, - outputs, - stateMutability, - constant, - payable - }; - case "constructor": - inputs = parametersToAbi(node.parameters.parameters, referenceDeclarations); - //note: need to coerce because of mutability restructions - return { - type: "constructor", - inputs, - stateMutability, - payable - }; - case "fallback": - //note: need to coerce because of mutability restructions - return { - type: "fallback", - stateMutability, - payable - }; - } - } - - function eventDefinitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.EventAbiEntry { - let inputs = parametersToAbi(node.parameters.parameters, referenceDeclarations, true); - let name = node.name; - let anonymous = node.anonymous; - return { - type: "event", - inputs, - name, - anonymous - }; - } - - function parametersToAbi(nodes: AstDefinition[], referenceDeclarations: AstReferences, checkIndexed: boolean = false): AbiUtils.AbiParameter[] { - return nodes.map(node => parameterToAbi(node, referenceDeclarations, checkIndexed)); - } - - function parameterToAbi(node: AstDefinition, referenceDeclarations: AstReferences, checkIndexed: boolean = false): AbiUtils.AbiParameter { - let name = node.name; //may be the empty string... or even undefined for a base type - let components: AbiUtils.AbiParameter[]; - let indexed: boolean; - if(checkIndexed) { - indexed = node.indexed; //note: may be undefined for a base type - } - //is this an array? if so use separate logic - if(typeClass(node) === "array") { - let baseType = node.typeName ? node.typeName.baseType : node.baseType; - let baseAbi = parameterToAbi(baseType, referenceDeclarations, checkIndexed); - let arraySuffix = isDynamicArray(node) - ? `[]` - : `[${staticLength(node)}]`; - return { - name, - type: baseAbi.type + arraySuffix, - indexed, - components: baseAbi.components - }; - } - let abiTypeString = toAbiType(node, referenceDeclarations); - //otherwise... is it a struct? if so we need to populate components - if(typeClass(node) === "struct") { - let id = typeId(node); - let referenceDeclaration = referenceDeclarations[id]; - if(referenceDeclaration === undefined) { - let typeToDisplay = typeString(node); - throw new UnknownUserDefinedTypeError(id, typeToDisplay); - } - components = parametersToAbi(referenceDeclaration.members, referenceDeclarations, checkIndexed); - } - return { - name, //may be empty string but should only be undefined in recursive calls - type: abiTypeString, - indexed, //undefined if !checkedIndex - components //undefined if not a struct or (multidim) array of structs - }; - } - - //note: this is only meant for non-array types that can go in the ABI - //it returns how that type is notated in the ABI -- just the string, - //to be clear, not components of tuples - //again, NOT FOR ARRAYS - function toAbiType(node: AstDefinition, referenceDeclarations: AstReferences): string { - let basicType = typeClassLongForm(node); //get that whole first segment! - switch(basicType) { - case "contract": - return "address"; - case "struct": - return "tuple"; //the more detailed checking will be handled elsewhere - case "enum": - let referenceId = typeId(node); - let referenceDeclaration = referenceDeclarations[referenceId]; - if(referenceDeclaration === undefined) { - let typeToDisplay = typeString(node); - throw new UnknownUserDefinedTypeError(referenceId, typeToDisplay); - } - let numOptions = referenceDeclaration.members.length; - let bits = 8 * Math.ceil(Math.log2(numOptions) / 8); - return `uint${bits}`; - default: - return basicType; - //note that: int/uint/fixed/ufixed/bytes will have their size and such left on; - //address will have "payable" left off; - //external functions will be reduced to "function" (and internal functions shouldn't - //be passed in!) - //(mappings shouldn't be passed in either obviously) - //(nor arrays :P ) - } - } - - function getterDefinitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.FunctionAbiEntry { - debug("getter node: %O", node); - let name = node.name; - let { inputs, outputs } = getterParameters(node, referenceDeclarations); - let inputsAbi = parametersToAbi(inputs, referenceDeclarations); - let outputsAbi = parametersToAbi(outputs, referenceDeclarations); - return { - type: "function", - name, - inputs: inputsAbi, - outputs: outputsAbi, - stateMutability: "view", - constant: true, - payable: false - }; - } - - //how getter parameters work: - //INPUT: - //types other than arrays and mappings take no input. - //array getters take uint256 input. mapping getters take input of their key type. - //if arrays, mappings, stacked, then takes multiple inputs, in order from outside - //to in. - //These parameters are unnamed. - //OUTPUT: - //if base type (beneath mappings & arrays) is not a struct, returns that. - //(This return parameter has no name -- it is *not* named for the variable!) - //if it is a struct, returns multiple outputs, one for each member of the struct, - //*except* arrays and mappings. (And they have names, the names of the members.) - //important note: inner structs within a struct are just returned, not - //partially destructured like the outermost struct! Yes, this is confusing. - - //here's a simplified function that just does the inputs. it's for use by the - //allocator. I'm keeping it separate because it doesn't require a - //referenceDeclarations argument. - export function getterInputs(node: AstDefinition): AstDefinition[] { - node = node.typeName || node; - let inputs: AstDefinition[] = []; - while(typeClass(node) === "array" || typeClass(node) === "mapping") { - let keyNode = keyDefinition(node); //note: if node is an array, this spoofs up a uint256 definition - inputs.push({...keyNode, name: ""}); //getter input params have no name - switch(typeClass(node)) { - case "array": - node = node.baseType; - break; - case "mapping": - node = node.valueType; - break; - } - } - return inputs; - } - - //again, despite the duplication, this function is kept separate from the - //more straightforward getterInputs function because, since it has to handle - //outputs too, it requires referenceDeclarations - function getterParameters(node: AstDefinition, referenceDeclarations: AstReferences): {inputs: AstDefinition[], outputs: AstDefinition[]} { - let baseNode: AstDefinition = node.typeName || node; - let inputs: AstDefinition[] = []; - while(typeClass(baseNode) === "array" || typeClass(baseNode) === "mapping") { - let keyNode = keyDefinition(baseNode); //note: if baseNode is an array, this spoofs up a uint256 definition - inputs.push({...keyNode, name: ""}); //again, getter input params have no name - switch(typeClass(baseNode)) { - case "array": - baseNode = baseNode.baseType; - break; - case "mapping": - baseNode = baseNode.valueType; - break; - } - } - //at this point, baseNode should hold the base type - //now we face the question: is it a struct? - if(typeClass(baseNode) === "struct") { - let id = typeId(baseNode); - let referenceDeclaration = referenceDeclarations[id]; - if(referenceDeclaration === undefined) { - let typeToDisplay = typeString(baseNode); - throw new UnknownUserDefinedTypeError(id, typeToDisplay); - } - let outputs = referenceDeclaration.members.filter( - member => typeClass(member) !== "array" && typeClass(member) !== "mapping" - ); - return { inputs, outputs }; //no need to wash name! - } - else { - //only one output; it's just the base node, with its name washed - return { inputs, outputs: [{...baseNode, name: ""}] }; - } - } - } diff --git a/packages/truffle-codec-utils/src/definition2abi.ts b/packages/truffle-codec-utils/src/definition2abi.ts new file mode 100644 index 00000000000..1e73c95ebd7 --- /dev/null +++ b/packages/truffle-codec-utils/src/definition2abi.ts @@ -0,0 +1,251 @@ +import debugModule from "debug"; +const debug = debugModule("codec-utils:definition2abi"); + +import { AstDefinition, AstReferences } from "./ast"; +import { AbiUtils } from "./abi"; +import { Definition } from "./definition"; +import { UnknownUserDefinedTypeError } from "./errors"; + +//the main function. just does some dispatch. +//returns undefined on bad input +export function definitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.AbiEntry | undefined { + switch(node.nodeType) { + case "FunctionDefinition": + if(node.visibility === "public" || node.visibility === "external") { + return functionDefinitionToAbi(node, referenceDeclarations); + } + else { + return undefined; + } + case "EventDefinition": + return eventDefinitionToAbi(node, referenceDeclarations); + case "VariableDeclaration": + if(node.visibility === "public") { + return getterDefinitionToAbi(node, referenceDeclarations); + } + else { + return undefined; + } + default: + return undefined; + } +} + +//note: not for FunctionTypeNames or VariableDeclarations +function functionDefinitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.FunctionAbiEntry | AbiUtils.ConstructorAbiEntry | AbiUtils.FallbackAbiEntry { + let kind = Definition.functionKind(node); + let stateMutability = Definition.mutability(node); + let payable = stateMutability === "payable"; + let constant = stateMutability === "view" || stateMutability == "pure"; + let inputs; + switch(kind) { + case "function": + let name = node.name; + let outputs = parametersToAbi(node.returnParameters.parameters, referenceDeclarations); + inputs = parametersToAbi(node.parameters.parameters, referenceDeclarations); + return { + type: "function", + name, + inputs, + outputs, + stateMutability, + constant, + payable + }; + case "constructor": + inputs = parametersToAbi(node.parameters.parameters, referenceDeclarations); + //note: need to coerce because of mutability restrictions + return { + type: "constructor", + inputs, + stateMutability, + payable + }; + case "fallback": + //note: need to coerce because of mutability restrictions + return { + type: "fallback", + stateMutability, + payable + }; + } +} + +function eventDefinitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.EventAbiEntry { + let inputs = parametersToAbi(node.parameters.parameters, referenceDeclarations, true); + let name = node.name; + let anonymous = node.anonymous; + return { + type: "event", + inputs, + name, + anonymous + }; +} + +function parametersToAbi(nodes: AstDefinition[], referenceDeclarations: AstReferences, checkIndexed: boolean = false): AbiUtils.AbiParameter[] { + return nodes.map(node => parameterToAbi(node, referenceDeclarations, checkIndexed)); +} + +function parameterToAbi(node: AstDefinition, referenceDeclarations: AstReferences, checkIndexed: boolean = false): AbiUtils.AbiParameter { + let name = node.name; //may be the empty string... or even undefined for a base type + let components: AbiUtils.AbiParameter[]; + let indexed: boolean; + if(checkIndexed) { + indexed = node.indexed; //note: may be undefined for a base type + } + //is this an array? if so use separate logic + if(Definition.typeClass(node) === "array") { + let baseType = node.typeName ? node.typeName.baseType : node.baseType; + let baseAbi = parameterToAbi(baseType, referenceDeclarations, checkIndexed); + let arraySuffix = Definition.isDynamicArray(node) + ? `[]` + : `[${Definition.staticLength(node)}]`; + return { + name, + type: baseAbi.type + arraySuffix, + indexed, + components: baseAbi.components + }; + } + let abiTypeString = toAbiType(node, referenceDeclarations); + //otherwise... is it a struct? if so we need to populate components + if(Definition.typeClass(node) === "struct") { + let id = Definition.typeId(node); + let referenceDeclaration = referenceDeclarations[id]; + if(referenceDeclaration === undefined) { + let typeToDisplay = Definition.typeString(node); + throw new UnknownUserDefinedTypeError(id, typeToDisplay); + } + components = parametersToAbi(referenceDeclaration.members, referenceDeclarations, checkIndexed); + } + return { + name, //may be empty string but should only be undefined in recursive calls + type: abiTypeString, + indexed, //undefined if !checkedIndex + components //undefined if not a struct or (multidim) array of structs + }; +} + +//note: this is only meant for non-array types that can go in the ABI +//it returns how that type is notated in the ABI -- just the string, +//to be clear, not components of tuples +//again, NOT FOR ARRAYS +function toAbiType(node: AstDefinition, referenceDeclarations: AstReferences): string { + let basicType = Definition.typeClassLongForm(node); //get that whole first segment! + switch(basicType) { + case "contract": + return "address"; + case "struct": + return "tuple"; //the more detailed checking will be handled elsewhere + case "enum": + let referenceId = Definition.typeId(node); + let referenceDeclaration = referenceDeclarations[referenceId]; + if(referenceDeclaration === undefined) { + let typeToDisplay = Definition.typeString(node); + throw new UnknownUserDefinedTypeError(referenceId, typeToDisplay); + } + let numOptions = referenceDeclaration.members.length; + let bits = 8 * Math.ceil(Math.log2(numOptions) / 8); + return `uint${bits}`; + default: + return basicType; + //note that: int/uint/fixed/ufixed/bytes will have their size and such left on; + //address will have "payable" left off; + //external functions will be reduced to "function" (and internal functions shouldn't + //be passed in!) + //(mappings shouldn't be passed in either obviously) + //(nor arrays :P ) + } +} + +function getterDefinitionToAbi(node: AstDefinition, referenceDeclarations: AstReferences): AbiUtils.FunctionAbiEntry { + debug("getter node: %O", node); + let name = node.name; + let { inputs, outputs } = getterParameters(node, referenceDeclarations); + let inputsAbi = parametersToAbi(inputs, referenceDeclarations); + let outputsAbi = parametersToAbi(outputs, referenceDeclarations); + return { + type: "function", + name, + inputs: inputsAbi, + outputs: outputsAbi, + stateMutability: "view", + constant: true, + payable: false + }; +} + +//how getter parameters work: +//INPUT: +//types other than arrays and mappings take no input. +//array getters take uint256 input. mapping getters take input of their key type. +//if arrays, mappings, stacked, then takes multiple inputs, in order from outside +//to in. +//These parameters are unnamed. +//OUTPUT: +//if base type (beneath mappings & arrays) is not a struct, returns that. +//(This return parameter has no name -- it is *not* named for the variable!) +//if it is a struct, returns multiple outputs, one for each member of the struct, +//*except* arrays and mappings. (And they have names, the names of the members.) +//important note: inner structs within a struct are just returned, not +//partially destructured like the outermost struct! Yes, this is confusing. + +//here's a simplified function that just does the inputs. it's for use by the +//allocator. I'm keeping it separate because it doesn't require a +//referenceDeclarations argument. +export function getterInputs(node: AstDefinition): AstDefinition[] { + node = node.typeName || node; + let inputs: AstDefinition[] = []; + while(Definition.typeClass(node) === "array" || Definition.typeClass(node) === "mapping") { + let keyNode = Definition.keyDefinition(node); //note: if node is an array, this spoofs up a uint256 definition + inputs.push({...keyNode, name: ""}); //getter input params have no name + switch(Definition.typeClass(node)) { + case "array": + node = node.baseType; + break; + case "mapping": + node = node.valueType; + break; + } + } + return inputs; +} + +//again, despite the duplication, this function is kept separate from the +//more straightforward getterInputs function because, since it has to handle +//outputs too, it requires referenceDeclarations +function getterParameters(node: AstDefinition, referenceDeclarations: AstReferences): {inputs: AstDefinition[], outputs: AstDefinition[]} { + let baseNode: AstDefinition = node.typeName || node; + let inputs: AstDefinition[] = []; + while(Definition.typeClass(baseNode) === "array" || Definition.typeClass(baseNode) === "mapping") { + let keyNode = Definition.keyDefinition(baseNode); //note: if baseNode is an array, this spoofs up a uint256 definition + inputs.push({...keyNode, name: ""}); //again, getter input params have no name + switch(Definition.typeClass(baseNode)) { + case "array": + baseNode = baseNode.baseType; + break; + case "mapping": + baseNode = baseNode.valueType; + break; + } + } + //at this point, baseNode should hold the base type + //now we face the question: is it a struct? + if(Definition.typeClass(baseNode) === "struct") { + let id = Definition.typeId(baseNode); + let referenceDeclaration = referenceDeclarations[id]; + if(referenceDeclaration === undefined) { + let typeToDisplay = Definition.typeString(baseNode); + throw new UnknownUserDefinedTypeError(id, typeToDisplay); + } + let outputs = referenceDeclaration.members.filter( + member => Definition.typeClass(member) !== "array" && Definition.typeClass(member) !== "mapping" + ); + return { inputs, outputs }; //no need to wash name! + } + else { + //only one output; it's just the base node, with its name washed + return { inputs, outputs: [{...baseNode, name: ""}] }; + } +} diff --git a/packages/truffle-codec-utils/src/index.ts b/packages/truffle-codec-utils/src/index.ts index d8be2a67432..217a399df6c 100644 --- a/packages/truffle-codec-utils/src/index.ts +++ b/packages/truffle-codec-utils/src/index.ts @@ -3,6 +3,7 @@ export * from "./evm"; export * from "./definition"; export * from "./ast"; export * from "./contexts"; +export * from "./definition2abi"; export * from "./abi"; export * from "./compiler"; export * from "./errors"; diff --git a/packages/truffle-codec-utils/src/types/inspect.ts b/packages/truffle-codec-utils/src/types/inspect.ts index 3442c4d30a0..86fff4e83ed 100644 --- a/packages/truffle-codec-utils/src/types/inspect.ts +++ b/packages/truffle-codec-utils/src/types/inspect.ts @@ -203,7 +203,7 @@ export class ResultInspector { } } -//these get there own class to deal with a minor complication +//these get their own class to deal with a minor complication class ContractInfoInspector { value: Values.ContractValueInfo; constructor(value: Values.ContractValueInfo) { diff --git a/packages/truffle-codec-utils/src/types/types.ts b/packages/truffle-codec-utils/src/types/types.ts index 08e7d6337cb..f2545433fd0 100644 --- a/packages/truffle-codec-utils/src/types/types.ts +++ b/packages/truffle-codec-utils/src/types/types.ts @@ -240,14 +240,18 @@ export namespace Types { //now } + export type MagicVariableName = "message" | "block" | "transaction"; + export interface MagicType { typeClass: "magic"; - variable: string; //not putting this in the type annotation for technical - //reasons, but this should be one of "message", "block", or "transaction"; - //we do *not* presently support abi or meta_type + variable: MagicVariableName; + //really, I could do this as a tagged union, but I don't see a reason to + //introduce such complexity here, especially as this type is basically just + //for the debugger memberTypes?: { [field: string]: Type }; + //may have more optional fields defined in the future } export type ReferenceType = ArrayType | MappingType | StructType | StringType | BytesTypeDynamic; @@ -493,7 +497,7 @@ export namespace Types { } case "magic": { let typeIdentifier = DefinitionUtils.typeIdentifier(definition); - let variable = typeIdentifier.match(/^t_magic_(.*)$/)[1]; + let variable = typeIdentifier.match(/^t_magic_(.*)$/)[1]; return { typeClass, variable diff --git a/packages/truffle-codec/lib/allocate/abi.ts b/packages/truffle-codec/lib/allocate/abi.ts index f53a58b9e9e..f4f4e808089 100644 --- a/packages/truffle-codec/lib/allocate/abi.ts +++ b/packages/truffle-codec/lib/allocate/abi.ts @@ -318,7 +318,7 @@ function allocateCalldata( break; case "VariableDeclaration": //getter case - parameters = CodecUtils.Definition.getterInputs(node); + parameters = CodecUtils.getterInputs(node); break; } const abiAllocation = allocateMembers(node, parameters, referenceDeclarations, abiAllocations, offset)[node.id]; diff --git a/packages/truffle-codec/lib/decode/abi.ts b/packages/truffle-codec/lib/decode/abi.ts index b30bbe98a07..afa9946182d 100644 --- a/packages/truffle-codec/lib/decode/abi.ts +++ b/packages/truffle-codec/lib/decode/abi.ts @@ -8,7 +8,8 @@ import decodeValue from "./value"; import { AbiDataPointer, DataPointer } from "../types/pointer"; import { AbiMemberAllocation } from "../types/allocation"; import { abiSizeForType, isTypeDynamic } from "../allocate/abi"; -import { EvmInfo, DecoderOptions } from "../types/evm"; +import { EvmInfo } from "../types/evm"; +import { DecoderOptions } from "../types/options"; import { DecoderRequest, GeneratorJunk } from "../types/request"; import { StopDecodingError } from "../types/errors"; diff --git a/packages/truffle-codec/lib/decode/event.ts b/packages/truffle-codec/lib/decode/event.ts index 24d1249dba9..35696246b77 100644 --- a/packages/truffle-codec/lib/decode/event.ts +++ b/packages/truffle-codec/lib/decode/event.ts @@ -5,7 +5,8 @@ import decodeValue from "./value"; import read from "../read"; import { Types, Values, Conversion as ConversionUtils } from "truffle-codec-utils"; import { EventTopicPointer } from "../types/pointer"; -import { EvmInfo, DecoderOptions } from "../types/evm"; +import { EvmInfo } from "../types/evm"; +import { DecoderOptions } from "../types/options"; import { DecoderRequest, GeneratorJunk } from "../types/request"; import { StopDecodingError } from "../types/errors"; diff --git a/packages/truffle-codec/lib/decode/index.ts b/packages/truffle-codec/lib/decode/index.ts index e9364e56140..284f9a9b6d3 100644 --- a/packages/truffle-codec/lib/decode/index.ts +++ b/packages/truffle-codec/lib/decode/index.ts @@ -12,7 +12,8 @@ import decodeSpecial from "./special"; import decodeTopic from "./event"; import { Types, Values } from "truffle-codec-utils"; import * as Pointer from "../types/pointer"; -import { EvmInfo, DecoderOptions } from "../types/evm"; +import { EvmInfo } from "../types/evm"; +import { DecoderOptions } from "../types/options"; import { DecoderRequest, GeneratorJunk } from "../types/request"; export default function* decode(dataType: Types.Type, pointer: Pointer.DataPointer, info: EvmInfo, options: DecoderOptions = {}): IterableIterator { diff --git a/packages/truffle-codec/lib/decode/value.ts b/packages/truffle-codec/lib/decode/value.ts index 9adec1e9791..26d4e635bf8 100644 --- a/packages/truffle-codec/lib/decode/value.ts +++ b/packages/truffle-codec/lib/decode/value.ts @@ -7,7 +7,8 @@ import { Types, Values } from "truffle-codec-utils"; import BN from "bn.js"; import utf8 from "utf8"; import { DataPointer } from "../types/pointer"; -import { EvmInfo, DecoderOptions } from "../types/evm"; +import { EvmInfo } from "../types/evm"; +import { DecoderOptions } from "../types/options"; import { DecoderRequest, GeneratorJunk } from "../types/request"; import { StopDecodingError } from "../types/errors"; diff --git a/packages/truffle-codec/lib/types/evm.ts b/packages/truffle-codec/lib/types/evm.ts index cf5d89f719a..c0e0e05ce04 100644 --- a/packages/truffle-codec/lib/types/evm.ts +++ b/packages/truffle-codec/lib/types/evm.ts @@ -55,10 +55,3 @@ export interface InternalFunction { contractPayable?: boolean; isDesignatedInvalid: boolean; } - -export interface DecoderOptions { - permissivePadding?: boolean; //allows incorrect padding on certain data types - strictAbiMode?: boolean; //throw errors instead of returning; check array & string lengths (crudely) - abiPointerBase?: number; - memoryVisited?: number[]; //for the future -} diff --git a/packages/truffle-codec/lib/types/options.ts b/packages/truffle-codec/lib/types/options.ts new file mode 100644 index 00000000000..a657573d6dc --- /dev/null +++ b/packages/truffle-codec/lib/types/options.ts @@ -0,0 +1,6 @@ +export interface DecoderOptions { + permissivePadding?: boolean; //allows incorrect padding on certain data types + strictAbiMode?: boolean; //throw errors instead of returning; check array & string lengths (crudely) + abiPointerBase?: number; + memoryVisited?: number[]; //for the future +} From d5c833c954e5283bd2297a2ac80673a82e28f2d5 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Thu, 15 Aug 2019 22:53:04 -0400 Subject: [PATCH 88/89] Rename fallback decoding case to message --- packages/truffle-codec/lib/interface/decoding.ts | 2 +- packages/truffle-codec/lib/types/decoding.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/truffle-codec/lib/interface/decoding.ts b/packages/truffle-codec/lib/interface/decoding.ts index 90bb54acdf8..28aa8d491c1 100644 --- a/packages/truffle-codec/lib/interface/decoding.ts +++ b/packages/truffle-codec/lib/interface/decoding.ts @@ -54,7 +54,7 @@ export function* decodeCalldata(info: EvmInfo): IterableIterator Date: Fri, 16 Aug 2019 00:44:24 -0400 Subject: [PATCH 89/89] Add test of debugger's boolean-key-cleaning --- .../test/data/more-decoding.js | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/packages/truffle-debugger/test/data/more-decoding.js b/packages/truffle-debugger/test/data/more-decoding.js index 2e23660fa4e..ec5ff9390eb 100644 --- a/packages/truffle-debugger/test/data/more-decoding.js +++ b/packages/truffle-debugger/test/data/more-decoding.js @@ -2,6 +2,7 @@ import debugModule from "debug"; const debug = debugModule("test:data:more-decoding"); import { assert } from "chai"; +import Web3 from "web3"; //just using for utils import Ganache from "ganache-core"; @@ -227,12 +228,26 @@ contract OverflowTest { } `; +const __BADBOOL = ` +pragma solidity ^0.5.0; + +contract BadBoolTest { + + mapping(bool => uint) boolMap; + + function run(bool key) public { + boolMap[key] = 1; + } +} +`; + let sources = { "ContainersTest.sol": __CONTAINERS, "ElementaryTest.sol": __KEYSANDBYTES, "SpliceTest.sol": __SPLICING, "ComplexMappingsTest.sol": __INNERMAPS, - "OverflowTest.sol": __OVERFLOW + "OverflowTest.sol": __OVERFLOW, + "BadBoolTest.sol": __BADBOOL }; describe("Further Decoding", function() { @@ -441,6 +456,42 @@ describe("Further Decoding", function() { } }); + it("Cleans badly-encoded booleans used as mapping keys", async function() { + this.timeout(12000); + + let instance = await abstractions.BadBoolTest.deployed(); + let signature = "run(bool)"; + //manually set up the selector; 10 is for initial 0x + 8 more hex digits + let selector = Web3.utils + .soliditySha3({ type: "string", value: signature }) + .slice(0, 10); + let argument = + "0000000000000000000000000000000000000000000000000000000000000002"; + let receipt = await instance.sendTransaction({ data: selector + argument }); + let txHash = receipt.tx; + + let bugger = await Debugger.forTx(txHash, { + provider, + files, + contracts: artifacts + }); + + let session = bugger.connect(); + + await session.continueUntilBreakpoint(); //run till end + + const variables = TruffleCodecUtils.Conversion.nativizeVariables( + await session.variables() + ); + debug("variables %O", variables); + + const expectedResult = { + boolMap: { true: 1 } + }; + + assert.deepInclude(variables, expectedResult); + }); + describe("Overflow", function() { it("Discards padding on unsigned integers", async function() { let instance = await abstractions.OverflowTest.deployed();