UNPKG

@truffle/codec

Version:

Library for encoding and decoding smart contract data

875 lines 37.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.decodeRevert = exports.decodeReturndata = exports.decodeEvent = exports.decodeCalldata = exports.decodeVariable = void 0; const debug_1 = __importDefault(require("debug")); const debug = (0, debug_1.default)("codec:core"); const Ast = __importStar(require("./ast")); const AbiData = __importStar(require("./abi-data")); const Topic = __importStar(require("./topic")); const Evm = __importStar(require("./evm")); const Contexts = __importStar(require("./contexts")); const abify_1 = require("./abify"); const Conversion = __importStar(require("./conversion")); const errors_1 = require("./errors"); const read_1 = __importDefault(require("./read")); const decode_1 = __importDefault(require("./decode")); const web3_utils_1 = __importDefault(require("web3-utils")); /** * @Category Decoding */ function* decodeVariable(definition, pointer, info, compilationId) { let compiler = info.currentContext.compiler; let dataType = Ast.Import.definitionToType(definition, compilationId, compiler); return yield* (0, decode_1.default)(dataType, pointer, info); //no need to pass an offset } exports.decodeVariable = decodeVariable; /** * @Category Decoding */ function* decodeCalldata(info, isConstructor, //ignored if context! trust context instead if have strictAbiMode //used for selector-based decoding. has two effects: //1. sets the strictAbiMode option when calling decode(), causing it to throw //if it encounters a problem rather than returning an ErrorResult; //2. performs a re-encoding check at the end (like decodeEvevent and decodeReturndata) //and throws if it fails ) { const context = info.currentContext; if (context === null) { //if we don't know the contract ID, we can't decode if (isConstructor) { return { kind: "create", decodingMode: "full", bytecode: Conversion.toHexString(info.state.calldata), interpretations: {} }; } else { return { kind: "unknown", decodingMode: "full", data: Conversion.toHexString(info.state.calldata), interpretations: {} }; } } const contextHash = context.context; const contractType = Contexts.Import.contextToType(context); isConstructor = context.isConstructor; const allocations = info.allocations.calldata; let allocation; let selector; //first: is this a creation call? if (isConstructor) { allocation = (allocations.constructorAllocations[contextHash] || { input: undefined }).input; } else { //skipping any error-handling on this read, as a calldata read can't throw anyway let rawSelector = yield* (0, read_1.default)({ location: "calldata", start: 0, length: Evm.Utils.SELECTOR_SIZE }, info.state); selector = Conversion.toHexString(rawSelector); allocation = ((allocations.functionAllocations[contextHash] || {})[selector] || { input: undefined }).input; } if (allocation === undefined) { let abiEntry = null; if (info.state.calldata.length === 0) { //to hell with reads, let's just be direct abiEntry = context.fallbackAbi.receive || context.fallbackAbi.fallback; } else { abiEntry = context.fallbackAbi.fallback; } return { kind: "message", class: contractType, abi: abiEntry, data: Conversion.toHexString(info.state.calldata), decodingMode: "full", interpretations: {} }; } let decodingMode = allocation.allocationMode; //starts out this way, degrades to ABI if necessary debug("calldata decoding mode: %s", decodingMode); //you can't map with a generator, so we have to do this map manually let decodedArguments = []; for (const argumentAllocation of allocation.arguments) { let value; let dataType = decodingMode === "full" ? argumentAllocation.type : (0, abify_1.abifyType)(argumentAllocation.type, info.userDefinedTypes); try { value = yield* (0, decode_1.default)(dataType, argumentAllocation.pointer, info, { abiPointerBase: allocation.offset, allowRetry: decodingMode === "full", strictAbiMode }); } catch (error) { if (error instanceof errors_1.StopDecodingError && error.allowRetry && decodingMode === "full") { debug("problem! retrying as ABI"); debug("error: %O", error); //if a retry happens, we've got to do several things in order to switch to ABI mode: //1. mark that we're switching to ABI mode; decodingMode = "abi"; //2. abify all previously decoded values; decodedArguments = decodedArguments.map(argumentDecoding => (Object.assign(Object.assign({}, argumentDecoding), { value: (0, abify_1.abifyResult)(argumentDecoding.value, info.userDefinedTypes) }))); //3. retry this particular decode in ABI mode. //(no try/catch on this one because we can't actually handle errors here! //not that they should be occurring) value = yield* (0, decode_1.default)((0, abify_1.abifyType)(argumentAllocation.type, info.userDefinedTypes), //type is now abified! argumentAllocation.pointer, info, { abiPointerBase: allocation.offset, strictAbiMode }); //4. the remaining parameters will then automatically be decoded in ABI mode due to (1), //so we don't need to do anything special there. } else { //we shouldn't be getting other exceptions, but if we do, we don't know //how to handle them, so uhhhh just rethrow I guess?? throw error; } } const name = argumentAllocation.name; decodedArguments.push(name //deliberate general falsiness test ? { name, value } : { value }); } //if we're in strict mode, do a re-encoding check if (strictAbiMode) { const decodedArgumentValues = decodedArguments.map(argument => argument.value); const reEncodedData = AbiData.Encode.encodeTupleAbi(decodedArgumentValues, info.allocations.abi); const selectorLength = isConstructor ? (context.binary.length - 2) / 2 //for a constructor, the bytecode acts as the "selector" : //note we have to account for the fact that it's a hex string Evm.Utils.SELECTOR_SIZE; const encodedData = info.state.calldata.subarray(selectorLength); //slice off the selector if (!Evm.Utils.equalData(reEncodedData, encodedData)) { //if not, this allocation doesn't work debug("rejected due to mismatch"); throw new errors_1.StopDecodingError({ kind: "ReEncodingMismatchError", data: encodedData, reEncodedData }); } } if (isConstructor) { return { kind: "constructor", class: contractType, arguments: decodedArguments, abi: allocation.abi, bytecode: Conversion.toHexString(info.state.calldata.slice(0, allocation.offset)), decodingMode, interpretations: {} }; } else { return { kind: "function", class: contractType, abi: allocation.abi, arguments: decodedArguments, selector, decodingMode, interpretations: {} }; } } exports.decodeCalldata = decodeCalldata; /** * @Category Decoding */ function* decodeEvent(info, address, //if null is passed, must pass currentContext in info options = {}) { const allocations = info.allocations.event; const extras = options.extras || "off"; let rawSelector; let selector; let contractAllocations; //for non-anonymous events let libraryAllocations; //similar let contractAnonymousAllocations; let libraryAnonymousAllocations; const topicsCount = info.state.eventtopics.length; //yeah, it's not great to read directly from the state like this (bypassing read), but what are you gonna do? if (allocations[topicsCount]) { if (topicsCount > 0) { rawSelector = yield* (0, read_1.default)({ location: "eventtopic", topic: 0 }, info.state); selector = Conversion.toHexString(rawSelector); if (allocations[topicsCount].bySelector[selector]) { ({ contract: contractAllocations, library: libraryAllocations } = allocations[topicsCount].bySelector[selector]); } else { debug("no allocations for that selector!"); contractAllocations = {}; libraryAllocations = {}; } } else { //if we don't have a selector, it means we don't have any non-anonymous events contractAllocations = {}; libraryAllocations = {}; } //now: let's get our allocations for anonymous events ({ contract: contractAnonymousAllocations, library: libraryAnonymousAllocations } = allocations[topicsCount].anonymous); } else { //if there's not even an allocation for the topics count, we can't //decode; we could do this the honest way of setting all four allocation //objects to {}, but let's just short circuit debug("no allocations for that topic count!"); return []; } let contractContext; if (address !== null) { //now: what contract are we (probably) dealing with? let's get its code to find out const codeBytes = yield { type: "code", address }; const codeAsHex = Conversion.toHexString(codeBytes); contractContext = Contexts.Utils.findContext(info.contexts, codeAsHex); } else { contractContext = info.currentContext; } let possibleContractAllocations; //excludes anonymous events let possibleContractAnonymousAllocations; let possibleExtraAllocations; //excludes anonymous events let possibleExtraAnonymousAllocations; const emittingContextHash = (contractContext || { context: undefined }) .context; if (emittingContextHash) { //if we found the contract, maybe it's from that contract const contractAllocation = contractAllocations[emittingContextHash]; const contractAnonymousAllocation = contractAnonymousAllocations[emittingContextHash]; possibleContractAllocations = contractAllocation || []; possibleContractAnonymousAllocations = contractAnonymousAllocation || []; //also, we need to set up the extras (everything that's from a //non-library contract but *not* this one) possibleExtraAllocations = [].concat(...Object.entries(contractAllocations) .filter(([key, _]) => key !== emittingContextHash) .map(([_, value]) => value)); possibleExtraAnonymousAllocations = [].concat(...Object.entries(contractAnonymousAllocations) .filter(([key, _]) => key !== emittingContextHash) .map(([_, value]) => value)); } else { //if we couldn't determine the contract, well, we have to assume it's from a library debug("couldn't find context"); possibleContractAllocations = []; possibleContractAnonymousAllocations = []; //or it's an extra, which could be any of the contracts possibleExtraAllocations = [].concat(...Object.values(contractAllocations)); possibleExtraAnonymousAllocations = [].concat(...Object.values(contractAnonymousAllocations)); } //now we get all the library allocations! const possibleLibraryAllocations = [].concat(...Object.values(libraryAllocations)); const possibleLibraryAnonymousAllocations = [].concat(...Object.values(libraryAnonymousAllocations)); //now we put it all together! const possibleAllocations = possibleContractAllocations.concat(possibleLibraryAllocations); const possibleAnonymousAllocations = possibleContractAnonymousAllocations.concat(possibleLibraryAnonymousAllocations); const possibleAllocationsTotalMinusExtras = possibleAllocations.concat(possibleAnonymousAllocations); //...and also there's the extras const possibleExtraAllocationsTotal = possibleExtraAllocations.concat(possibleExtraAnonymousAllocations); const possibleAllocationsTotal = possibleAllocationsTotalMinusExtras.concat([null], //HACK: add sentinel value before the extras possibleExtraAllocationsTotal); //whew! let decodings = []; allocationAttempts: for (const allocation of possibleAllocationsTotal) { debug("trying allocation: %O", allocation); //first: check for our sentinel value for extras (yeah, kind of HACKy) if (allocation === null) { switch (extras) { case "on": continue allocationAttempts; //ignore the sentinel and continue case "off": break allocationAttempts; //don't include extras; stop here case "necessary": //stop on the sentinel and exclude extras *unless* there are no decodings yet if (decodings.length > 0) { break allocationAttempts; } else { continue allocationAttempts; } } } //second: do a name check so we can skip decoding if name is wrong //(this will likely be a more detailed check in the future) if (options.name !== undefined && allocation.abi.name !== options.name) { continue; } //now: the main part! let decodingMode = allocation.allocationMode; //starts out here; degrades to abi if necessary const contextHash = allocation.contextHash; const attemptContext = info.contexts[contextHash]; const emittingContractType = Contexts.Import.contextToType(attemptContext); const contractType = allocation.definedIn; //you can't map with a generator, so we have to do this map manually let decodedArguments = []; for (const argumentAllocation of allocation.arguments) { let value; //if in full mode, use the allocation's listed data type. //if in ABI mode, abify it before use. let dataType = decodingMode === "full" ? argumentAllocation.type : (0, abify_1.abifyType)(argumentAllocation.type, info.userDefinedTypes); try { value = yield* (0, decode_1.default)(dataType, argumentAllocation.pointer, info, { strictAbiMode: true, allowRetry: decodingMode === "full" //this option is unnecessary but including for clarity }); } catch (error) { if (error instanceof errors_1.StopDecodingError && error.allowRetry && decodingMode === "full") { //if a retry happens, we've got to do several things in order to switch to ABI mode: //1. mark that we're switching to ABI mode; decodingMode = "abi"; //2. abify all previously decoded values; decodedArguments = decodedArguments.map(argumentDecoding => (Object.assign(Object.assign({}, argumentDecoding), { value: (0, abify_1.abifyResult)(argumentDecoding.value, info.userDefinedTypes) }))); //3. retry this particular decode in ABI mode. try { value = yield* (0, decode_1.default)((0, abify_1.abifyType)(argumentAllocation.type, info.userDefinedTypes), //type is now abified! argumentAllocation.pointer, info, { strictAbiMode: true //turns on STRICT MODE to cause more errors to be thrown //retries no longer allowed, not that this has an effect }); } catch (_) { //if an error occurred on the retry, this isn't a valid decoding! debug("rejected due to exception on retry"); continue allocationAttempts; } //4. the remaining parameters will then automatically be decoded in ABI mode due to (1), //so we don't need to do anything special there. } else { //if any other sort of error occurred, this isn't a valid decoding! debug("rejected due to exception on first try: %O", error); continue allocationAttempts; } } const name = argumentAllocation.name; const indexed = argumentAllocation.pointer.location === "eventtopic"; decodedArguments.push(name //deliberate general falsiness test ? { name, indexed, value } : { indexed, value }); } if (!options.disableChecks) { //OK, so, having decoded the result, the question is: does it reencode to the original? //NOTE: we skip this check if disableChecks is passed! (it shouldn't be passed :P ) //first, we have to filter out the indexed arguments, and also get rid of the name information const nonIndexedValues = decodedArguments .filter(argument => !argument.indexed) .map(argument => argument.value); //now, we can encode! const reEncodedData = AbiData.Encode.encodeTupleAbi(nonIndexedValues, info.allocations.abi); const encodedData = info.state.eventdata; //again, not great to read this directly, but oh well //are they equal? if (!Evm.Utils.equalData(reEncodedData, encodedData)) { //if not, this allocation doesn't work debug("rejected due to [non-indexed] mismatch"); continue; } } //one last check -- let's check that the indexed arguments match up, too //(we won't skip this even if disableChecks was passed) const indexedValues = decodedArguments .filter(argument => argument.indexed) .map(argument => argument.value); const reEncodedTopics = indexedValues.map(Topic.Encode.encodeTopic); const encodedTopics = info.state.eventtopics; //now: do *these* match? const selectorAdjustment = allocation.anonymous ? 0 : 1; for (let i = 0; i < reEncodedTopics.length; i++) { if (!Evm.Utils.equalData(reEncodedTopics[i], encodedTopics[i + selectorAdjustment])) { debug("rejected due to indexed mismatch"); continue allocationAttempts; } } //if we've made it here, the allocation works! hooray! debug("allocation accepted!"); let decoding; if (allocation.abi.anonymous) { decoding = { kind: "anonymous", definedIn: contractType, class: emittingContractType, abi: allocation.abi, arguments: decodedArguments, decodingMode, interpretations: {} }; } else { decoding = { kind: "event", definedIn: contractType, class: emittingContractType, abi: allocation.abi, arguments: decodedArguments, selector, decodingMode, interpretations: {} }; } decodings.push(decoding); //if we've made this far (so this allocation works), and we were passed an //ID, and it matches this ID, bail out & return this as the *only* decoding if (options.id && allocation.id === options.id) { return [decoding]; } } return decodings; } exports.decodeEvent = decodeEvent; const errorSelector = Conversion.toBytes(web3_utils_1.default.soliditySha3({ type: "string", value: "Error(string)" })).subarray(0, Evm.Utils.SELECTOR_SIZE); const panicSelector = Conversion.toBytes(web3_utils_1.default.soliditySha3({ type: "string", value: "Panic(uint256)" })).subarray(0, Evm.Utils.SELECTOR_SIZE); const defaultRevertAllocations = [ { kind: "revert", allocationMode: "full", selector: errorSelector, abi: { name: "Error", type: "error", inputs: [ { name: "", type: "string", internalType: "string" } ] }, definedIn: null, arguments: [ { name: "", pointer: { location: "returndata", start: errorSelector.length, length: Evm.Utils.WORD_SIZE }, type: { typeClass: "string", typeHint: "string" } } ] }, { kind: "revert", allocationMode: "full", selector: panicSelector, abi: { name: "Panic", type: "error", inputs: [ { name: "", type: "uint256", internalType: "uint256" } ] }, definedIn: null, arguments: [ { name: "", pointer: { location: "returndata", start: panicSelector.length, length: Evm.Utils.WORD_SIZE }, type: { typeClass: "uint", bits: Evm.Utils.WORD_SIZE * 8, typeHint: "uint256" } } ] } ]; const defaultEmptyAllocations = [ { kind: "failure", allocationMode: "full", selector: new Uint8Array(), arguments: [] }, { kind: "selfdestruct", allocationMode: "full", selector: new Uint8Array(), arguments: [] } ]; /** * If there are multiple possibilities, they're always returned in * the order: return, revert, returnmessage, failure, empty, bytecode, unknownbytecode * Moreover, within "revert", builtin ones are put above custom ones * @Category Decoding */ function* decodeReturndata(info, successAllocation, //null here must be explicit status, //you can pass this to indicate that you know the status, id //useful when status = false ) { let possibleAllocations; const selector = Conversion.toHexString(info.state.returndata.slice(0, 4)); const contextHash = (info.currentContext || { context: "" }).context; //HACK: "" is used to represent no context const customRevertAllocations = ((info.allocations.returndata || { [contextHash]: {} })[contextHash] || { [selector]: [] })[selector] || []; if (successAllocation === null) { possibleAllocations = [ ...defaultRevertAllocations, ...customRevertAllocations, ...defaultEmptyAllocations ]; } else { switch (successAllocation.kind) { case "return": possibleAllocations = [ successAllocation, ...defaultRevertAllocations, ...customRevertAllocations, ...defaultEmptyAllocations ]; break; case "bytecode": possibleAllocations = [ ...defaultRevertAllocations, ...customRevertAllocations, ...defaultEmptyAllocations, successAllocation ]; break; case "returnmessage": possibleAllocations = [ ...defaultRevertAllocations, ...customRevertAllocations, successAllocation, ...defaultEmptyAllocations ]; break; //Other cases shouldn't happen so I'm leaving them to cause errors! } } let decodings = []; allocationAttempts: for (const allocation of possibleAllocations) { debug("trying allocation: %O", allocation); //before we attempt to use this allocation, we check: does the selector match? let encodedData = info.state.returndata; //again, not great to read this directly, but oh well const encodedPrefix = encodedData.subarray(0, allocation.selector.length); if (!Evm.Utils.equalData(encodedPrefix, allocation.selector)) { continue; } encodedData = encodedData.subarray(allocation.selector.length); //slice off the selector for later //also we check, does the status match? if (status !== undefined) { const successKinds = [ "return", "selfdestruct", "bytecode", "returnmessage" ]; const failKinds = ["failure", "revert"]; if (status) { if (!successKinds.includes(allocation.kind)) { continue; } } else { if (!failKinds.includes(allocation.kind)) { continue; } } } if (allocation.kind === "bytecode") { //bytecode is special and can't really be integrated with the other cases. //so it gets its own function. const decoding = yield* decodeBytecode(info); if (decoding) { decodings.push(decoding); } continue; } if (allocation.kind === "returnmessage") { //this kind is also special, though thankfully it's easier const decoding = { kind: "returnmessage", status: true, data: Conversion.toHexString(info.state.returndata), decodingMode: allocation.allocationMode, interpretations: {} }; decodings.push(decoding); continue; } let decodingMode = allocation.allocationMode; //starts out here; degrades to abi if necessary //you can't map with a generator, so we have to do this map manually let decodedArguments = []; for (const argumentAllocation of allocation.arguments) { let value; //if in full mode, use the allocation's listed data type. //if in ABI mode, abify it before use. let dataType = decodingMode === "full" ? argumentAllocation.type : (0, abify_1.abifyType)(argumentAllocation.type, info.userDefinedTypes); //now, let's decode! try { value = yield* (0, decode_1.default)(dataType, argumentAllocation.pointer, info, { abiPointerBase: allocation.selector.length, strictAbiMode: true, allowRetry: decodingMode === "full" //this option is unnecessary but including for clarity }); debug("value on first try: %O", value); } catch (error) { if (error instanceof errors_1.StopDecodingError && error.allowRetry && decodingMode === "full") { debug("retry!"); //if a retry happens, we've got to do several things in order to switch to ABI mode: //1. mark that we're switching to ABI mode; decodingMode = "abi"; //2. abify all previously decoded values; decodedArguments = decodedArguments.map(argumentDecoding => (Object.assign(Object.assign({}, argumentDecoding), { value: (0, abify_1.abifyResult)(argumentDecoding.value, info.userDefinedTypes) }))); //3. retry this particular decode in ABI mode. try { value = yield* (0, decode_1.default)((0, abify_1.abifyType)(argumentAllocation.type, info.userDefinedTypes), //type is now abified! argumentAllocation.pointer, info, { abiPointerBase: allocation.selector.length, strictAbiMode: true //turns on STRICT MODE to cause more errors to be thrown //retries no longer allowed, not that this has an effect }); debug("value on retry: %O", value); } catch (_) { //if an error occurred on the retry, this isn't a valid decoding! debug("rejected due to exception on retry"); continue allocationAttempts; } //4. the remaining parameters will then automatically be decoded in ABI mode due to (1), //so we don't need to do anything special there. } else { //if any other sort of error occurred, this isn't a valid decoding! debug("rejected due to exception on first try: %O", error); continue allocationAttempts; } } const name = argumentAllocation.name; decodedArguments.push(name //deliberate general falsiness test ? { name, value } : { value }); } //OK, so, having decoded the result, the question is: does it reencode to the original? //first, we have to filter out the indexed arguments, and also get rid of the name information debug("decodedArguments: %O", decodedArguments); const decodedArgumentValues = decodedArguments.map(argument => argument.value); const reEncodedData = AbiData.Encode.encodeTupleAbi(decodedArgumentValues, info.allocations.abi); //are they equal? note the selector has been stripped off encodedData! if (!Evm.Utils.equalData(reEncodedData, encodedData)) { //if not, this allocation doesn't work debug("rejected due to mismatch"); continue; } //if we've made it here, the allocation works! hooray! debug("allocation accepted!"); let decoding; switch (allocation.kind) { case "return": decoding = { kind: "return", status: true, arguments: decodedArguments, decodingMode, interpretations: {} }; break; case "revert": decoding = { kind: "revert", abi: allocation.abi, definedIn: allocation.definedIn, status: false, arguments: decodedArguments, decodingMode, interpretations: {} }; break; case "selfdestruct": decoding = { kind: "selfdestruct", status: true, decodingMode, interpretations: {} }; break; case "failure": decoding = { kind: "failure", status: false, decodingMode, interpretations: {} }; break; } decodings.push(decoding); //if we've made this far (so this allocation works), and we were passed an //ID, and it matches this ID, bail out & return this as the *only* decoding if (id && allocation.kind === "revert" && allocation.id === id) { return [decoding]; } } return decodings; } exports.decodeReturndata = decodeReturndata; //note: requires the bytecode to be in returndata, not code function* decodeBytecode(info) { let decodingMode = "full"; //as always, degrade as necessary const bytecode = Conversion.toHexString(info.state.returndata); const context = Contexts.Utils.findContext(info.contexts, bytecode); if (!context) { return { kind: "unknownbytecode", status: true, decodingMode: "full", bytecode, interpretations: {} }; } const contractType = Contexts.Import.contextToType(context); //now: ignore original allocation (which we didn't even pass :) ) //and lookup allocation by context const allocation = info.allocations.calldata.constructorAllocations[context.context].output; debug("bytecode allocation: %O", allocation); //now: add immutables if applicable let immutables; if (allocation.immutables) { immutables = []; //NOTE: if we're in here, we can assume decodingMode === "full" for (const variable of allocation.immutables) { const dataType = variable.type; //we don't conditioning on decodingMode here because we know it let value; try { value = yield* (0, decode_1.default)(dataType, variable.pointer, info, { allowRetry: true, strictAbiMode: true, paddingMode: "defaultOrZero" }); } catch (error) { if (error instanceof errors_1.StopDecodingError && error.allowRetry) { //we "retry" by... not bothering with immutables :P //(but we do set the mode to ABI) decodingMode = "abi"; immutables = undefined; break; } else { //otherwise, this isn't a valid decoding I guess return null; } } immutables.push({ name: variable.name, class: variable.definedIn, value }); } } let decoding = { kind: "bytecode", status: true, decodingMode, bytecode, immutables, class: contractType, interpretations: {} }; //finally: add address if applicable if (allocation.delegatecallGuard) { decoding.address = web3_utils_1.default.toChecksumAddress(bytecode.slice(4, 4 + 2 * Evm.Utils.ADDRESS_SIZE) //4 = "0x73".length ); } return decoding; } /** * Decodes the return data from a failed call. * * @param returndata The returned data, as a Uint8Array. * @return An array of possible decodings. At the moment it's * impossible for there to be more than one. (If the call didn't actually * fail, or failed in a nonstandard way, you may get no decodings at all, though!) * * Decodings can either be decodings of revert messages, or decodings * indicating that there was no revert message. If somehow both were to be * possible, they'd go in that order, although as mentioned, there (at least * currently) isn't any way for that to occur. * @Category Decoding convenience */ function decodeRevert(returndata) { //coercing because TS doesn't know it'll finish in one go return decodeReturndata({ allocations: {}, state: { storage: {}, returndata } }, null, false).next().value; } exports.decodeRevert = decodeRevert; //# sourceMappingURL=core.js.map