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..ce3cee18df4 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,48 @@ 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.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); + } + //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); +} + 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 +307,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 +330,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 +350,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 +372,7 @@ function* variablesAndMappingsSaga() { if ( DecodeUtils.Definition.isSimpleConstant(indexConstantDefinition) ) { - indexValue = yield call(decode, keyDefinition, { + indexValue = yield* decode(keyDefinition, { definition: indexConstantDeclaration.value }); } @@ -362,7 +401,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..6df8afce230 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 } 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,16 @@ export default class Session { return true; } + /** + * @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(); + } + async interrupt() { return this.dispatch(controller.interrupt()); } @@ -219,49 +232,29 @@ 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"); + 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; } } 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-debugger/test/data/helpers.js b/packages/truffle-debugger/test/data/helpers.js index cb5ac677a6b..713875479b9 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"; @@ -87,41 +87,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) { 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 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..b27d4fd5edb 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, 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"; @@ -54,6 +56,7 @@ interface DecodedVariable { interface ContractState { name: string; balance: BN; + nonce: BN; variables: { [name: string]: DecodedVariable }; @@ -157,10 +160,50 @@ 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 + 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 + + 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 +211,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 +225,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 +239,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 +424,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..f6c266b4477 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 { + type: "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..9264486e5a8 --- /dev/null +++ b/packages/truffle-decoder/lib/types/request.ts @@ -0,0 +1,12 @@ +import BN from "bn.js"; + +export type DecoderRequest = StorageRequest; //will add more later + +export interface StorageRequest { + type: "storage"; + slot: BN; //will add more fields as needed +} + +export function isStorageRequest(request: DecoderRequest): request is StorageRequest { + return request.type === "storage"; +} diff --git a/packages/truffle-decoder/package.json b/packages/truffle-decoder/package.json index 7a6818f9211..308d0769907 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.12", @@ -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.8", "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"