abi-coder.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /**
  2. * When sending values to or receiving values from a [[Contract]], the
  3. * data is generally encoded using the [ABI standard](link-solc-abi).
  4. *
  5. * The AbiCoder provides a utility to encode values to ABI data and
  6. * decode values from ABI data.
  7. *
  8. * Most of the time, developers should favour the [[Contract]] class,
  9. * which further abstracts a lot of the finer details of ABI data.
  10. *
  11. * @_section api/abi/abi-coder:ABI Encoding
  12. */
  13. // See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
  14. import { assertArgumentCount, assertArgument } from "../utils/index.js";
  15. import { Coder, Reader, Result, Writer } from "./coders/abstract-coder.js";
  16. import { AddressCoder } from "./coders/address.js";
  17. import { ArrayCoder } from "./coders/array.js";
  18. import { BooleanCoder } from "./coders/boolean.js";
  19. import { BytesCoder } from "./coders/bytes.js";
  20. import { FixedBytesCoder } from "./coders/fixed-bytes.js";
  21. import { NullCoder } from "./coders/null.js";
  22. import { NumberCoder } from "./coders/number.js";
  23. import { StringCoder } from "./coders/string.js";
  24. import { TupleCoder } from "./coders/tuple.js";
  25. import { ParamType } from "./fragments.js";
  26. import { getAddress } from "../address/index.js";
  27. import { getBytes, hexlify, makeError } from "../utils/index.js";
  28. import type {
  29. BytesLike,
  30. CallExceptionAction, CallExceptionError, CallExceptionTransaction
  31. } from "../utils/index.js";
  32. // https://docs.soliditylang.org/en/v0.8.17/control-structures.html
  33. const PanicReasons: Map<number, string> = new Map();
  34. PanicReasons.set(0x00, "GENERIC_PANIC");
  35. PanicReasons.set(0x01, "ASSERT_FALSE");
  36. PanicReasons.set(0x11, "OVERFLOW");
  37. PanicReasons.set(0x12, "DIVIDE_BY_ZERO");
  38. PanicReasons.set(0x21, "ENUM_RANGE_ERROR");
  39. PanicReasons.set(0x22, "BAD_STORAGE_DATA");
  40. PanicReasons.set(0x31, "STACK_UNDERFLOW");
  41. PanicReasons.set(0x32, "ARRAY_RANGE_ERROR");
  42. PanicReasons.set(0x41, "OUT_OF_MEMORY");
  43. PanicReasons.set(0x51, "UNINITIALIZED_FUNCTION_CALL");
  44. const paramTypeBytes = new RegExp(/^bytes([0-9]*)$/);
  45. const paramTypeNumber = new RegExp(/^(u?int)([0-9]*)$/);
  46. let defaultCoder: null | AbiCoder = null;
  47. let defaultMaxInflation = 1024;
  48. function getBuiltinCallException(action: CallExceptionAction, tx: { to?: null | string, from?: null | string, data?: string }, data: null | BytesLike, abiCoder: AbiCoder): CallExceptionError {
  49. let message = "missing revert data";
  50. let reason: null | string = null;
  51. const invocation = null;
  52. let revert: null | { signature: string, name: string, args: Array<any> } = null;
  53. if (data) {
  54. message = "execution reverted";
  55. const bytes = getBytes(data);
  56. data = hexlify(data);
  57. if (bytes.length === 0) {
  58. message += " (no data present; likely require(false) occurred";
  59. reason = "require(false)";
  60. } else if (bytes.length % 32 !== 4) {
  61. message += " (could not decode reason; invalid data length)";
  62. } else if (hexlify(bytes.slice(0, 4)) === "0x08c379a0") {
  63. // Error(string)
  64. try {
  65. reason = abiCoder.decode([ "string" ], bytes.slice(4))[0]
  66. revert = {
  67. signature: "Error(string)",
  68. name: "Error",
  69. args: [ reason ]
  70. };
  71. message += `: ${ JSON.stringify(reason) }`;
  72. } catch (error) {
  73. message += " (could not decode reason; invalid string data)";
  74. }
  75. } else if (hexlify(bytes.slice(0, 4)) === "0x4e487b71") {
  76. // Panic(uint256)
  77. try {
  78. const code = Number(abiCoder.decode([ "uint256" ], bytes.slice(4))[0]);
  79. revert = {
  80. signature: "Panic(uint256)",
  81. name: "Panic",
  82. args: [ code ]
  83. };
  84. reason = `Panic due to ${ PanicReasons.get(code) || "UNKNOWN" }(${ code })`;
  85. message += `: ${ reason }`;
  86. } catch (error) {
  87. message += " (could not decode panic code)";
  88. }
  89. } else {
  90. message += " (unknown custom error)";
  91. }
  92. }
  93. const transaction: CallExceptionTransaction = {
  94. to: (tx.to ? getAddress(tx.to): null),
  95. data: (tx.data || "0x")
  96. };
  97. if (tx.from) { transaction.from = getAddress(tx.from); }
  98. return makeError(message, "CALL_EXCEPTION", {
  99. action, data, reason, transaction, invocation, revert
  100. });
  101. }
  102. /**
  103. * The **AbiCoder** is a low-level class responsible for encoding JavaScript
  104. * values into binary data and decoding binary data into JavaScript values.
  105. */
  106. export class AbiCoder {
  107. #getCoder(param: ParamType): Coder {
  108. if (param.isArray()) {
  109. return new ArrayCoder(this.#getCoder(param.arrayChildren), param.arrayLength, param.name);
  110. }
  111. if (param.isTuple()) {
  112. return new TupleCoder(param.components.map((c) => this.#getCoder(c)), param.name);
  113. }
  114. switch (param.baseType) {
  115. case "address":
  116. return new AddressCoder(param.name);
  117. case "bool":
  118. return new BooleanCoder(param.name);
  119. case "string":
  120. return new StringCoder(param.name);
  121. case "bytes":
  122. return new BytesCoder(param.name);
  123. case "":
  124. return new NullCoder(param.name);
  125. }
  126. // u?int[0-9]*
  127. let match = param.type.match(paramTypeNumber);
  128. if (match) {
  129. let size = parseInt(match[2] || "256");
  130. assertArgument(size !== 0 && size <= 256 && (size % 8) === 0,
  131. "invalid " + match[1] + " bit length", "param", param);
  132. return new NumberCoder(size / 8, (match[1] === "int"), param.name);
  133. }
  134. // bytes[0-9]+
  135. match = param.type.match(paramTypeBytes);
  136. if (match) {
  137. let size = parseInt(match[1]);
  138. assertArgument(size !== 0 && size <= 32, "invalid bytes length", "param", param);
  139. return new FixedBytesCoder(size, param.name);
  140. }
  141. assertArgument(false, "invalid type", "type", param.type);
  142. }
  143. /**
  144. * Get the default values for the given %%types%%.
  145. *
  146. * For example, a ``uint`` is by default ``0`` and ``bool``
  147. * is by default ``false``.
  148. */
  149. getDefaultValue(types: ReadonlyArray<string | ParamType>): Result {
  150. const coders: Array<Coder> = types.map((type) => this.#getCoder(ParamType.from(type)));
  151. const coder = new TupleCoder(coders, "_");
  152. return coder.defaultValue();
  153. }
  154. /**
  155. * Encode the %%values%% as the %%types%% into ABI data.
  156. *
  157. * @returns DataHexstring
  158. */
  159. encode(types: ReadonlyArray<string | ParamType>, values: ReadonlyArray<any>): string {
  160. assertArgumentCount(values.length, types.length, "types/values length mismatch");
  161. const coders = types.map((type) => this.#getCoder(ParamType.from(type)));
  162. const coder = (new TupleCoder(coders, "_"));
  163. const writer = new Writer();
  164. coder.encode(writer, values);
  165. return writer.data;
  166. }
  167. /**
  168. * Decode the ABI %%data%% as the %%types%% into values.
  169. *
  170. * If %%loose%% decoding is enabled, then strict padding is
  171. * not enforced. Some older versions of Solidity incorrectly
  172. * padded event data emitted from ``external`` functions.
  173. */
  174. decode(types: ReadonlyArray<string | ParamType>, data: BytesLike, loose?: boolean): Result {
  175. const coders: Array<Coder> = types.map((type) => this.#getCoder(ParamType.from(type)));
  176. const coder = new TupleCoder(coders, "_");
  177. return coder.decode(new Reader(data, loose, defaultMaxInflation));
  178. }
  179. static _setDefaultMaxInflation(value: number): void {
  180. assertArgument(typeof(value) === "number" && Number.isInteger(value), "invalid defaultMaxInflation factor", "value", value);
  181. defaultMaxInflation = value;
  182. }
  183. /**
  184. * Returns the shared singleton instance of a default [[AbiCoder]].
  185. *
  186. * On the first call, the instance is created internally.
  187. */
  188. static defaultAbiCoder(): AbiCoder {
  189. if (defaultCoder == null) {
  190. defaultCoder = new AbiCoder();
  191. }
  192. return defaultCoder;
  193. }
  194. /**
  195. * Returns an ethers-compatible [[CallExceptionError]] Error for the given
  196. * result %%data%% for the [[CallExceptionAction]] %%action%% against
  197. * the Transaction %%tx%%.
  198. */
  199. static getBuiltinCallException(action: CallExceptionAction, tx: { to?: null | string, from?: null | string, data?: string }, data: null | BytesLike): CallExceptionError {
  200. return getBuiltinCallException(action, tx, data, AbiCoder.defaultAbiCoder());
  201. }
  202. }