From 1eea6e23b2716221b8000e7ed9ead415808120ed Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Mon, 8 Apr 2019 20:47:01 -0400 Subject: [PATCH 1/7] Move from promise-based decoder to generator-based decoder, and have it make requests for unknown storage instead of passing in web3. --- .../lib/data/actions/index.js | 8 -- .../truffle-debugger/lib/data/reducers.js | 10 --- .../truffle-debugger/lib/data/sagas/index.js | 62 +++++++++++-- .../lib/data/selectors/index.js | 52 ----------- .../truffle-debugger/lib/session/index.js | 47 +++------- packages/truffle-debugger/lib/store/common.js | 42 +++++---- .../truffle-decoder/lib/decode/calldata.ts | 68 +++++++------- .../truffle-decoder/lib/decode/constant.ts | 7 +- packages/truffle-decoder/lib/decode/index.ts | 22 ++--- packages/truffle-decoder/lib/decode/memory.ts | 44 +++++---- .../truffle-decoder/lib/decode/special.ts | 50 +++++------ packages/truffle-decoder/lib/decode/stack.ts | 23 +++-- .../truffle-decoder/lib/decode/storage.ts | 49 +++++----- packages/truffle-decoder/lib/decode/value.ts | 8 +- .../lib/interface/contract-decoder.ts | 90 ++++++++++--------- .../truffle-decoder/lib/interface/index.ts | 5 +- packages/truffle-decoder/lib/read/index.ts | 6 +- packages/truffle-decoder/lib/read/storage.ts | 28 +++--- packages/truffle-decoder/lib/types/request.ts | 6 ++ packages/truffle-decoder/package.json | 2 - yarn.lock | 11 --- 21 files changed, 293 insertions(+), 347 deletions(-) create mode 100644 packages/truffle-decoder/lib/types/request.ts diff --git a/packages/truffle-debugger/lib/data/actions/index.js b/packages/truffle-debugger/lib/data/actions/index.js index 1f3f027a8ad..1cf21f07596 100644 --- a/packages/truffle-debugger/lib/data/actions/index.js +++ b/packages/truffle-debugger/lib/data/actions/index.js @@ -43,14 +43,6 @@ export function mapPathAndAssign( }; } -export const MAP_KEY_DECODING = "MAP_KEY_DECODING"; -export function mapKeyDecoding(started) { - return { - type: MAP_KEY_DECODING, - started - }; -} - export const RESET = "DATA_RESET"; export function reset() { return { type: RESET }; diff --git a/packages/truffle-debugger/lib/data/reducers.js b/packages/truffle-debugger/lib/data/reducers.js index 45c80f2d684..a195243b665 100644 --- a/packages/truffle-debugger/lib/data/reducers.js +++ b/packages/truffle-debugger/lib/data/reducers.js @@ -156,7 +156,6 @@ function assignments(state = DEFAULT_ASSIGNMENTS, action) { } const DEFAULT_PATHS = { - decodingStarted: 0, byAddress: {} }; @@ -166,15 +165,6 @@ const DEFAULT_PATHS = { //which is fine, as that's all we need it for. function mappedPaths(state = DEFAULT_PATHS, action) { switch (action.type) { - case actions.MAP_KEY_DECODING: - debug( - "decoding started: %d", - state.decodingStarted + (action.started ? 1 : -1) - ); - return { - ...state, - decodingStarted: state.decodingStarted + (action.started ? 1 : -1) - }; case actions.MAP_PATH_AND_ASSIGN: let { address, slot, typeIdentifier, parentType } = action; //how this case works: first, we find the spot in our table (based on diff --git a/packages/truffle-debugger/lib/data/sagas/index.js b/packages/truffle-debugger/lib/data/sagas/index.js index 96e27b84660..1f6f4e86f08 100644 --- a/packages/truffle-debugger/lib/data/sagas/index.js +++ b/packages/truffle-debugger/lib/data/sagas/index.js @@ -1,7 +1,7 @@ import debugModule from "debug"; const debug = debugModule("debugger:data:sagas"); -import { put, takeEvery, select, call } from "redux-saga/effects"; +import { put, takeEvery, select } from "redux-saga/effects"; import { prefixName, stableKeccak256, makeAssignment } from "lib/helpers"; @@ -19,7 +19,8 @@ import { getMemoryAllocations, getCalldataAllocations, readStack, - storageSize + storageSize, + forEvmState } from "truffle-decoder"; import BN from "bn.js"; @@ -42,9 +43,56 @@ function* tickSaga() { yield* trace.signalTickSagaCompletion(); } +export function* decode(definition, ref) { + let referenceDeclarations = yield select(data.views.referenceDeclarations); + let state = yield select(data.current.state); + let mappingKeys = yield select(data.views.mappingKeys); + let allocations = yield select(data.info.allocations); + + let ZERO_WORD = new Uint8Array(DecodeUtils.EVM.WORD_SIZE); + ZERO_WORD.fill(0); + + let decoder = forEvmState(definition, ref, { + referenceDeclarations, + state, + mappingKeys, + storageAllocations: allocations.storage, + memoryAllocations: allocations.memory, + calldataAllocations: allocations.calldata + }); + + let result = decoder.next(); + while (!result.done) { + let request = result.value; + let response; + switch (request.requesting) { + //yes, this is a little silly right now + case "storage": + //the debugger supplies all storage it knows at the beginning. + //any storage it does not know is presumed to be zero. + response = ZERO_WORD; + break; + } + result = decoder.next(response); + } + //at this point, result.value holds the final value + //note: we're still using the old decoder output format, so we need to clean + //containers before returning something the debugger can use + return DecodeUtils.Conversion.cleanContainers(result.value); +} + +export function* decodeAll() { + let definitions = yield select(data.current.identifiers.definitions); + let refs = yield select(data.current.identifiers.refs); + let decoded = {}; + for (let [identifier, ref] of Object.entries(refs)) { + decoded[identifier] = yield* decode(definitions[identifier], ref); + } + return decoded; +} + function* variablesAndMappingsSaga() { let node = yield select(data.current.node); - let decode = yield select(data.views.decoder); let scopes = yield select(data.views.scopes.inlined); let referenceDeclarations = yield select(data.views.referenceDeclarations); let allocations = yield select(data.info.allocations.storage); @@ -267,7 +315,6 @@ function* variablesAndMappingsSaga() { //begin subsection: key decoding //(I tried factoring this out into its own saga but it didn't work when I //did :P ) - yield put(actions.mapKeyDecoding(true)); let indexValue; let indexDefinition = node.indexExpression; @@ -291,7 +338,7 @@ function* variablesAndMappingsSaga() { //value will go on the stack *left*-padded instead of right-padded, //so looking for a prior assignment will read the wrong value. //so instead it's preferable to use the constant directly. - indexValue = yield call(decode, keyDefinition, { + indexValue = yield* decode(keyDefinition, { definition: indexDefinition }); } else if (indexReference) { @@ -311,7 +358,7 @@ function* variablesAndMappingsSaga() { } else { splicedDefinition = keyDefinition; } - indexValue = yield call(decode, splicedDefinition, indexReference); + indexValue = yield* decode(splicedDefinition, indexReference); } else if ( indexDefinition.referencedDeclaration && scopes[indexDefinition.referenceDeclaration] @@ -333,7 +380,7 @@ function* variablesAndMappingsSaga() { if ( DecodeUtils.Definition.isSimpleConstant(indexConstantDefinition) ) { - indexValue = yield call(decode, keyDefinition, { + indexValue = yield* decode(keyDefinition, { definition: indexConstantDeclaration.value }); } @@ -362,7 +409,6 @@ function* variablesAndMappingsSaga() { //now, as mentioned, retry in the typeConversion case } - yield put(actions.mapKeyDecoding(false)); //end subsection: key decoding debug("index value %O", indexValue); diff --git a/packages/truffle-debugger/lib/data/selectors/index.js b/packages/truffle-debugger/lib/data/selectors/index.js index 7568d89657d..0e5a4277f22 100644 --- a/packages/truffle-debugger/lib/data/selectors/index.js +++ b/packages/truffle-debugger/lib/data/selectors/index.js @@ -10,7 +10,6 @@ import evm from "lib/evm/selectors"; import solidity from "lib/solidity/selectors"; import * as DecodeUtils from "truffle-decode-utils"; -import { forEvmState } from "truffle-decoder"; /** * @private @@ -120,33 +119,6 @@ const data = createSelectorTree({ } }, - /** - * data.views.decoder - * - * selector returns (ast node definition, data reference) => Promise - */ - decoder: createLeaf( - [ - "/views/referenceDeclarations", - "/current/state", - "/views/mappingKeys", - "/info/allocations" - ], - - (referenceDeclarations, state, mappingKeys, allocations) => ( - definition, - ref - ) => - forEvmState(definition, ref, { - referenceDeclarations, - state, - mappingKeys, - storageAllocations: allocations.storage, - memoryAllocations: allocations.memory, - calldataAllocations: allocations.calldata - }) - ), - /* * data.views.userDefinedTypes */ @@ -717,30 +689,6 @@ const data = createSelectorTree({ } ) ) - ), - - /** - * data.current.identifiers.decoded - * - * Returns an object with values as Promises - */ - decoded: createLeaf( - ["/views/decoder", "./definitions", "./refs"], - - async (decode, definitions, refs) => { - debug("setting up keyedPromises"); - const keyedPromises = Object.entries(refs).map( - async ([identifier, ref]) => ({ - [identifier]: await decode(definitions[identifier], ref) - }) - ); - debug("set up keyedPromises"); - const keyedResults = await Promise.all(keyedPromises); - debug("got keyedResults"); - return DecodeUtils.Conversion.cleanContainers( - Object.assign({}, ...keyedResults) - ); - } ) } }, diff --git a/packages/truffle-debugger/lib/session/index.js b/packages/truffle-debugger/lib/session/index.js index 8bfae022eb6..6317d828caf 100644 --- a/packages/truffle-debugger/lib/session/index.js +++ b/packages/truffle-debugger/lib/session/index.js @@ -6,6 +6,7 @@ import configureStore from "lib/store"; import * as controller from "lib/controller/actions"; import * as actions from "./actions"; import data from "lib/data/selectors"; +import { decode, decodeAll } from "lib/data/sagas"; import controllerSelector from "lib/controller/selectors"; import rootSaga from "./sagas"; @@ -26,7 +27,9 @@ export default class Session { /** * @private */ - this._store = configureStore(reducer, rootSaga); + let { store, sagaMiddleware } = configureStore(reducer, rootSaga); + this._store = store; + this._sagaMiddleware = sagaMiddleware; let { contexts, sources } = Session.normalize(contracts, files); @@ -145,6 +148,10 @@ export default class Session { return true; } + async runSaga(saga, ...args) { + return await this._sagaMiddleware.run(saga, ...args).toPromise(); + } + async interrupt() { return this.dispatch(controller.interrupt()); } @@ -219,49 +226,19 @@ export default class Session { return this.dispatch(controller.removeAllBreakpoints()); } + //deprecated -- decode is now *always* ready! async decodeReady() { - return new Promise(resolve => { - let haveResolved = false; - const unsubscribe = this._store.subscribe(() => { - const subscriptionDecodingStarted = this.view(data.proc.decodingKeys); - - debug("following decoding started: %d", subscriptionDecodingStarted); - - if (subscriptionDecodingStarted <= 0 && !haveResolved) { - haveResolved = true; - unsubscribe(); - resolve(); - } - }); - - const decodingStarted = this.view(data.proc.decodingKeys); - - debug("initial decoding started: %d", decodingStarted); - - if (decodingStarted <= 0) { - haveResolved = true; - unsubscribe(); - resolve(); - } - }); + return true; } async variable(name) { - await this.decodeReady(); - const definitions = this.view(data.current.identifiers.definitions); const refs = this.view(data.current.identifiers.refs); - const decode = this.view(data.views.decoder); - return await decode(definitions[name], refs[name]); + return await this.runSaga(decode, definitions[name], refs[name]); } async variables() { - debug("awaiting decodeReady"); - await this.decodeReady(); - debug("decode now ready"); - - return await this.view(data.current.identifiers.decoded); - debug("got variables"); + return await this.runSaga(decodeAll); } } diff --git a/packages/truffle-debugger/lib/store/common.js b/packages/truffle-debugger/lib/store/common.js index 1f07db33b3d..01afc2ed93f 100644 --- a/packages/truffle-debugger/lib/store/common.js +++ b/packages/truffle-debugger/lib/store/common.js @@ -15,7 +15,7 @@ export function abbreviateValues(value, options = {}, depth = 0) { return "..."; } - const recurse = (child) => abbreviateValues(child, options, depth + 1); + const recurse = child => abbreviateValues(child, options, depth + 1); if (value instanceof Array) { if (value.length > options.arrayLimit) { @@ -27,27 +27,28 @@ export function abbreviateValues(value, options = {}, depth = 0) { } return value.map(recurse); - } else if (value instanceof Object) { - return Object.assign({}, - ...Object.entries(value).map( - ([k, v]) => ({ [recurse(k)]: recurse(v) }) - ) + return Object.assign( + {}, + ...Object.entries(value).map(([k, v]) => ({ [recurse(k)]: recurse(v) })) ); - } else if (typeof value === "string" && value.length > options.stringLimit) { let inner = "..."; let extractAmount = (options.stringLimit - inner.length) / 2; let leading = value.slice(0, Math.ceil(extractAmount)); let trailing = value.slice(value.length - Math.floor(extractAmount)); return `${leading}${inner}${trailing}`; - } else { return value; } } -export default function configureStore (reducer, saga, initialState, composeEnhancers) { +export default function configureStore( + reducer, + saga, + initialState, + composeEnhancers +) { const sagaMiddleware = createSagaMiddleware(); if (!composeEnhancers) { @@ -56,25 +57,22 @@ export default function configureStore (reducer, saga, initialState, composeEnha const loggerMiddleware = createLogger({ log: reduxDebug, - stateTransformer: (state) => abbreviateValues(state, { - arrayLimit: 4, - recurseLimit: 3 - }), - actionTransformer: abbreviateValues, + stateTransformer: state => + abbreviateValues(state, { + arrayLimit: 4, + recurseLimit: 3 + }), + actionTransformer: abbreviateValues }); let store = createStore( - reducer, initialState, + reducer, + initialState, - composeEnhancers( - applyMiddleware( - sagaMiddleware, - loggerMiddleware - ) - ) + composeEnhancers(applyMiddleware(sagaMiddleware, loggerMiddleware)) ); sagaMiddleware.run(saga); - return store; + return { store, sagaMiddleware }; } diff --git a/packages/truffle-decoder/lib/decode/calldata.ts b/packages/truffle-decoder/lib/decode/calldata.ts index db364cdad14..4e9b01013ff 100644 --- a/packages/truffle-decoder/lib/decode/calldata.ts +++ b/packages/truffle-decoder/lib/decode/calldata.ts @@ -8,28 +8,28 @@ import { CalldataPointer, DataPointer } from "../types/pointer"; import { CalldataMemberAllocation } from "../types/allocation"; import { calldataSize } from "../allocate/calldata"; import { EvmInfo } from "../types/evm"; -import range from "lodash.range"; +import { DecoderRequest } from "../types/request"; -export default async function decodeCalldata(definition: DecodeUtils.AstDefinition, pointer: CalldataPointer, info: EvmInfo, base: number = 0): Promise { +export default function* decodeCalldata(definition: DecodeUtils.AstDefinition, pointer: CalldataPointer, info: EvmInfo, base: number = 0): IterableIterator { if(DecodeUtils.Definition.isReference(definition)) { let dynamic = calldataSize(definition, info.referenceDeclarations, info.calldataAllocations)[1]; if(dynamic) { - return await decodeCalldataReferenceByAddress(definition, pointer, info, base); + return yield* decodeCalldataReferenceByAddress(definition, pointer, info, base); } else { - return await decodeCalldataReferenceStatic(definition, pointer, info); + return yield* decodeCalldataReferenceStatic(definition, pointer, info); } } else { debug("pointer %o", pointer); - return await decodeValue(definition, pointer, info); + return yield* decodeValue(definition, pointer, info); } } -export async function decodeCalldataReferenceByAddress(definition: DecodeUtils.AstDefinition, pointer: DataPointer, info: EvmInfo, base: number = 0): Promise { +export function* decodeCalldataReferenceByAddress(definition: DecodeUtils.AstDefinition, pointer: DataPointer, info: EvmInfo, base: number = 0): IterableIterator { const { state } = info; debug("pointer %o", pointer); - let rawValue: Uint8Array = await read(pointer, state); + let rawValue: Uint8Array = yield* read(pointer, state); let startPosition = DecodeUtils.Conversion.toBN(rawValue).toNumber() + base; debug("startPosition %d", startPosition); @@ -42,14 +42,14 @@ export async function decodeCalldataReferenceByAddress(definition: DecodeUtils.A length: size } } - return await decodeCalldataReferenceStatic(definition, staticPointer, info); + return yield* decodeCalldataReferenceStatic(definition, staticPointer, info); } let length; switch (DecodeUtils.Definition.typeClass(definition)) { case "bytes": case "string": - length = DecodeUtils.Conversion.toBN(await read({ + length = DecodeUtils.Conversion.toBN(yield* read({ calldata: { start: startPosition, length: DecodeUtils.EVM.WORD_SIZE} }, state)).toNumber(); //initial word contains length @@ -57,12 +57,12 @@ export async function decodeCalldataReferenceByAddress(definition: DecodeUtils.A calldata: { start: startPosition + DecodeUtils.EVM.WORD_SIZE, length } } - return await decodeValue(definition, childPointer, info); + return yield* decodeValue(definition, childPointer, info); case "array": if (DecodeUtils.Definition.isDynamicArray(definition)) { - length = DecodeUtils.Conversion.toBN(await read({ + length = DecodeUtils.Conversion.toBN(yield* read({ calldata: { start: startPosition, length: DecodeUtils.EVM.WORD_SIZE }, }, state)).toNumber(); // initial word contains array length startPosition += DecodeUtils.EVM.WORD_SIZE; //increment startPosition to @@ -84,17 +84,20 @@ export async function decodeCalldataReferenceByAddress(definition: DecodeUtils.A baseDefinition = DecodeUtils.Definition.spliceLocation(baseDefinition, "calldata"); let baseSize = calldataSize(baseDefinition, info.referenceDeclarations, info.calldataAllocations)[0]; - return await Promise.all(range(length).map( (index: number) => - decodeCalldata(baseDefinition, + let decodedChildren = []; + for(let index = 0; index < length; index++) { + decodedChildren.push(yield* decodeCalldata( + baseDefinition, { calldata: { start: startPosition + index * baseSize, length: baseSize }}, - info, startPosition) //pointer base is always start of list, never the length - )); + info, startPosition)); //pointer base is always start of list, never the length + } + return decodedChildren; case "struct": - return await decodeCalldataStructByPosition(definition, startPosition, info); + return yield* decodeCalldataStructByPosition(definition, startPosition, info); default: // debug("Unknown calldata reference type: %s", DecodeUtils.typeIdentifier(definition)); @@ -102,7 +105,7 @@ export async function decodeCalldataReferenceByAddress(definition: DecodeUtils.A } } -export async function decodeCalldataReferenceStatic(definition: DecodeUtils.AstDefinition, pointer: CalldataPointer, info: EvmInfo): Promise { +export function* decodeCalldataReferenceStatic(definition: DecodeUtils.AstDefinition, pointer: CalldataPointer, info: EvmInfo): IterableIterator { const { state } = info; debug("static"); debug("pointer %o", pointer); @@ -123,17 +126,20 @@ export async function decodeCalldataReferenceStatic(definition: DecodeUtils.AstD baseDefinition = DecodeUtils.Definition.spliceLocation(baseDefinition, "calldata"); let baseSize = calldataSize(baseDefinition, info.referenceDeclarations, info.calldataAllocations)[0]; - return await Promise.all(range(length).map( (index: number) => - decodeCalldata(baseDefinition, + let decodedChildren = []; + for(let index = 0; index < length; index++) { + decodedChildren.push(yield* decodeCalldata( + baseDefinition, { calldata: { start: pointer.calldata.start + index * baseSize, length: baseSize }}, - info) //static case so don't need base - )); + info)); //static case so don't need base + } + return decodedChildren; case "struct": - return await decodeCalldataStructByPosition(definition, pointer.calldata.start, info); + return yield* decodeCalldataStructByPosition(definition, pointer.calldata.start, info); default: // debug("Unknown calldata reference type: %s", DecodeUtils.typeIdentifier(definition)); @@ -142,7 +148,7 @@ export async function decodeCalldataReferenceStatic(definition: DecodeUtils.AstD } //note that this function takes the start position as a *number*; it does not take a calldata pointer -async function decodeCalldataStructByPosition(definition: DecodeUtils.AstDefinition, startPosition: number, info: EvmInfo): Promise { +function* decodeCalldataStructByPosition(definition: DecodeUtils.AstDefinition, startPosition: number, info: EvmInfo): IterableIterator { const { state, referenceDeclarations, calldataAllocations } = info; const referencedDeclaration = definition.typeName @@ -154,7 +160,8 @@ async function decodeCalldataStructByPosition(definition: DecodeUtils.AstDefinit return undefined; //this should never happen } - const decodeAllocation = async (memberAllocation: CalldataMemberAllocation) => { + let decodedMembers: any = {}; + for(let memberAllocation of Object.values(structAllocation.members)) { const memberPointer = memberAllocation.pointer; const childPointer: CalldataPointer = { calldata: { @@ -170,16 +177,9 @@ async function decodeCalldataStructByPosition(definition: DecodeUtils.AstDefinit //there also used to be code here to add on the "_ptr" ending when absent, but we //presently ignore that ending, so we'll skip that - let decoded = await decodeCalldata(memberDefinition, childPointer, info, startPosition); - //note that if we are in the static case, then the last parameter is irrelevant, - //but we pass it anyway for simplicity + let decoded = yield* decodeCalldata(memberDefinition, childPointer, info); - return { - [memberDefinition.name]: decoded - }; + decodedMembers[memberDefinition.name] = decoded; } - - const decodings = Object.values(structAllocation.members).map(decodeAllocation); - - return Object.assign({}, ...await Promise.all(decodings)); + return decodedMembers; } diff --git a/packages/truffle-decoder/lib/decode/constant.ts b/packages/truffle-decoder/lib/decode/constant.ts index 75eba6e03ea..a59622c5f5a 100644 --- a/packages/truffle-decoder/lib/decode/constant.ts +++ b/packages/truffle-decoder/lib/decode/constant.ts @@ -6,9 +6,10 @@ import read from "../read"; import decodeValue from "./value"; import { ConstantDefinitionPointer} from "../types/pointer"; import { EvmInfo } from "../types/evm"; +import { DecoderRequest } from "../types/request"; import BN from "bn.js"; -export default async function decodeConstant(definition: DecodeUtils.AstDefinition, pointer: ConstantDefinitionPointer, info: EvmInfo): Promise { +export default function* decodeConstant(definition: DecodeUtils.AstDefinition, pointer: ConstantDefinitionPointer, info: EvmInfo): IterableIterator { debug("definition %O", definition); debug("pointer %o", pointer); @@ -22,12 +23,12 @@ export default async function decodeConstant(definition: DecodeUtils.AstDefiniti if(DecodeUtils.Definition.typeClass(definition) === "bytes") { let size = DecodeUtils.Definition.specifiedSize(definition); if(size !== null) { - let word = await read(pointer, info.state); + let word = yield* read(pointer, info.state); let bytes = word.slice(DecodeUtils.EVM.WORD_SIZE - size); return DecodeUtils.Conversion.toHexString(bytes); } } //otherwise, as mentioned, just dispatch to decodeValue - return await decodeValue(definition, pointer, info); + return yield* decodeValue(definition, pointer, info); } diff --git a/packages/truffle-decoder/lib/decode/index.ts b/packages/truffle-decoder/lib/decode/index.ts index 0867d83661e..9c793b4a0b9 100644 --- a/packages/truffle-decoder/lib/decode/index.ts +++ b/packages/truffle-decoder/lib/decode/index.ts @@ -11,46 +11,42 @@ import decodeSpecial from "./special"; import { AstDefinition } from "truffle-decode-utils"; import * as Pointer from "../types/pointer"; import { EvmInfo } from "../types/evm"; -import Web3 from "web3"; +import { DecoderRequest } from "../types/request"; -export default async function decode(definition: AstDefinition, pointer: Pointer.DataPointer, info: EvmInfo, web3?: Web3, contractAddress?: string): Promise { +export default function* decode(definition: AstDefinition, pointer: Pointer.DataPointer, info: EvmInfo): IterableIterator { debug("Decoding %s", definition.name); debug("pointer %O", pointer); if(Pointer.isStoragePointer(pointer)) { - return await decodeStorage(definition, pointer, info, web3, contractAddress) + return yield* decodeStorage(definition, pointer, info) } if(Pointer.isStackPointer(pointer)) { - return await decodeStack(definition, pointer, info, web3, contractAddress); - //stack may contain pointer to storage so may need web3 & contractAddress + return yield* decodeStack(definition, pointer, info); } if (Pointer.isStackLiteralPointer(pointer)) { - return await decodeLiteral(definition, pointer, info, web3, contractAddress); - //literal may contain pointer to storage so may need web3 & contractAddress + return yield* decodeLiteral(definition, pointer, info); } if(Pointer.isConstantDefinitionPointer(pointer)) { - return await decodeConstant(definition, pointer, info); + return yield* decodeConstant(definition, pointer, info); //I'd like to just use decodeValue, but unfortunately there are some special //cases to deal with } if(Pointer.isSpecialPointer(pointer)) { - return await decodeSpecial(definition, pointer, info); + return yield* decodeSpecial(definition, pointer, info); } //NOTE: the following two cases shouldn't come up but they've been left in as //fallback cases if(Pointer.isMemoryPointer(pointer)) { - return await decodeMemory(definition, pointer, info); - //memory does not need web3 & contractAddress + return yield* decodeMemory(definition, pointer, info); } if(Pointer.isCalldataPointer(pointer)) { - return await decodeCalldata(definition, pointer, info); - //calldata does not need web3 & contractAddress + return yield* decodeCalldata(definition, pointer, info); } } diff --git a/packages/truffle-decoder/lib/decode/memory.ts b/packages/truffle-decoder/lib/decode/memory.ts index 2cd91ce92a2..a63722a48fa 100644 --- a/packages/truffle-decoder/lib/decode/memory.ts +++ b/packages/truffle-decoder/lib/decode/memory.ts @@ -7,21 +7,21 @@ import decodeValue from "./value"; import { MemoryPointer, DataPointer } from "../types/pointer"; import { MemoryMemberAllocation } from "../types/allocation"; import { EvmInfo } from "../types/evm"; -import range from "lodash.range"; +import { DecoderRequest } from "../types/request"; -export default async function decodeMemory(definition: DecodeUtils.AstDefinition, pointer: MemoryPointer, info: EvmInfo): Promise { +export default function* decodeMemory(definition: DecodeUtils.AstDefinition, pointer: MemoryPointer, info: EvmInfo): IterableIterator { if(DecodeUtils.Definition.isReference(definition)) { - return await decodeMemoryReferenceByAddress(definition, pointer, info); + return yield* decodeMemoryReferenceByAddress(definition, pointer, info); } else { - return await decodeValue(definition, pointer, info); + return yield* decodeValue(definition, pointer, info); } } -export async function decodeMemoryReferenceByAddress(definition: DecodeUtils.AstDefinition, pointer: DataPointer, info: EvmInfo): Promise { +export function* decodeMemoryReferenceByAddress(definition: DecodeUtils.AstDefinition, pointer: DataPointer, info: EvmInfo): IterableIterator { const { state } = info; // debug("pointer %o", pointer); - let rawValue: Uint8Array = await read(pointer, state); + let rawValue: Uint8Array = yield* read(pointer, state); let startPosition = DecodeUtils.Conversion.toBN(rawValue).toNumber(); let length; @@ -30,7 +30,7 @@ export async function decodeMemoryReferenceByAddress(definition: DecodeUtils.Ast case "bytes": case "string": - length = DecodeUtils.Conversion.toBN(await read({ + length = DecodeUtils.Conversion.toBN(yield* read({ memory: { start: startPosition, length: DecodeUtils.EVM.WORD_SIZE} }, state)).toNumber(); //initial word contains length @@ -38,12 +38,12 @@ export async function decodeMemoryReferenceByAddress(definition: DecodeUtils.Ast memory: { start: startPosition + DecodeUtils.EVM.WORD_SIZE, length } } - return await decodeValue(definition, childPointer, info); + return yield* decodeValue(definition, childPointer, info); case "array": if (DecodeUtils.Definition.isDynamicArray(definition)) { - length = DecodeUtils.Conversion.toBN(await read({ + length = DecodeUtils.Conversion.toBN(yield* read({ memory: { start: startPosition, length: DecodeUtils.EVM.WORD_SIZE }, }, state)).toNumber(); // initial word contains array length startPosition += DecodeUtils.EVM.WORD_SIZE; //increment startPosition to @@ -60,14 +60,17 @@ export async function decodeMemoryReferenceByAddress(definition: DecodeUtils.Ast // replace erroneous `_storage_` type identifiers with `_memory_` baseDefinition = DecodeUtils.Definition.spliceLocation(baseDefinition, "memory"); - return await Promise.all(range(length).map( (index: number) => - decodeMemory(baseDefinition, + let decodedChildren = []; + for(let index = 0; index < length; index++) { + decodedChildren.push(yield* decodeMemory( + baseDefinition, { memory: { start: startPosition + index * DecodeUtils.EVM.WORD_SIZE, length: DecodeUtils.EVM.WORD_SIZE }}, - info) - )); + info)); + } + return decodedChildren; case "struct": const { referenceDeclarations, memoryAllocations } = info; @@ -79,7 +82,8 @@ export async function decodeMemoryReferenceByAddress(definition: DecodeUtils.Ast debug("structAllocation %O", structAllocation); - const decodeAllocation = async (memberAllocation: MemoryMemberAllocation) => { + let decodedMembers: any = {}; + for(let memberAllocation of Object.values(structAllocation.members)) { const memberPointer = memberAllocation.pointer; const childPointer: MemoryPointer = { memory: { @@ -95,17 +99,11 @@ export async function decodeMemoryReferenceByAddress(definition: DecodeUtils.Ast //there also used to be code here to add on the "_ptr" ending when absent, but we //presently ignore that ending, so we'll skip that - let decoded = await decodeMemory(memberDefinition, childPointer, info); + let decoded = yield* decodeMemory(memberDefinition, childPointer, info); - return { - [memberDefinition.name]: decoded - }; + decodedMembers[memberDefinition.name] = decoded; } - - const decodings = Object.values(structAllocation.members).map(decodeAllocation); - - return Object.assign({}, ...await Promise.all(decodings)); - + return decodedMembers; default: // debug("Unknown memory reference type: %s", DecodeUtils.typeIdentifier(definition)); diff --git a/packages/truffle-decoder/lib/decode/special.ts b/packages/truffle-decoder/lib/decode/special.ts index 4c9f3903c8b..eed447d2a4b 100644 --- a/packages/truffle-decoder/lib/decode/special.ts +++ b/packages/truffle-decoder/lib/decode/special.ts @@ -5,24 +5,25 @@ import * as DecodeUtils from "truffle-decode-utils"; import decodeValue from "./value"; import { EvmInfo } from "../types/evm"; import { SpecialPointer } from "../types/pointer"; +import { DecoderRequest } from "../types/request"; -export default async function decodeSpecial(definition: DecodeUtils.AstDefinition, pointer: SpecialPointer, info: EvmInfo): Promise { +export default function* decodeSpecial(definition: DecodeUtils.AstDefinition, pointer: SpecialPointer, info: EvmInfo): IterableIterator { if(DecodeUtils.Definition.typeClass(definition) === "magic") { //that's right, magic! - return await decodeMagic(definition, pointer, info); + return yield* decodeMagic(definition, pointer, info); } else { - return await decodeValue(definition, pointer, info); + return yield* decodeValue(definition, pointer, info); } } -export async function decodeMagic(definition: DecodeUtils.AstDefinition, pointer: SpecialPointer, info: EvmInfo): Promise { +export function* decodeMagic(definition: DecodeUtils.AstDefinition, pointer: SpecialPointer, info: EvmInfo): IterableIterator { let {state} = info; switch(pointer.special) { case "msg": return { - data: await decodeValue( + data: yield* decodeValue( DecodeUtils.Definition.MSG_DATA_DEFINITION, {calldata: { start: 0, @@ -30,7 +31,7 @@ export async function decodeMagic(definition: DecodeUtils.AstDefinition, pointer }}, info ), - sig: await decodeValue( + sig: yield* decodeValue( DecodeUtils.Definition.MSG_SIG_DEFINITION, {calldata: { start: 0, @@ -38,12 +39,12 @@ export async function decodeMagic(definition: DecodeUtils.AstDefinition, pointer }}, info ), - sender: await decodeValue( + sender: yield* decodeValue( DecodeUtils.Definition.spoofAddressPayableDefinition("sender"), {special: "sender"}, info ), - value: await decodeValue( + value: yield* decodeValue( DecodeUtils.Definition.spoofUintDefinition("value"), {special: "value"}, info @@ -51,39 +52,36 @@ export async function decodeMagic(definition: DecodeUtils.AstDefinition, pointer }; case "tx": return { - origin: await decodeValue( + origin: yield* decodeValue( DecodeUtils.Definition.spoofAddressPayableDefinition("origin"), {special: "origin"}, info ), - gasprice: await decodeValue( + gasprice: yield* decodeValue( DecodeUtils.Definition.spoofUintDefinition("gasprice"), {special: "gasprice"}, info ) }; case "block": - return { - coinbase: await decodeValue( + let block: any = { + coinbase: yield* decodeValue( DecodeUtils.Definition.spoofAddressPayableDefinition("coinbase"), {special: "coinbase"}, info - ), - //the other ones are all uint's, so let's handle them all at once - ...Object.assign({}, - ...await Promise.all( - ["difficulty", "gaslimit", "number", "timestamp"].map( - async (variable) => ({ - [variable]: await decodeValue( - DecodeUtils.Definition.spoofUintDefinition(variable), - {special: variable}, - info - ) - }) - ) - ) ) }; + //the other ones are all uint's, so let's handle them all at once; due to + //the lack of generator arrow functions, we do it by mutating block + const variables = ["difficulty", "gaslimit", "number", "timestamp"]; + for (let variable of variables) { + block[variable] = yield* decodeValue( + DecodeUtils.Definition.spoofUintDefinition(variable), + {special: variable}, + info + ); + } + return block; default: debug("Unrecognized magic variable!"); } diff --git a/packages/truffle-decoder/lib/decode/stack.ts b/packages/truffle-decoder/lib/decode/stack.ts index 52783b0d11b..2e343eda9fa 100644 --- a/packages/truffle-decoder/lib/decode/stack.ts +++ b/packages/truffle-decoder/lib/decode/stack.ts @@ -9,15 +9,15 @@ import { decodeStorageReferenceByAddress } from "./storage"; import { decodeCalldataReferenceByAddress } from "./calldata"; import { StackPointer, StackLiteralPointer } from "../types/pointer"; import { EvmInfo } from "../types/evm"; -import Web3 from "web3"; +import { DecoderRequest } from "../types/request"; -export async function decodeStack(definition: DecodeUtils.AstDefinition, pointer: StackPointer, info: EvmInfo, web3?: Web3, contractAddress?: string): Promise { - const rawValue: Uint8Array = await read(pointer, info.state, web3, contractAddress); +export function* decodeStack(definition: DecodeUtils.AstDefinition, pointer: StackPointer, info: EvmInfo): IterableIterator { + const rawValue: Uint8Array = yield* read(pointer, info.state); const literalPointer: StackLiteralPointer = { literal: rawValue }; - return await decodeLiteral(definition, literalPointer, info, web3, contractAddress); + return yield* decodeLiteral(definition, literalPointer, info); } -export async function decodeLiteral(definition: DecodeUtils.AstDefinition, pointer: StackLiteralPointer, info: EvmInfo, web3?: Web3, contractAddress?: string): Promise { +export function* decodeLiteral(definition: DecodeUtils.AstDefinition, pointer: StackLiteralPointer, info: EvmInfo): IterableIterator { debug("definition %O", definition); debug("pointer %o", pointer); @@ -26,7 +26,7 @@ export async function decodeLiteral(definition: DecodeUtils.AstDefinition, point //decodeMemoryReference, which knows how to decode the pointer already if(DecodeUtils.Definition.isReference(definition) && DecodeUtils.Definition.referenceType(definition) === "memory") { - return await decodeMemoryReferenceByAddress(definition, pointer, info); + return yield* decodeMemoryReferenceByAddress(definition, pointer, info); } //next: do we have a storage pointer (which may be a mapping)? if so, we can @@ -35,7 +35,7 @@ export async function decodeLiteral(definition: DecodeUtils.AstDefinition, point if((DecodeUtils.Definition.isReference(definition) && DecodeUtils.Definition.referenceType(definition) === "storage") || DecodeUtils.Definition.isMapping(definition)) { - return await decodeStorageReferenceByAddress(definition, pointer, info, web3, contractAddress); + return yield* decodeStorageReferenceByAddress(definition, pointer, info); } //next: do we have a calldata pointer? @@ -50,7 +50,7 @@ export async function decodeLiteral(definition: DecodeUtils.AstDefinition, point 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 }}; - return await decodeValue(definition, newPointer, info); + return yield* decodeValue(definition, newPointer, info); } //otherwise, is it a dynamic array? @@ -62,12 +62,12 @@ export async function decodeLiteral(definition: DecodeUtils.AstDefinition, point //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 await decodeCalldataReferenceByAddress(definition, {literal: locationOnly}, info, -DecodeUtils.EVM.WORD_SIZE); + return yield* decodeCalldataReferenceByAddress(definition, {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 await decodeCalldataReferenceByAddress(definition, pointer, info, 0); + return yield* decodeCalldataReferenceByAddress(definition, pointer, info, 0); } } @@ -76,6 +76,5 @@ export async function decodeLiteral(definition: DecodeUtils.AstDefinition, point //don't yet support this case, so let's just move on //finally, if none of the above hold, we can just dispatch to decodeValue. - //note we don't need web3 and contract address at this point - return await decodeValue(definition, pointer, info); + return yield* decodeValue(definition, pointer, info); } diff --git a/packages/truffle-decoder/lib/decode/storage.ts b/packages/truffle-decoder/lib/decode/storage.ts index fe5ac5e03bf..2d14c886512 100644 --- a/packages/truffle-decoder/lib/decode/storage.ts +++ b/packages/truffle-decoder/lib/decode/storage.ts @@ -10,25 +10,25 @@ import { storageSize } from "../allocate/storage"; import { slotAddress } from "../read/storage"; import * as Types from "../types/storage"; import BN from "bn.js"; -import Web3 from "web3"; import { EvmStruct, EvmMapping } from "../interface/contract-decoder"; +import { DecoderRequest } from "../types/request"; -export default async function decodeStorage(definition: DecodeUtils.AstDefinition, pointer: StoragePointer, info: EvmInfo, web3?: Web3, contractAddress?: string): Promise { +export default function* decodeStorage(definition: DecodeUtils.AstDefinition, pointer: StoragePointer, info: EvmInfo): IterableIterator { if(DecodeUtils.Definition.isReference(definition) || DecodeUtils.Definition.isMapping(definition)) { //note that mappings are not caught by isReference and must be checked for separately - return await decodeStorageReference(definition, pointer, info, web3, contractAddress); + return yield* decodeStorageReference(definition, pointer, info); } else { - return await decodeValue(definition, pointer, info, web3, contractAddress); + return yield* decodeValue(definition, pointer, info); } } //decodes storage at the address *read* from the pointer -- hence why this takes DataPointer rather than StoragePointer. //NOTE: ONLY for use with pointers to reference types! //Of course, pointers to value types don't exist in Solidity, so that warning is redundant, but... -export async function decodeStorageReferenceByAddress(definition: DecodeUtils.AstDefinition, pointer: DataPointer, info: EvmInfo, web3?: Web3, contractAddress?: string): Promise { +export function* decodeStorageReferenceByAddress(definition: DecodeUtils.AstDefinition, pointer: DataPointer, info: EvmInfo): IterableIterator { - const rawValue: Uint8Array = await read(pointer, info.state, web3, contractAddress); + const rawValue: Uint8Array = yield* read(pointer, info.state); const startOffset = DecodeUtils.Conversion.toBN(rawValue); //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 @@ -50,10 +50,10 @@ export async function decodeStorageReferenceByAddress(definition: DecodeUtils.As } }}; //dispatch to decodeStorageReference - return await decodeStorageReference(definition, newPointer, info, web3, contractAddress); + return yield* decodeStorageReference(definition, newPointer, info); } -export async function decodeStorageReference(definition: DecodeUtils.AstDefinition, pointer: StoragePointer, info: EvmInfo, web3?: Web3, contractAddress?: string): Promise { +export function* decodeStorageReference(definition: DecodeUtils.AstDefinition, pointer: StoragePointer, info: EvmInfo): IterableIterator { var data; var length; @@ -65,7 +65,7 @@ export async function decodeStorageReference(definition: DecodeUtils.AstDefiniti if (DecodeUtils.Definition.isDynamicArray(definition)) { debug("dynamic array"); debug("definition %O", definition); - data = await read(pointer, state, web3, contractAddress); + data = yield* read(pointer, state); length = DecodeUtils.Conversion.toBN(data).toNumber(); } @@ -162,19 +162,20 @@ export async function decodeStorageReference(definition: DecodeUtils.AstDefiniti } } - const decodePromises = ranges.map( (childRange, idx) => { - debug("childFrom %d, %o", idx, childRange.from); - return decodeStorage(baseDefinition, { - storage: childRange - }, info, web3, contractAddress); - }); + let decodedChildren: any[] = []; - return await Promise.all(decodePromises); + for(let childRange of ranges) { + decodedChildren.push( + yield* decodeStorage(baseDefinition, {storage: childRange}, info) + ); + } + + return decodedChildren; } case "bytes": case "string": { - data = await read(pointer, state, web3, contractAddress); + data = yield* read(pointer, state); if (data == undefined) { return undefined; } @@ -190,16 +191,16 @@ export async function decodeStorageReference(definition: DecodeUtils.AstDefiniti length = lengthByte / 2; debug("in-word; length %o", length); - return decodeValue(definition, { storage: { + return yield* decodeValue(definition, { storage: { from: { slot: pointer.storage.from.slot, index: 0 }, to: { slot: pointer.storage.from.slot, index: length - 1} - }}, info, web3, contractAddress); + }}, info); } else { length = DecodeUtils.Conversion.toBN(data).subn(1).divn(2).toNumber(); debug("new-word, length %o", length); - return decodeValue(definition, { + return yield* decodeValue(definition, { storage: { from: { slot: { @@ -211,7 +212,7 @@ export async function decodeStorageReference(definition: DecodeUtils.AstDefiniti }, length } - }, info, web3, contractAddress); + }, info); } } @@ -259,9 +260,9 @@ export async function decodeStorageReference(definition: DecodeUtils.AstDefiniti index: memberPointer.storage.to.index }, }; - const val = await decodeStorage( + const val = yield* decodeStorage( memberDefinition, - {storage: childRange}, info, web3, contractAddress + {storage: childRange}, info ); result.members[memberDefinition.name] = { @@ -355,7 +356,7 @@ export async function decodeStorageReference(definition: DecodeUtils.AstDefiniti //note at this point, key could be a string, hex string, //BN, or boolean result.members[key.toString()] = - await decodeStorage(valueDefinition, valuePointer, info, web3, contractAddress); + yield* decodeStorage(valueDefinition, valuePointer, info); } return result; diff --git a/packages/truffle-decoder/lib/decode/value.ts b/packages/truffle-decoder/lib/decode/value.ts index c9409989a6b..13879c837c8 100644 --- a/packages/truffle-decoder/lib/decode/value.ts +++ b/packages/truffle-decoder/lib/decode/value.ts @@ -7,12 +7,14 @@ import BN from "bn.js"; import { DataPointer } from "../types/pointer"; import { EvmInfo } from "../types/evm"; import { EvmEnum } from "../interface/contract-decoder"; -import Web3 from "web3"; +import { DecoderRequest } from "../types/request"; -export default async function decodeValue(definition: DecodeUtils.AstDefinition, pointer: DataPointer, info: EvmInfo, web3?: Web3, contractAddress?: string): Promise { +export default function* decodeValue(definition: DecodeUtils.AstDefinition, pointer: DataPointer, info: EvmInfo): 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; - let bytes = await read(pointer, state, web3, contractAddress); + let bytes = yield* read(pointer, state); if (bytes == undefined) { // debug("segfault, pointer %o, state: %O", pointer, state); return undefined; diff --git a/packages/truffle-decoder/lib/interface/contract-decoder.ts b/packages/truffle-decoder/lib/interface/contract-decoder.ts index 870396e999a..a26a95729c3 100644 --- a/packages/truffle-decoder/lib/interface/contract-decoder.ts +++ b/packages/truffle-decoder/lib/interface/contract-decoder.ts @@ -1,6 +1,7 @@ import debugModule from "debug"; const debug = debugModule("decoder:interface:contract-decoder"); +import * as DecodeUtils from "truffle-decode-utils"; import AsyncEventEmitter from "async-eventemitter"; import Web3 from "web3"; import { ContractObject } from "truffle-contract-schema/spec"; @@ -11,6 +12,7 @@ import * as storage from "../allocate/storage"; import { StoragePointer, isStoragePointer } from "../types/pointer"; import { StorageAllocations, StorageMemberAllocations, StorageMemberAllocation } from "../types/allocation"; import { Slot, isWordsLength } from "../types/storage"; +import { DecoderRequest } from "../types/request"; import decode from "../decode"; import { Definition as DefinitionUtils, EVM, AstDefinition, AstReferences } from "truffle-decode-utils"; import { BlockType, Transaction } from "web3/eth/types"; @@ -54,6 +56,7 @@ interface DecodedVariable { interface ContractState { name: string; balance: BN; + nonce: BN; variables: { [name: string]: DecodedVariable }; @@ -157,10 +160,51 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { debug("stateVariableReferences %O", this.stateVariableReferences); } + private async decodeVariable(variable: StorageMemberAllocation, block: BlockType): Promise { + const info: EvmInfo = { + state: { + stack: [], + storage: {}, + memory: new Uint8Array(0) + }, + mappingKeys: this.mappingKeys, + referenceDeclarations: this.referenceDeclarations, + storageAllocations: this.storageAllocations, + }; + + const decoder: IterableIterator = decode(variable.definition, variable.pointer, info); + + let result: IteratorResult = decoder.next(); + while(!result.done) { + let request = (result.value); + let response: any + switch(request.requesting) { + //yes, this is a little silly right now + case "storage": + response = DecodeUtils.Conversion.toBytes( + await this.web3.eth.getStorageAt( + this.contractAddress, + request.slot, + block), + DecodeUtils.EVM.WORD_SIZE); + break; + } + result = decoder.next(response); + } + //at this point, result.value holds the final value + + return { + name: variable.definition.name, + type: DefinitionUtils.typeClass(variable.definition), + value: result.value + }; + } + public async state(block: BlockType = "latest"): Promise { let result: ContractState = { name: this.contract.contractName, - balance: new BN(await this.web3.eth.getBalance(this.contractAddress)), + balance: new BN(await this.web3.eth.getBalance(this.contractAddress, block)), + nonce: new BN(await this.web3.eth.getTransactionCount(this.contractAddress, block)), variables: {} }; @@ -168,26 +212,11 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { for(const variable of Object.values(this.stateVariableReferences)) { - const info: EvmInfo = { - state: { - stack: [], - storage: {}, - memory: new Uint8Array(0) - }, - mappingKeys: this.mappingKeys, - referenceDeclarations: this.referenceDeclarations, - storageAllocations: this.storageAllocations, - }; - debug("about to decode %s", variable.definition.name); - const val = await decode(variable.definition, variable.pointer, info, this.web3, this.contractAddress); + const decodedVariable = await this.decodeVariable(variable, block); debug("decoded"); - result.variables[variable.definition.name] = { - name: variable.definition.name, - type: DefinitionUtils.typeClass(variable.definition), - value: val - }; + result.variables[variable.definition.name] = decodedVariable; debug("var %O", result.variables[variable.definition.name]); } @@ -197,17 +226,6 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { public async variable(nameOrId: string | number, block: BlockType = "latest"): Promise { - const info: EvmInfo = { - state: { - stack: [], - storage: {}, - memory: new Uint8Array(0) - }, - mappingKeys: this.mappingKeys, - referenceDeclarations: this.referenceDeclarations, - storageAllocations: this.storageAllocations, - }; - let variable: StorageMemberAllocation; if(typeof nameOrId === "number") { @@ -222,15 +240,7 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { return undefined; } - debug("about to decode %o", nameOrId); - const value = await decode(variable.definition, variable.pointer, info, this.web3, this.contractAddress); - debug("decoded"); - - return { - name: variable.definition.name, - type: DefinitionUtils.typeClass(variable.definition), - value - }; + return await this.decodeVariable(variable, block); } //EXAMPLE: to watch a.b.c[d][e], use watchMappingKey("a", "b", "c", d, e) @@ -415,8 +425,8 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { index = rawIndex; break; case "address": - index = Web3.utils.toChecksumAddress(rawIndex); - break; + index = Web3.utils.toChecksumAddress(rawIndex); + break; case "int": case "uint": if(rawIndex instanceof BN) { diff --git a/packages/truffle-decoder/lib/interface/index.ts b/packages/truffle-decoder/lib/interface/index.ts index af768b57e0c..17a3b92693a 100644 --- a/packages/truffle-decoder/lib/interface/index.ts +++ b/packages/truffle-decoder/lib/interface/index.ts @@ -5,6 +5,7 @@ import decode from "../decode"; import TruffleDecoder from "./contract-decoder"; import { ContractObject } from "truffle-contract-schema/spec"; import { Provider } from "web3/providers"; +import { DecoderRequest } from "../types/request"; export { getStorageAllocations, storageSize } from "../allocate/storage"; export { getCalldataAllocations } from "../allocate/calldata"; @@ -16,6 +17,6 @@ export function forContract(contract: ContractObject, relevantContracts: Contrac return new TruffleDecoder(contract, relevantContracts, provider, address); } -export async function forEvmState(definition: AstDefinition, pointer: DataPointer, info: EvmInfo): Promise { - return await decode(definition, pointer, info); +export function* forEvmState(definition: AstDefinition, pointer: DataPointer, info: EvmInfo): IterableIterator { + return yield* decode(definition, pointer, info); } diff --git a/packages/truffle-decoder/lib/read/index.ts b/packages/truffle-decoder/lib/read/index.ts index 3a65bd1c84a..5123e374558 100644 --- a/packages/truffle-decoder/lib/read/index.ts +++ b/packages/truffle-decoder/lib/read/index.ts @@ -4,13 +4,13 @@ import * as stack from "./stack"; import * as constant from "./constant"; import * as Pointer from "../types/pointer"; import { EvmState } from "../types/evm"; -import Web3 from "web3"; +import { DecoderRequest } from "../types/request"; -export default async function read(pointer: Pointer.DataPointer, state: EvmState, web3?: Web3, contractAddress?: string): Promise { +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 await storage.readRange(state.storage, pointer.storage, web3, contractAddress); + 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) { diff --git a/packages/truffle-decoder/lib/read/storage.ts b/packages/truffle-decoder/lib/read/storage.ts index 77734586ae0..470d88cb0cb 100644 --- a/packages/truffle-decoder/lib/read/storage.ts +++ b/packages/truffle-decoder/lib/read/storage.ts @@ -4,8 +4,8 @@ const debug = debugModule("decoder:read:storage"); import * as DecodeUtils from "truffle-decode-utils"; import { Slot, Range } from "../types/storage"; import { WordMapping } from "../types/evm"; +import { DecoderRequest } from "../types/request"; import BN from "bn.js"; -import Web3 from "web3"; /** * convert a slot to a word corresponding to actual storage address @@ -59,29 +59,25 @@ export function slotAddressPrintout(slot: Slot): string { * @param slot - see slotAddress() code to understand how these work * @param offset - for array, offset from the keccak determined location */ -export async function read(storage: WordMapping, slot: Slot, web3?: Web3, contractAddress?: string): Promise { +export function* read(storage: WordMapping, slot: Slot): IterableIterator { debug("Slot printout: %s", slotAddressPrintout(slot)); - const address = slotAddress(slot); + const address: BN = slotAddress(slot); // debug("reading slot: %o", DecodeUtils.toHexString(address)); const hexAddress = DecodeUtils.Conversion.toHexString(address, DecodeUtils.EVM.WORD_SIZE); let word = storage[hexAddress]; - if (word === undefined && web3 && contractAddress) { - // fallback - word = DecodeUtils.Conversion.toBytes(await web3.eth.getStorageAt(contractAddress, address), DecodeUtils.EVM.WORD_SIZE); - } - - //if not found, it's 0 - //NOTE: really this shouldn't be a fallback like this but rather inside the above cases; - //however that would require a reorganization, it'll wait for fullState/contextSelector + //if we can't find the word in the map, we place a request to the invoker to supply it + //(contract-decoder will look it up from the blockchain, while the debugger will just + //say 0) if(word === undefined) { - word = new Uint8Array(DecodeUtils.EVM.WORD_SIZE); - word.fill(0); + word = yield { + requesting: "storage", + slot: address + }; } - // debug("word %o", word); return word; } @@ -101,7 +97,7 @@ export async function read(storage: WordMapping, slot: Slot, web3?: Web3, contra * @param to - location (see ^). inclusive. * @param length - instead of `to`, number of bytes after `from` */ -export async function readRange(storage: WordMapping, range: Range, web3?: Web3, contractAddress?: string): Promise { +export function* readRange(storage: WordMapping, range: Range): IterableIterator { // debug("readRange %o", range); let { from, to, length } = range; @@ -144,7 +140,7 @@ export async function readRange(storage: WordMapping, range: Range, web3?: Web3, for (let i = 0; i < totalWords; i++) { let offset = from.slot.offset.addn(i); - const word = await read(storage, { ...from.slot, offset }, web3, contractAddress); + const word = yield* read(storage, { ...from.slot, offset }); if (typeof word !== "undefined") { data.set(word, i * DecodeUtils.EVM.WORD_SIZE); } diff --git a/packages/truffle-decoder/lib/types/request.ts b/packages/truffle-decoder/lib/types/request.ts new file mode 100644 index 00000000000..e72d9f0f9e7 --- /dev/null +++ b/packages/truffle-decoder/lib/types/request.ts @@ -0,0 +1,6 @@ +import BN from "bn.js"; + +export interface DecoderRequest { + requesting: "storage"; + slot?: BN; //will add more fields as needed +} diff --git a/packages/truffle-decoder/package.json b/packages/truffle-decoder/package.json index 182eae0e6aa..63488c3ae73 100644 --- a/packages/truffle-decoder/package.json +++ b/packages/truffle-decoder/package.json @@ -33,7 +33,6 @@ "@types/lodash.clonedeep": "^4.5.4", "@types/lodash.isequal": "^4.5.4", "@types/lodash.merge": "^4.6.4", - "@types/lodash.range": "^3.2.5", "@types/web3": "^1.0.5", "json-schema-to-typescript": "^5.5.0", "truffle": "^5.0.10", @@ -48,7 +47,6 @@ "lodash.clonedeep": "^4.5.0", "lodash.isequal": "^4.5.0", "lodash.merge": "^4.6.1", - "lodash.range": "^3.2.0", "truffle-decode-utils": "^1.0.7", "web3": "1.0.0-beta.37" }, diff --git a/yarn.lock b/yarn.lock index cd209b4d0cb..67cb7c1d37e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -825,12 +825,6 @@ dependencies: "@types/lodash" "*" -"@types/lodash.range@^3.2.5": - version "3.2.6" - resolved "https://registry.yarnpkg.com/@types/lodash.range/-/lodash.range-3.2.6.tgz#82e0afe2cbc77698a57a21b9f14cfd22cf547e9f" - dependencies: - "@types/lodash" "*" - "@types/lodash@*", "@types/lodash@^4.14.116": version "4.14.122" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.122.tgz#3e31394c38cf1e5949fb54c1192cbc406f152c6c" @@ -3364,7 +3358,6 @@ copy-webpack-plugin@^4.0.1: core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.3, core-js@^2.5.7: version "2.6.5" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895" - integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -8011,10 +8004,6 @@ lodash.pickby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff" -lodash.range@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash.range/-/lodash.range-3.2.0.tgz#f461e588f66683f7eadeade513e38a69a565a15d" - lodash.reduce@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" From bc64f92c085f08b6aefc4ef7a1de717bfdd3c694 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Mon, 8 Apr 2019 22:06:26 -0400 Subject: [PATCH 2/7] Update mapping tests to use Maps --- .../truffle-debugger/test/data/decoding.js | 31 +++++-------------- .../truffle-debugger/test/data/helpers.js | 14 ++++++++- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/packages/truffle-debugger/test/data/decoding.js b/packages/truffle-debugger/test/data/decoding.js index 6e7f945ecc3..f337d891d3b 100644 --- a/packages/truffle-debugger/test/data/decoding.js +++ b/packages/truffle-debugger/test/data/decoding.js @@ -53,12 +53,7 @@ const mappingFixtures = [ from: "uint256", to: "uint256" }, - value: { - ...Object.assign( - {}, - ...generateArray(5).map((value, idx) => ({ [idx]: value })) - ) - } + value: new Map(generateArray(5).map((value, idx) => [idx, value])) }, { name: "numberedStrings", @@ -66,14 +61,9 @@ const mappingFixtures = [ from: "uint256", to: "string" }, - value: { - ...Object.assign( - {}, - ...generateArray(7).map((value, idx) => ({ - [value]: faker.lorem.slug(idx) - })) - ) - } + value: new Map( + generateArray(7).map((value, idx) => [value, faker.lorem.slug(idx)]) + ) }, { name: "stringsToStrings", @@ -81,14 +71,9 @@ const mappingFixtures = [ from: "string", to: "string" }, - value: { - ...Object.assign( - {}, - ...[0, 1, 2, 3, 4].map(idx => ({ - [faker.lorem.slug(idx)]: faker.lorem.slug(idx) - })) - ) - } + value: new Map( + [0, 1, 2, 3, 4].map(idx => [faker.lorem.slug(idx), faker.lorem.slug(idx)]) + ) } ]; @@ -146,7 +131,7 @@ contract ${contractName} { function run() public { ${fixtures .map(({ name, type: { from }, value }) => - Object.entries(value) + Array.from(value.entries()) .map( ([k, v]) => from === "string" diff --git a/packages/truffle-debugger/test/data/helpers.js b/packages/truffle-debugger/test/data/helpers.js index cb5ac677a6b..b3682f2d465 100644 --- a/packages/truffle-debugger/test/data/helpers.js +++ b/packages/truffle-debugger/test/data/helpers.js @@ -32,7 +32,19 @@ function generateTests(fixtures) { for (let { name, value: expected } of fixtures) { it(`correctly decodes ${name}`, async () => { const response = await this.decode(name); - assert.deepEqual(response, expected); + if (expected instanceof Map) { + assert.instanceOf(response, Map); + assert.sameDeepMembers( + Array.from(response.keys()), + Array.from(expected.keys()) + ); + for (let key of expected.keys()) { + //no mappings in this test are nested so this will do fine + assert.deepEqual(response[key], expected[key]); + } + } else { + assert.deepEqual(response, expected); + } }); } } From 174f3bdff25595b4c96c9f453ea8d9d7a03f8788 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Mon, 8 Apr 2019 22:17:31 -0400 Subject: [PATCH 3/7] Use DebugUtils.nativize in decoding tests --- .../truffle-debugger/test/data/helpers.js | 39 +------------------ 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/packages/truffle-debugger/test/data/helpers.js b/packages/truffle-debugger/test/data/helpers.js index b3682f2d465..bacd164ba2d 100644 --- a/packages/truffle-debugger/test/data/helpers.js +++ b/packages/truffle-debugger/test/data/helpers.js @@ -4,9 +4,9 @@ const debug = debugModule("test:data:decode"); import Ganache from "ganache-core"; import { assert } from "chai"; import changeCase from "change-case"; -import BN from "bn.js"; import { prepareContracts } from "test/helpers"; +import DebugUtils from "truffle-debug-utils"; import Debugger from "lib/debugger"; @@ -33,7 +33,6 @@ function generateTests(fixtures) { it(`correctly decodes ${name}`, async () => { const response = await this.decode(name); if (expected instanceof Map) { - assert.instanceOf(response, Map); assert.sameDeepMembers( Array.from(response.keys()), Array.from(expected.keys()) @@ -99,41 +98,7 @@ async function prepareDebugger(testName, sources) { async function decode(name) { let result = await this.session.variable(name); - if (Array.isArray(result)) { - result = result.map(element => { - if (BN.isBN(element)) { - // We're assuming these tests have small numbers - return element.toNumber(); - } else if (typeof element.toString === "function") { - return element.toString(); - } else { - return element; - } - }); - } else if (typeof result === "object") { - switch (result.type) { - case "mapping": { - result = Object.assign( - {}, - ...Object.entries(result.members).map(([key, value]) => { - if (BN.isBN(value)) { - // We're assuming these tests have small numbers - value = value.toNumber(); - } else if (typeof value.toString === "function") { - value = value.toString(); - } - - return { - [key]: value - }; - }) - ); - break; - } - } - } - - return result; + return DebugUtils.nativize(result); } export function describeDecoding(testName, fixtures, selector, generateSource) { From e9b42731786f4d0cda069e00192c303d47c240ee Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Mon, 8 Apr 2019 22:18:54 -0400 Subject: [PATCH 4/7] Revert "Update mapping tests to use Maps" This reverts commit bc64f92c085f08b6aefc4ef7a1de717bfdd3c694. --- .../truffle-debugger/test/data/decoding.js | 31 ++++++++++++++----- .../truffle-debugger/test/data/helpers.js | 13 +------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/truffle-debugger/test/data/decoding.js b/packages/truffle-debugger/test/data/decoding.js index f337d891d3b..6e7f945ecc3 100644 --- a/packages/truffle-debugger/test/data/decoding.js +++ b/packages/truffle-debugger/test/data/decoding.js @@ -53,7 +53,12 @@ const mappingFixtures = [ from: "uint256", to: "uint256" }, - value: new Map(generateArray(5).map((value, idx) => [idx, value])) + value: { + ...Object.assign( + {}, + ...generateArray(5).map((value, idx) => ({ [idx]: value })) + ) + } }, { name: "numberedStrings", @@ -61,9 +66,14 @@ const mappingFixtures = [ from: "uint256", to: "string" }, - value: new Map( - generateArray(7).map((value, idx) => [value, faker.lorem.slug(idx)]) - ) + value: { + ...Object.assign( + {}, + ...generateArray(7).map((value, idx) => ({ + [value]: faker.lorem.slug(idx) + })) + ) + } }, { name: "stringsToStrings", @@ -71,9 +81,14 @@ const mappingFixtures = [ from: "string", to: "string" }, - value: new Map( - [0, 1, 2, 3, 4].map(idx => [faker.lorem.slug(idx), faker.lorem.slug(idx)]) - ) + value: { + ...Object.assign( + {}, + ...[0, 1, 2, 3, 4].map(idx => ({ + [faker.lorem.slug(idx)]: faker.lorem.slug(idx) + })) + ) + } } ]; @@ -131,7 +146,7 @@ contract ${contractName} { function run() public { ${fixtures .map(({ name, type: { from }, value }) => - Array.from(value.entries()) + Object.entries(value) .map( ([k, v]) => from === "string" diff --git a/packages/truffle-debugger/test/data/helpers.js b/packages/truffle-debugger/test/data/helpers.js index bacd164ba2d..713875479b9 100644 --- a/packages/truffle-debugger/test/data/helpers.js +++ b/packages/truffle-debugger/test/data/helpers.js @@ -32,18 +32,7 @@ function generateTests(fixtures) { for (let { name, value: expected } of fixtures) { it(`correctly decodes ${name}`, async () => { const response = await this.decode(name); - if (expected instanceof Map) { - assert.sameDeepMembers( - Array.from(response.keys()), - Array.from(expected.keys()) - ); - for (let key of expected.keys()) { - //no mappings in this test are nested so this will do fine - assert.deepEqual(response[key], expected[key]); - } - } else { - assert.deepEqual(response, expected); - } + assert.deepEqual(response, expected); }); } } From d30dd5b7d74b4eeda2cc676ee2c622fdab1e5b04 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Mon, 8 Apr 2019 22:31:25 -0400 Subject: [PATCH 5/7] Fix an error in cleanContainers --- packages/truffle-decode-utils/src/conversion.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/truffle-decode-utils/src/conversion.ts b/packages/truffle-decode-utils/src/conversion.ts index d1f28526c56..b88e4f71a2f 100644 --- a/packages/truffle-decode-utils/src/conversion.ts +++ b/packages/truffle-decode-utils/src/conversion.ts @@ -202,7 +202,8 @@ export namespace Conversion { type !== "mapping" && keyType === undefined && typeof members === "object" && Object.values(members).every( - (member: any) => member.name && member.type && member.value); + (member: any) => member.name && member.type && + member.value !== undefined); // converts integer mapping keys to BN // converts bool mapping keys to boolean From 4da769c28ea6bc051e16941f1f812d5138e30d2b Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 9 Apr 2019 20:22:47 -0400 Subject: [PATCH 6/7] Remove decodeAll; mark _runSaga as private. --- .../truffle-debugger/lib/data/sagas/index.js | 10 -------- .../truffle-debugger/lib/session/index.js | 24 +++++++++++++++---- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/truffle-debugger/lib/data/sagas/index.js b/packages/truffle-debugger/lib/data/sagas/index.js index 1f6f4e86f08..fc553490ad8 100644 --- a/packages/truffle-debugger/lib/data/sagas/index.js +++ b/packages/truffle-debugger/lib/data/sagas/index.js @@ -81,16 +81,6 @@ export function* decode(definition, ref) { return DecodeUtils.Conversion.cleanContainers(result.value); } -export function* decodeAll() { - let definitions = yield select(data.current.identifiers.definitions); - let refs = yield select(data.current.identifiers.refs); - let decoded = {}; - for (let [identifier, ref] of Object.entries(refs)) { - decoded[identifier] = yield* decode(definitions[identifier], ref); - } - return decoded; -} - function* variablesAndMappingsSaga() { let node = yield select(data.current.node); let scopes = yield select(data.views.scopes.inlined); diff --git a/packages/truffle-debugger/lib/session/index.js b/packages/truffle-debugger/lib/session/index.js index 6317d828caf..6df8afce230 100644 --- a/packages/truffle-debugger/lib/session/index.js +++ b/packages/truffle-debugger/lib/session/index.js @@ -6,7 +6,7 @@ import configureStore from "lib/store"; import * as controller from "lib/controller/actions"; import * as actions from "./actions"; import data from "lib/data/selectors"; -import { decode, decodeAll } from "lib/data/sagas"; +import { decode } from "lib/data/sagas"; import controllerSelector from "lib/controller/selectors"; import rootSaga from "./sagas"; @@ -148,7 +148,13 @@ export default class Session { return true; } - async runSaga(saga, ...args) { + /** + * @private + * Allows running any saga -- for internal use only! + * Using this could seriously screw up the debugger state if you + * don't know what you're doing! + */ + async _runSaga(saga, ...args) { return await this._sagaMiddleware.run(saga, ...args).toPromise(); } @@ -235,10 +241,20 @@ export default class Session { const definitions = this.view(data.current.identifiers.definitions); const refs = this.view(data.current.identifiers.refs); - return await this.runSaga(decode, definitions[name], refs[name]); + return await this._runSaga(decode, definitions[name], refs[name]); } async variables() { - return await this.runSaga(decodeAll); + let definitions = this.view(data.current.identifiers.definitions); + let refs = this.view(data.current.identifiers.refs); + let decoded = {}; + for (let [identifier, ref] of Object.entries(refs)) { + decoded[identifier] = await this._runSaga( + decode, + definitions[identifier], + ref + ); + } + return decoded; } } From 65967b4bb67f92a2144e4ae7a2cc23ebdda8d2b8 Mon Sep 17 00:00:00 2001 From: Harry Altman Date: Tue, 9 Apr 2019 22:05:46 -0400 Subject: [PATCH 7/7] Improve request typing --- .../truffle-debugger/lib/data/sagas/index.js | 4 +++- .../lib/interface/contract-decoder.ts | 21 +++++++++---------- packages/truffle-decoder/lib/read/storage.ts | 2 +- packages/truffle-decoder/lib/types/request.ts | 12 ++++++++--- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/truffle-debugger/lib/data/sagas/index.js b/packages/truffle-debugger/lib/data/sagas/index.js index fc553490ad8..ce3cee18df4 100644 --- a/packages/truffle-debugger/lib/data/sagas/index.js +++ b/packages/truffle-debugger/lib/data/sagas/index.js @@ -65,13 +65,15 @@ export function* decode(definition, ref) { while (!result.done) { let request = result.value; let response; - switch (request.requesting) { + switch (request.type) { //yes, this is a little silly right now case "storage": //the debugger supplies all storage it knows at the beginning. //any storage it does not know is presumed to be zero. response = ZERO_WORD; break; + default: + debug("unrecognized request type!"); } result = decoder.next(response); } diff --git a/packages/truffle-decoder/lib/interface/contract-decoder.ts b/packages/truffle-decoder/lib/interface/contract-decoder.ts index a26a95729c3..b27d4fd5edb 100644 --- a/packages/truffle-decoder/lib/interface/contract-decoder.ts +++ b/packages/truffle-decoder/lib/interface/contract-decoder.ts @@ -12,7 +12,7 @@ import * as storage from "../allocate/storage"; import { StoragePointer, isStoragePointer } from "../types/pointer"; import { StorageAllocations, StorageMemberAllocations, StorageMemberAllocation } from "../types/allocation"; import { Slot, isWordsLength } from "../types/storage"; -import { DecoderRequest } from "../types/request"; +import { DecoderRequest, isStorageRequest } from "../types/request"; import decode from "../decode"; import { Definition as DefinitionUtils, EVM, AstDefinition, AstReferences } from "truffle-decode-utils"; import { BlockType, Transaction } from "web3/eth/types"; @@ -178,17 +178,16 @@ export default class TruffleContractDecoder extends AsyncEventEmitter { while(!result.done) { let request = (result.value); let response: any - switch(request.requesting) { - //yes, this is a little silly right now - case "storage": - response = DecodeUtils.Conversion.toBytes( - await this.web3.eth.getStorageAt( - this.contractAddress, - request.slot, - block), - DecodeUtils.EVM.WORD_SIZE); - break; + if(isStorageRequest(request)) { + response = DecodeUtils.Conversion.toBytes( + await this.web3.eth.getStorageAt( + this.contractAddress, + request.slot, + block), + DecodeUtils.EVM.WORD_SIZE); } + //note: one of the above conditionals *must* be true by the type system. + //yes, right now there's only one such conditional. result = decoder.next(response); } //at this point, result.value holds the final value diff --git a/packages/truffle-decoder/lib/read/storage.ts b/packages/truffle-decoder/lib/read/storage.ts index 470d88cb0cb..f6c266b4477 100644 --- a/packages/truffle-decoder/lib/read/storage.ts +++ b/packages/truffle-decoder/lib/read/storage.ts @@ -73,7 +73,7 @@ export function* read(storage: WordMapping, slot: Slot): IterableIterator