abstract-coder.js 16 KB

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