UNPKG

@truffle/codec

Version:

Library for encoding and decoding smart contract data

411 lines 17.2 kB
"use strict"; /** * @protected * * @packageDocumentation */ 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.decodeAbiReferenceStatic = exports.decodeAbiReferenceByAddress = exports.decodeAbi = void 0; const debug_1 = __importDefault(require("debug")); const debug = (0, debug_1.default)("codec:abi-data:decode"); const read_1 = __importDefault(require("../../read")); const Conversion = __importStar(require("../../conversion")); const Basic = __importStar(require("../../basic")); const Bytes = __importStar(require("../../bytes")); const Format = __importStar(require("../../format")); const Evm = __importStar(require("../../evm")); const allocate_1 = require("../allocate"); const errors_1 = require("../../errors"); function* decodeAbi(dataType, pointer, info, options = {}) { if (Format.Types.isReferenceType(dataType) || dataType.typeClass === "tuple") { //I don't want tuples to be considered a reference type, but it makes sense //to group them for this purpose let dynamic; try { dynamic = (0, allocate_1.abiSizeInfo)(dataType, info.allocations.abi).dynamic; } catch (error) { return (0, errors_1.handleDecodingError)(dataType, error, options.strictAbiMode); } if (dynamic) { return yield* decodeAbiReferenceByAddress(dataType, pointer, info, options); } else { return yield* decodeAbiReferenceStatic(dataType, pointer, info, options); } } else { debug("pointer %o", pointer); return yield* Basic.Decode.decodeBasic(dataType, pointer, info, options); } } exports.decodeAbi = decodeAbi; function* decodeAbiReferenceByAddress(dataType, pointer, info, options = {}) { let { strictAbiMode: strict, abiPointerBase: base, lengthOverride } = options; base = base || 0; //in case base was undefined const { allocations: { abi: allocations }, state } = info; debug("pointer %o", pointer); //this variable holds the location we should look to *next* //stack pointers point to calldata; other pointers point to same location const location = pointer.location === "stack" || pointer.location === "stackliteral" ? "calldata" : pointer.location; if (pointer.location !== "stack" && pointer.location !== "stackliteral") { //length overrides are only applicable when you're decoding a pointer //from the stack! otherwise they must be ignored! lengthOverride = undefined; } let rawValue; try { rawValue = yield* (0, read_1.default)(pointer, state); } catch (error) { return (0, errors_1.handleDecodingError)(dataType, error, strict); } let rawValueAsBN = Conversion.toBN(rawValue); debug("rawValue: %O", rawValue); debug("rawValueAsBN: %O", rawValueAsBN); let rawValueAsNumber; try { rawValueAsNumber = rawValueAsBN.toNumber(); } catch (_a) { let error = { kind: "OverlargePointersNotImplementedError", pointerAsBN: rawValueAsBN }; if (strict) { throw new errors_1.StopDecodingError(error); } return { //again with the TS failures... type: dataType, kind: "error", error }; } let startPosition = rawValueAsNumber + base; debug("startPosition %d", startPosition); let dynamic; let size; try { ({ dynamic, size } = (0, allocate_1.abiSizeInfo)(dataType, allocations)); } catch (error) { return (0, errors_1.handleDecodingError)(dataType, error, strict); } if (!dynamic) { //this will only come up when called from stack.ts let staticPointer = { location, start: startPosition, length: size }; return yield* decodeAbiReferenceStatic(dataType, staticPointer, info, options); } let length; let lengthAsBN; let rawLength; switch (dataType.typeClass) { case "bytes": case "string": //initial word contains length (unless an override was given) if (lengthOverride !== undefined) { lengthAsBN = lengthOverride; //note in this case we do *not* increment start position; //if a length override is given, that means the given start //position skips over the length word! } else { try { rawLength = yield* (0, read_1.default)({ location, start: startPosition, length: Evm.Utils.WORD_SIZE }, state); } catch (error) { return (0, errors_1.handleDecodingError)(dataType, error, strict); } lengthAsBN = Conversion.toBN(rawLength); startPosition += Evm.Utils.WORD_SIZE; //increment start position after reading length //so it'll be set up to read the data } if (strict && lengthAsBN.gtn(state[location].length)) { //you may notice that the comparison is a bit crude; that's OK, this is //just to prevent huge numbers from DOSing us, other errors will still //be caught regardless throw new errors_1.StopDecodingError({ kind: "OverlongArrayOrStringStrictModeError", lengthAsBN, dataLength: state[location].length }); } try { length = lengthAsBN.toNumber(); } catch (_b) { //note: if we're in this situation, we can assume we're not in strict mode, //as the strict case was handled above return { //again with the TS failures... type: dataType, kind: "error", error: { kind: "OverlongArraysAndStringsNotImplementedError", lengthAsBN } }; } let childPointer = { location, start: startPosition, length }; return yield* Bytes.Decode.decodeBytes(dataType, childPointer, info, options); case "array": if (dataType.kind === "static") { //static-length array lengthAsBN = dataType.length; //note we don't increment start position; static arrays don't //include a length word! } else if (lengthOverride !== undefined) { debug("override: %o", lengthOverride); //dynamic-length array, but with length override lengthAsBN = lengthOverride; //we don't increment start position; if a length override was //given, that means the pointer skipped the length word! } else { //dynamic-length array, read length from data //initial word contains array length try { rawLength = yield* (0, read_1.default)({ location, start: startPosition, length: Evm.Utils.WORD_SIZE }, state); } catch (error) { return (0, errors_1.handleDecodingError)(dataType, error, strict); } lengthAsBN = Conversion.toBN(rawLength); startPosition += Evm.Utils.WORD_SIZE; //increment startPosition //to next word, as first word was used for length } if (strict && lengthAsBN.gtn(state[location].length)) { //you may notice that the comparison is a bit crude; that's OK, this is //just to prevent huge numbers from DOSing us, other errors will still //be caught regardless throw new errors_1.StopDecodingError({ kind: "OverlongArraysAndStringsNotImplementedError", lengthAsBN, dataLength: state[location].length }); } try { length = lengthAsBN.toNumber(); } catch (_c) { //again, if we get here, we can assume we're not in strict mode return { type: dataType, kind: "error", error: { kind: "OverlongArraysAndStringsNotImplementedError", lengthAsBN } }; } //note: I've written this fairly generically, but it is worth noting that //since this array is of dynamic type, we know that if it's static length //then size must be EVM.WORD_SIZE let baseSize; try { baseSize = (0, allocate_1.abiSizeInfo)(dataType.baseType, allocations).size; } catch (error) { return (0, errors_1.handleDecodingError)(dataType, error, strict); } let decodedChildren = []; for (let index = 0; index < length; index++) { decodedChildren.push(yield* decodeAbi(dataType.baseType, { location, start: startPosition + index * baseSize, length: baseSize }, info, Object.assign(Object.assign({}, options), { abiPointerBase: startPosition }))); //pointer base is always start of list, never the length } return { type: dataType, kind: "value", value: decodedChildren, interpretations: {} }; case "struct": return yield* decodeAbiStructByPosition(dataType, location, startPosition, info, options); case "tuple": return yield* decodeAbiTupleByPosition(dataType, location, startPosition, info, options); } } exports.decodeAbiReferenceByAddress = decodeAbiReferenceByAddress; function* decodeAbiReferenceStatic(dataType, pointer, info, options = {}) { debug("static"); debug("pointer %o", pointer); const location = pointer.location; switch (dataType.typeClass) { case "array": //we're in the static case, so we know the array must be statically sized const lengthAsBN = dataType.length; let length; try { length = lengthAsBN.toNumber(); } catch (_a) { //note: since this is the static case, we don't bother including the stronger //strict-mode guard against getting DOSed by large array sizes, since in this //case we're not reading the size from the input; if there's a huge static size //array, well, we'll just have to deal with it let error = { kind: "OverlongArraysAndStringsNotImplementedError", lengthAsBN }; if (options.strictAbiMode) { throw new errors_1.StopDecodingError(error); } return { type: dataType, kind: "error", error }; } let baseSize; try { baseSize = (0, allocate_1.abiSizeInfo)(dataType.baseType, info.allocations.abi).size; } catch (error) { return (0, errors_1.handleDecodingError)(dataType, error, options.strictAbiMode); } let decodedChildren = []; for (let index = 0; index < length; index++) { decodedChildren.push(yield* decodeAbi(dataType.baseType, { location, start: pointer.start + index * baseSize, length: baseSize }, info, options)); } return { type: dataType, kind: "value", value: decodedChildren, interpretations: {} }; case "struct": return yield* decodeAbiStructByPosition(dataType, location, pointer.start, info, options); case "tuple": return yield* decodeAbiTupleByPosition(dataType, location, pointer.start, info, options); } } exports.decodeAbiReferenceStatic = decodeAbiReferenceStatic; //note that this function takes the start position as a *number*; it does not take a pointer function* decodeAbiStructByPosition(dataType, location, startPosition, info, options = {}) { const { allocations: { abi: allocations } } = info; const typeLocation = location === "calldata" ? "calldata" : null; //other abi locations are not valid type locations const typeId = dataType.id; const structAllocation = allocations[typeId]; if (!structAllocation) { let error = { kind: "UserDefinedTypeNotFoundError", type: dataType }; if (options.strictAbiMode || options.allowRetry) { throw new errors_1.StopDecodingError(error, true); //note that we allow a retry if we couldn't locate the allocation! } return { type: dataType, kind: "error", error }; } let decodedMembers = []; for (let index = 0; index < structAllocation.members.length; index++) { const memberAllocation = structAllocation.members[index]; const memberPointer = memberAllocation.pointer; const childPointer = { location, start: startPosition + memberPointer.start, length: memberPointer.length }; let memberName = memberAllocation.name; let memberType = Format.Types.specifyLocation(memberAllocation.type, typeLocation); decodedMembers.push({ name: memberName, value: yield* decodeAbi(memberType, childPointer, info, Object.assign(Object.assign({}, options), { abiPointerBase: startPosition })) //note that the base option is only needed in the dynamic case, but we're being indiscriminate }); } return { type: dataType, kind: "value", value: decodedMembers, interpretations: {} }; } //note that this function takes the start position as a *number*; it does not take a pointer function* decodeAbiTupleByPosition(dataType, location, startPosition, info, options = {}) { //WARNING: This case is written in a way that involves a bunch of unnecessary recomputation! //I'm writing it this way anyway for simplicity, to avoid rewriting the decoder //However it may be worth revisiting this in the future if performance turns out to be a problem //(changing this may be pretty hard though) let decodedMembers = []; let position = startPosition; for (const { name, type: memberType } of dataType.memberTypes) { const memberSize = (0, allocate_1.abiSizeInfo)(memberType, info.allocations.abi).size; const childPointer = { location, start: position, length: memberSize }; decodedMembers.push({ name, value: yield* decodeAbi(memberType, childPointer, info, Object.assign(Object.assign({}, options), { abiPointerBase: startPosition })) //note that the base option is only needed in the dynamic case, but we're being indiscriminate }); position += memberSize; } return { type: dataType, kind: "value", value: decodedMembers, interpretations: {} }; } //# sourceMappingURL=index.js.map