abstract-coder.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Reader = exports.Writer = exports.Coder = exports.checkResultErrors = exports.Result = exports.WordSize = void 0;
  4. const index_js_1 = require("../../utils/index.js");
  5. /**
  6. * @_ignore:
  7. */
  8. exports.WordSize = 32;
  9. const Padding = new Uint8Array(exports.WordSize);
  10. // Properties used to immediate pass through to the underlying object
  11. // - `then` is used to detect if an object is a Promise for await
  12. const passProperties = ["then"];
  13. const _guard = {};
  14. const resultNames = new WeakMap();
  15. function getNames(result) {
  16. return resultNames.get(result);
  17. }
  18. function setNames(result, names) {
  19. resultNames.set(result, names);
  20. }
  21. function throwError(name, error) {
  22. const wrapped = new Error(`deferred error during ABI decoding triggered accessing ${name}`);
  23. wrapped.error = error;
  24. throw wrapped;
  25. }
  26. function toObject(names, items, deep) {
  27. if (names.indexOf(null) >= 0) {
  28. return items.map((item, index) => {
  29. if (item instanceof Result) {
  30. return toObject(getNames(item), item, deep);
  31. }
  32. return item;
  33. });
  34. }
  35. return names.reduce((accum, name, index) => {
  36. let item = items.getValue(name);
  37. if (!(name in accum)) {
  38. if (deep && item instanceof Result) {
  39. item = toObject(getNames(item), item, deep);
  40. }
  41. accum[name] = item;
  42. }
  43. return accum;
  44. }, {});
  45. }
  46. /**
  47. * A [[Result]] is a sub-class of Array, which allows accessing any
  48. * of its values either positionally by its index or, if keys are
  49. * provided by its name.
  50. *
  51. * @_docloc: api/abi
  52. */
  53. class Result extends Array {
  54. // No longer used; but cannot be removed as it will remove the
  55. // #private field from the .d.ts which may break backwards
  56. // compatibility
  57. #names;
  58. /**
  59. * @private
  60. */
  61. constructor(...args) {
  62. // To properly sub-class Array so the other built-in
  63. // functions work, the constructor has to behave fairly
  64. // well. So, in the event we are created via fromItems()
  65. // we build the read-only Result object we want, but on
  66. // any other input, we use the default constructor
  67. // constructor(guard: any, items: Array<any>, keys?: Array<null | string>);
  68. const guard = args[0];
  69. let items = args[1];
  70. let names = (args[2] || []).slice();
  71. let wrap = true;
  72. if (guard !== _guard) {
  73. items = args;
  74. names = [];
  75. wrap = false;
  76. }
  77. // Can't just pass in ...items since an array of length 1
  78. // is a special case in the super.
  79. super(items.length);
  80. items.forEach((item, index) => { this[index] = item; });
  81. // Find all unique keys
  82. const nameCounts = names.reduce((accum, name) => {
  83. if (typeof (name) === "string") {
  84. accum.set(name, (accum.get(name) || 0) + 1);
  85. }
  86. return accum;
  87. }, (new Map()));
  88. // Remove any key thats not unique
  89. setNames(this, Object.freeze(items.map((item, index) => {
  90. const name = names[index];
  91. if (name != null && nameCounts.get(name) === 1) {
  92. return name;
  93. }
  94. return null;
  95. })));
  96. // Dummy operations to prevent TypeScript from complaining
  97. this.#names = [];
  98. if (this.#names == null) {
  99. void (this.#names);
  100. }
  101. if (!wrap) {
  102. return;
  103. }
  104. // A wrapped Result is immutable
  105. Object.freeze(this);
  106. // Proxy indices and names so we can trap deferred errors
  107. const proxy = new Proxy(this, {
  108. get: (target, prop, receiver) => {
  109. if (typeof (prop) === "string") {
  110. // Index accessor
  111. if (prop.match(/^[0-9]+$/)) {
  112. const index = (0, index_js_1.getNumber)(prop, "%index");
  113. if (index < 0 || index >= this.length) {
  114. throw new RangeError("out of result range");
  115. }
  116. const item = target[index];
  117. if (item instanceof Error) {
  118. throwError(`index ${index}`, item);
  119. }
  120. return item;
  121. }
  122. // Pass important checks (like `then` for Promise) through
  123. if (passProperties.indexOf(prop) >= 0) {
  124. return Reflect.get(target, prop, receiver);
  125. }
  126. const value = target[prop];
  127. if (value instanceof Function) {
  128. // Make sure functions work with private variables
  129. // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#no_private_property_forwarding
  130. return function (...args) {
  131. return value.apply((this === receiver) ? target : this, args);
  132. };
  133. }
  134. else if (!(prop in target)) {
  135. // Possible name accessor
  136. return target.getValue.apply((this === receiver) ? target : this, [prop]);
  137. }
  138. }
  139. return Reflect.get(target, prop, receiver);
  140. }
  141. });
  142. setNames(proxy, getNames(this));
  143. return proxy;
  144. }
  145. /**
  146. * Returns the Result as a normal Array. If %%deep%%, any children
  147. * which are Result objects are also converted to a normal Array.
  148. *
  149. * This will throw if there are any outstanding deferred
  150. * errors.
  151. */
  152. toArray(deep) {
  153. const result = [];
  154. this.forEach((item, index) => {
  155. if (item instanceof Error) {
  156. throwError(`index ${index}`, item);
  157. }
  158. if (deep && item instanceof Result) {
  159. item = item.toArray(deep);
  160. }
  161. result.push(item);
  162. });
  163. return result;
  164. }
  165. /**
  166. * Returns the Result as an Object with each name-value pair. If
  167. * %%deep%%, any children which are Result objects are also
  168. * converted to an Object.
  169. *
  170. * This will throw if any value is unnamed, or if there are
  171. * any outstanding deferred errors.
  172. */
  173. toObject(deep) {
  174. const names = getNames(this);
  175. return names.reduce((accum, name, index) => {
  176. (0, index_js_1.assert)(name != null, `value at index ${index} unnamed`, "UNSUPPORTED_OPERATION", {
  177. operation: "toObject()"
  178. });
  179. return toObject(names, this, deep);
  180. }, {});
  181. }
  182. /**
  183. * @_ignore
  184. */
  185. slice(start, end) {
  186. if (start == null) {
  187. start = 0;
  188. }
  189. if (start < 0) {
  190. start += this.length;
  191. if (start < 0) {
  192. start = 0;
  193. }
  194. }
  195. if (end == null) {
  196. end = this.length;
  197. }
  198. if (end < 0) {
  199. end += this.length;
  200. if (end < 0) {
  201. end = 0;
  202. }
  203. }
  204. if (end > this.length) {
  205. end = this.length;
  206. }
  207. const _names = getNames(this);
  208. const result = [], names = [];
  209. for (let i = start; i < end; i++) {
  210. result.push(this[i]);
  211. names.push(_names[i]);
  212. }
  213. return new Result(_guard, result, names);
  214. }
  215. /**
  216. * @_ignore
  217. */
  218. filter(callback, thisArg) {
  219. const _names = getNames(this);
  220. const result = [], names = [];
  221. for (let i = 0; i < this.length; i++) {
  222. const item = this[i];
  223. if (item instanceof Error) {
  224. throwError(`index ${i}`, item);
  225. }
  226. if (callback.call(thisArg, item, i, this)) {
  227. result.push(item);
  228. names.push(_names[i]);
  229. }
  230. }
  231. return new Result(_guard, result, names);
  232. }
  233. /**
  234. * @_ignore
  235. */
  236. map(callback, thisArg) {
  237. const result = [];
  238. for (let i = 0; i < this.length; i++) {
  239. const item = this[i];
  240. if (item instanceof Error) {
  241. throwError(`index ${i}`, item);
  242. }
  243. result.push(callback.call(thisArg, item, i, this));
  244. }
  245. return result;
  246. }
  247. /**
  248. * Returns the value for %%name%%.
  249. *
  250. * Since it is possible to have a key whose name conflicts with
  251. * a method on a [[Result]] or its superclass Array, or any
  252. * JavaScript keyword, this ensures all named values are still
  253. * accessible by name.
  254. */
  255. getValue(name) {
  256. const index = getNames(this).indexOf(name);
  257. if (index === -1) {
  258. return undefined;
  259. }
  260. const value = this[index];
  261. if (value instanceof Error) {
  262. throwError(`property ${JSON.stringify(name)}`, value.error);
  263. }
  264. return value;
  265. }
  266. /**
  267. * Creates a new [[Result]] for %%items%% with each entry
  268. * also accessible by its corresponding name in %%keys%%.
  269. */
  270. static fromItems(items, keys) {
  271. return new Result(_guard, items, keys);
  272. }
  273. }
  274. exports.Result = Result;
  275. /**
  276. * Returns all errors found in a [[Result]].
  277. *
  278. * Since certain errors encountered when creating a [[Result]] do
  279. * not impact the ability to continue parsing data, they are
  280. * deferred until they are actually accessed. Hence a faulty string
  281. * in an Event that is never used does not impact the program flow.
  282. *
  283. * However, sometimes it may be useful to access, identify or
  284. * validate correctness of a [[Result]].
  285. *
  286. * @_docloc api/abi
  287. */
  288. function checkResultErrors(result) {
  289. // Find the first error (if any)
  290. const errors = [];
  291. const checkErrors = function (path, object) {
  292. if (!Array.isArray(object)) {
  293. return;
  294. }
  295. for (let key in object) {
  296. const childPath = path.slice();
  297. childPath.push(key);
  298. try {
  299. checkErrors(childPath, object[key]);
  300. }
  301. catch (error) {
  302. errors.push({ path: childPath, error: error });
  303. }
  304. }
  305. };
  306. checkErrors([], result);
  307. return errors;
  308. }
  309. exports.checkResultErrors = checkResultErrors;
  310. function getValue(value) {
  311. let bytes = (0, index_js_1.toBeArray)(value);
  312. (0, index_js_1.assert)(bytes.length <= exports.WordSize, "value out-of-bounds", "BUFFER_OVERRUN", { buffer: bytes, length: exports.WordSize, offset: bytes.length });
  313. if (bytes.length !== exports.WordSize) {
  314. bytes = (0, index_js_1.getBytesCopy)((0, index_js_1.concat)([Padding.slice(bytes.length % exports.WordSize), bytes]));
  315. }
  316. return bytes;
  317. }
  318. /**
  319. * @_ignore
  320. */
  321. class Coder {
  322. // The coder name:
  323. // - address, uint256, tuple, array, etc.
  324. name;
  325. // The fully expanded type, including composite types:
  326. // - address, uint256, tuple(address,bytes), uint256[3][4][], etc.
  327. type;
  328. // The localName bound in the signature, in this example it is "baz":
  329. // - tuple(address foo, uint bar) baz
  330. localName;
  331. // Whether this type is dynamic:
  332. // - Dynamic: bytes, string, address[], tuple(boolean[]), etc.
  333. // - Not Dynamic: address, uint256, boolean[3], tuple(address, uint8)
  334. dynamic;
  335. constructor(name, type, localName, dynamic) {
  336. (0, index_js_1.defineProperties)(this, { name, type, localName, dynamic }, {
  337. name: "string", type: "string", localName: "string", dynamic: "boolean"
  338. });
  339. }
  340. _throwError(message, value) {
  341. (0, index_js_1.assertArgument)(false, message, this.localName, value);
  342. }
  343. }
  344. exports.Coder = Coder;
  345. /**
  346. * @_ignore
  347. */
  348. class Writer {
  349. // An array of WordSize lengthed objects to concatenation
  350. #data;
  351. #dataLength;
  352. constructor() {
  353. this.#data = [];
  354. this.#dataLength = 0;
  355. }
  356. get data() {
  357. return (0, index_js_1.concat)(this.#data);
  358. }
  359. get length() { return this.#dataLength; }
  360. #writeData(data) {
  361. this.#data.push(data);
  362. this.#dataLength += data.length;
  363. return data.length;
  364. }
  365. appendWriter(writer) {
  366. return this.#writeData((0, index_js_1.getBytesCopy)(writer.data));
  367. }
  368. // Arrayish item; pad on the right to *nearest* WordSize
  369. writeBytes(value) {
  370. let bytes = (0, index_js_1.getBytesCopy)(value);
  371. const paddingOffset = bytes.length % exports.WordSize;
  372. if (paddingOffset) {
  373. bytes = (0, index_js_1.getBytesCopy)((0, index_js_1.concat)([bytes, Padding.slice(paddingOffset)]));
  374. }
  375. return this.#writeData(bytes);
  376. }
  377. // Numeric item; pad on the left *to* WordSize
  378. writeValue(value) {
  379. return this.#writeData(getValue(value));
  380. }
  381. // Inserts a numeric place-holder, returning a callback that can
  382. // be used to asjust the value later
  383. writeUpdatableValue() {
  384. const offset = this.#data.length;
  385. this.#data.push(Padding);
  386. this.#dataLength += exports.WordSize;
  387. return (value) => {
  388. this.#data[offset] = getValue(value);
  389. };
  390. }
  391. }
  392. exports.Writer = Writer;
  393. /**
  394. * @_ignore
  395. */
  396. class Reader {
  397. // Allows incomplete unpadded data to be read; otherwise an error
  398. // is raised if attempting to overrun the buffer. This is required
  399. // to deal with an old Solidity bug, in which event data for
  400. // external (not public thoguh) was tightly packed.
  401. allowLoose;
  402. #data;
  403. #offset;
  404. #bytesRead;
  405. #parent;
  406. #maxInflation;
  407. constructor(data, allowLoose, maxInflation) {
  408. (0, index_js_1.defineProperties)(this, { allowLoose: !!allowLoose });
  409. this.#data = (0, index_js_1.getBytesCopy)(data);
  410. this.#bytesRead = 0;
  411. this.#parent = null;
  412. this.#maxInflation = (maxInflation != null) ? maxInflation : 1024;
  413. this.#offset = 0;
  414. }
  415. get data() { return (0, index_js_1.hexlify)(this.#data); }
  416. get dataLength() { return this.#data.length; }
  417. get consumed() { return this.#offset; }
  418. get bytes() { return new Uint8Array(this.#data); }
  419. #incrementBytesRead(count) {
  420. if (this.#parent) {
  421. return this.#parent.#incrementBytesRead(count);
  422. }
  423. this.#bytesRead += count;
  424. // Check for excessive inflation (see: #4537)
  425. (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", {
  426. buffer: (0, index_js_1.getBytesCopy)(this.#data), offset: this.#offset,
  427. length: count, info: {
  428. bytesRead: this.#bytesRead,
  429. dataLength: this.dataLength
  430. }
  431. });
  432. }
  433. #peekBytes(offset, length, loose) {
  434. let alignedLength = Math.ceil(length / exports.WordSize) * exports.WordSize;
  435. if (this.#offset + alignedLength > this.#data.length) {
  436. if (this.allowLoose && loose && this.#offset + length <= this.#data.length) {
  437. alignedLength = length;
  438. }
  439. else {
  440. (0, index_js_1.assert)(false, "data out-of-bounds", "BUFFER_OVERRUN", {
  441. buffer: (0, index_js_1.getBytesCopy)(this.#data),
  442. length: this.#data.length,
  443. offset: this.#offset + alignedLength
  444. });
  445. }
  446. }
  447. return this.#data.slice(this.#offset, this.#offset + alignedLength);
  448. }
  449. // Create a sub-reader with the same underlying data, but offset
  450. subReader(offset) {
  451. const reader = new Reader(this.#data.slice(this.#offset + offset), this.allowLoose, this.#maxInflation);
  452. reader.#parent = this;
  453. return reader;
  454. }
  455. // Read bytes
  456. readBytes(length, loose) {
  457. let bytes = this.#peekBytes(0, length, !!loose);
  458. this.#incrementBytesRead(length);
  459. this.#offset += bytes.length;
  460. // @TODO: Make sure the length..end bytes are all 0?
  461. return bytes.slice(0, length);
  462. }
  463. // Read a numeric values
  464. readValue() {
  465. return (0, index_js_1.toBigInt)(this.readBytes(exports.WordSize));
  466. }
  467. readIndex() {
  468. return (0, index_js_1.toNumber)(this.readBytes(exports.WordSize));
  469. }
  470. }
  471. exports.Reader = Reader;
  472. //# sourceMappingURL=abstract-coder.js.map