123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.Reader = exports.Writer = exports.Coder = exports.checkResultErrors = exports.Result = exports.WordSize = void 0;
- const index_js_1 = require("../../utils/index.js");
- /**
- * @_ignore:
- */
- exports.WordSize = 32;
- const Padding = new Uint8Array(exports.WordSize);
- // Properties used to immediate pass through to the underlying object
- // - `then` is used to detect if an object is a Promise for await
- const passProperties = ["then"];
- const _guard = {};
- const resultNames = new WeakMap();
- function getNames(result) {
- return resultNames.get(result);
- }
- function setNames(result, names) {
- resultNames.set(result, names);
- }
- function throwError(name, error) {
- const wrapped = new Error(`deferred error during ABI decoding triggered accessing ${name}`);
- wrapped.error = error;
- throw wrapped;
- }
- function toObject(names, items, deep) {
- if (names.indexOf(null) >= 0) {
- return items.map((item, index) => {
- if (item instanceof Result) {
- return toObject(getNames(item), item, deep);
- }
- return item;
- });
- }
- return names.reduce((accum, name, index) => {
- let item = items.getValue(name);
- if (!(name in accum)) {
- if (deep && item instanceof Result) {
- item = toObject(getNames(item), item, deep);
- }
- accum[name] = item;
- }
- return accum;
- }, {});
- }
- /**
- * A [[Result]] is a sub-class of Array, which allows accessing any
- * of its values either positionally by its index or, if keys are
- * provided by its name.
- *
- * @_docloc: api/abi
- */
- class Result extends Array {
- // No longer used; but cannot be removed as it will remove the
- // #private field from the .d.ts which may break backwards
- // compatibility
- #names;
- /**
- * @private
- */
- constructor(...args) {
- // To properly sub-class Array so the other built-in
- // functions work, the constructor has to behave fairly
- // well. So, in the event we are created via fromItems()
- // we build the read-only Result object we want, but on
- // any other input, we use the default constructor
- // constructor(guard: any, items: Array<any>, keys?: Array<null | string>);
- const guard = args[0];
- let items = args[1];
- let names = (args[2] || []).slice();
- let wrap = true;
- if (guard !== _guard) {
- items = args;
- names = [];
- wrap = false;
- }
- // Can't just pass in ...items since an array of length 1
- // is a special case in the super.
- super(items.length);
- items.forEach((item, index) => { this[index] = item; });
- // Find all unique keys
- const nameCounts = names.reduce((accum, name) => {
- if (typeof (name) === "string") {
- accum.set(name, (accum.get(name) || 0) + 1);
- }
- return accum;
- }, (new Map()));
- // Remove any key thats not unique
- setNames(this, Object.freeze(items.map((item, index) => {
- const name = names[index];
- if (name != null && nameCounts.get(name) === 1) {
- return name;
- }
- return null;
- })));
- // Dummy operations to prevent TypeScript from complaining
- this.#names = [];
- if (this.#names == null) {
- void (this.#names);
- }
- if (!wrap) {
- return;
- }
- // A wrapped Result is immutable
- Object.freeze(this);
- // Proxy indices and names so we can trap deferred errors
- const proxy = new Proxy(this, {
- get: (target, prop, receiver) => {
- if (typeof (prop) === "string") {
- // Index accessor
- if (prop.match(/^[0-9]+$/)) {
- const index = (0, index_js_1.getNumber)(prop, "%index");
- if (index < 0 || index >= this.length) {
- throw new RangeError("out of result range");
- }
- const item = target[index];
- if (item instanceof Error) {
- throwError(`index ${index}`, item);
- }
- return item;
- }
- // Pass important checks (like `then` for Promise) through
- if (passProperties.indexOf(prop) >= 0) {
- return Reflect.get(target, prop, receiver);
- }
- const value = target[prop];
- if (value instanceof Function) {
- // Make sure functions work with private variables
- // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#no_private_property_forwarding
- return function (...args) {
- return value.apply((this === receiver) ? target : this, args);
- };
- }
- else if (!(prop in target)) {
- // Possible name accessor
- return target.getValue.apply((this === receiver) ? target : this, [prop]);
- }
- }
- return Reflect.get(target, prop, receiver);
- }
- });
- setNames(proxy, getNames(this));
- return proxy;
- }
- /**
- * Returns the Result as a normal Array. If %%deep%%, any children
- * which are Result objects are also converted to a normal Array.
- *
- * This will throw if there are any outstanding deferred
- * errors.
- */
- toArray(deep) {
- const result = [];
- this.forEach((item, index) => {
- if (item instanceof Error) {
- throwError(`index ${index}`, item);
- }
- if (deep && item instanceof Result) {
- item = item.toArray(deep);
- }
- result.push(item);
- });
- return result;
- }
- /**
- * Returns the Result as an Object with each name-value pair. If
- * %%deep%%, any children which are Result objects are also
- * converted to an Object.
- *
- * This will throw if any value is unnamed, or if there are
- * any outstanding deferred errors.
- */
- toObject(deep) {
- const names = getNames(this);
- return names.reduce((accum, name, index) => {
- (0, index_js_1.assert)(name != null, `value at index ${index} unnamed`, "UNSUPPORTED_OPERATION", {
- operation: "toObject()"
- });
- return toObject(names, this, deep);
- }, {});
- }
- /**
- * @_ignore
- */
- slice(start, end) {
- if (start == null) {
- start = 0;
- }
- if (start < 0) {
- start += this.length;
- if (start < 0) {
- start = 0;
- }
- }
- if (end == null) {
- end = this.length;
- }
- if (end < 0) {
- end += this.length;
- if (end < 0) {
- end = 0;
- }
- }
- if (end > this.length) {
- end = this.length;
- }
- const _names = getNames(this);
- const result = [], names = [];
- for (let i = start; i < end; i++) {
- result.push(this[i]);
- names.push(_names[i]);
- }
- return new Result(_guard, result, names);
- }
- /**
- * @_ignore
- */
- filter(callback, thisArg) {
- const _names = getNames(this);
- const result = [], names = [];
- for (let i = 0; i < this.length; i++) {
- const item = this[i];
- if (item instanceof Error) {
- throwError(`index ${i}`, item);
- }
- if (callback.call(thisArg, item, i, this)) {
- result.push(item);
- names.push(_names[i]);
- }
- }
- return new Result(_guard, result, names);
- }
- /**
- * @_ignore
- */
- map(callback, thisArg) {
- const result = [];
- for (let i = 0; i < this.length; i++) {
- const item = this[i];
- if (item instanceof Error) {
- throwError(`index ${i}`, item);
- }
- result.push(callback.call(thisArg, item, i, this));
- }
- return result;
- }
- /**
- * Returns the value for %%name%%.
- *
- * Since it is possible to have a key whose name conflicts with
- * a method on a [[Result]] or its superclass Array, or any
- * JavaScript keyword, this ensures all named values are still
- * accessible by name.
- */
- getValue(name) {
- const index = getNames(this).indexOf(name);
- if (index === -1) {
- return undefined;
- }
- const value = this[index];
- if (value instanceof Error) {
- throwError(`property ${JSON.stringify(name)}`, value.error);
- }
- return value;
- }
- /**
- * Creates a new [[Result]] for %%items%% with each entry
- * also accessible by its corresponding name in %%keys%%.
- */
- static fromItems(items, keys) {
- return new Result(_guard, items, keys);
- }
- }
- exports.Result = Result;
- /**
- * Returns all errors found in a [[Result]].
- *
- * Since certain errors encountered when creating a [[Result]] do
- * not impact the ability to continue parsing data, they are
- * deferred until they are actually accessed. Hence a faulty string
- * in an Event that is never used does not impact the program flow.
- *
- * However, sometimes it may be useful to access, identify or
- * validate correctness of a [[Result]].
- *
- * @_docloc api/abi
- */
- function checkResultErrors(result) {
- // Find the first error (if any)
- const errors = [];
- const checkErrors = function (path, object) {
- if (!Array.isArray(object)) {
- return;
- }
- for (let key in object) {
- const childPath = path.slice();
- childPath.push(key);
- try {
- checkErrors(childPath, object[key]);
- }
- catch (error) {
- errors.push({ path: childPath, error: error });
- }
- }
- };
- checkErrors([], result);
- return errors;
- }
- exports.checkResultErrors = checkResultErrors;
- function getValue(value) {
- let bytes = (0, index_js_1.toBeArray)(value);
- (0, index_js_1.assert)(bytes.length <= exports.WordSize, "value out-of-bounds", "BUFFER_OVERRUN", { buffer: bytes, length: exports.WordSize, offset: bytes.length });
- if (bytes.length !== exports.WordSize) {
- bytes = (0, index_js_1.getBytesCopy)((0, index_js_1.concat)([Padding.slice(bytes.length % exports.WordSize), bytes]));
- }
- return bytes;
- }
- /**
- * @_ignore
- */
- class Coder {
- // The coder name:
- // - address, uint256, tuple, array, etc.
- name;
- // The fully expanded type, including composite types:
- // - address, uint256, tuple(address,bytes), uint256[3][4][], etc.
- type;
- // The localName bound in the signature, in this example it is "baz":
- // - tuple(address foo, uint bar) baz
- localName;
- // Whether this type is dynamic:
- // - Dynamic: bytes, string, address[], tuple(boolean[]), etc.
- // - Not Dynamic: address, uint256, boolean[3], tuple(address, uint8)
- dynamic;
- constructor(name, type, localName, dynamic) {
- (0, index_js_1.defineProperties)(this, { name, type, localName, dynamic }, {
- name: "string", type: "string", localName: "string", dynamic: "boolean"
- });
- }
- _throwError(message, value) {
- (0, index_js_1.assertArgument)(false, message, this.localName, value);
- }
- }
- exports.Coder = Coder;
- /**
- * @_ignore
- */
- class Writer {
- // An array of WordSize lengthed objects to concatenation
- #data;
- #dataLength;
- constructor() {
- this.#data = [];
- this.#dataLength = 0;
- }
- get data() {
- return (0, index_js_1.concat)(this.#data);
- }
- get length() { return this.#dataLength; }
- #writeData(data) {
- this.#data.push(data);
- this.#dataLength += data.length;
- return data.length;
- }
- appendWriter(writer) {
- return this.#writeData((0, index_js_1.getBytesCopy)(writer.data));
- }
- // Arrayish item; pad on the right to *nearest* WordSize
- writeBytes(value) {
- let bytes = (0, index_js_1.getBytesCopy)(value);
- const paddingOffset = bytes.length % exports.WordSize;
- if (paddingOffset) {
- bytes = (0, index_js_1.getBytesCopy)((0, index_js_1.concat)([bytes, Padding.slice(paddingOffset)]));
- }
- return this.#writeData(bytes);
- }
- // Numeric item; pad on the left *to* WordSize
- writeValue(value) {
- return this.#writeData(getValue(value));
- }
- // Inserts a numeric place-holder, returning a callback that can
- // be used to asjust the value later
- writeUpdatableValue() {
- const offset = this.#data.length;
- this.#data.push(Padding);
- this.#dataLength += exports.WordSize;
- return (value) => {
- this.#data[offset] = getValue(value);
- };
- }
- }
- exports.Writer = Writer;
- /**
- * @_ignore
- */
- class Reader {
- // Allows incomplete unpadded data to be read; otherwise an error
- // is raised if attempting to overrun the buffer. This is required
- // to deal with an old Solidity bug, in which event data for
- // external (not public thoguh) was tightly packed.
- allowLoose;
- #data;
- #offset;
- #bytesRead;
- #parent;
- #maxInflation;
- constructor(data, allowLoose, maxInflation) {
- (0, index_js_1.defineProperties)(this, { allowLoose: !!allowLoose });
- this.#data = (0, index_js_1.getBytesCopy)(data);
- this.#bytesRead = 0;
- this.#parent = null;
- this.#maxInflation = (maxInflation != null) ? maxInflation : 1024;
- this.#offset = 0;
- }
- get data() { return (0, index_js_1.hexlify)(this.#data); }
- get dataLength() { return this.#data.length; }
- get consumed() { return this.#offset; }
- get bytes() { return new Uint8Array(this.#data); }
- #incrementBytesRead(count) {
- if (this.#parent) {
- return this.#parent.#incrementBytesRead(count);
- }
- this.#bytesRead += count;
- // Check for excessive inflation (see: #4537)
- (0, index_js_1.assert)(this.#maxInflation < 1 || this.#bytesRead <= this.#maxInflation * this.dataLength, `compressed ABI data exceeds inflation ratio of ${this.#maxInflation} ( see: https:/\/github.com/ethers-io/ethers.js/issues/4537 )`, "BUFFER_OVERRUN", {
- buffer: (0, index_js_1.getBytesCopy)(this.#data), offset: this.#offset,
- length: count, info: {
- bytesRead: this.#bytesRead,
- dataLength: this.dataLength
- }
- });
- }
- #peekBytes(offset, length, loose) {
- let alignedLength = Math.ceil(length / exports.WordSize) * exports.WordSize;
- if (this.#offset + alignedLength > this.#data.length) {
- if (this.allowLoose && loose && this.#offset + length <= this.#data.length) {
- alignedLength = length;
- }
- else {
- (0, index_js_1.assert)(false, "data out-of-bounds", "BUFFER_OVERRUN", {
- buffer: (0, index_js_1.getBytesCopy)(this.#data),
- length: this.#data.length,
- offset: this.#offset + alignedLength
- });
- }
- }
- return this.#data.slice(this.#offset, this.#offset + alignedLength);
- }
- // Create a sub-reader with the same underlying data, but offset
- subReader(offset) {
- const reader = new Reader(this.#data.slice(this.#offset + offset), this.allowLoose, this.#maxInflation);
- reader.#parent = this;
- return reader;
- }
- // Read bytes
- readBytes(length, loose) {
- let bytes = this.#peekBytes(0, length, !!loose);
- this.#incrementBytesRead(length);
- this.#offset += bytes.length;
- // @TODO: Make sure the length..end bytes are all 0?
- return bytes.slice(0, length);
- }
- // Read a numeric values
- readValue() {
- return (0, index_js_1.toBigInt)(this.readBytes(exports.WordSize));
- }
- readIndex() {
- return (0, index_js_1.toNumber)(this.readBytes(exports.WordSize));
- }
- }
- exports.Reader = Reader;
- //# sourceMappingURL=abstract-coder.js.map
|