123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- /**
- * When sending values to or receiving values from a [[Contract]], the
- * data is generally encoded using the [ABI standard](link-solc-abi).
- *
- * The AbiCoder provides a utility to encode values to ABI data and
- * decode values from ABI data.
- *
- * Most of the time, developers should favour the [[Contract]] class,
- * which further abstracts a lot of the finer details of ABI data.
- *
- * @_section api/abi/abi-coder:ABI Encoding
- */
- // See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
- import { assertArgumentCount, assertArgument } from "../utils/index.js";
- import { Reader, Writer } from "./coders/abstract-coder.js";
- import { AddressCoder } from "./coders/address.js";
- import { ArrayCoder } from "./coders/array.js";
- import { BooleanCoder } from "./coders/boolean.js";
- import { BytesCoder } from "./coders/bytes.js";
- import { FixedBytesCoder } from "./coders/fixed-bytes.js";
- import { NullCoder } from "./coders/null.js";
- import { NumberCoder } from "./coders/number.js";
- import { StringCoder } from "./coders/string.js";
- import { TupleCoder } from "./coders/tuple.js";
- import { ParamType } from "./fragments.js";
- import { getAddress } from "../address/index.js";
- import { getBytes, hexlify, makeError } from "../utils/index.js";
- // https://docs.soliditylang.org/en/v0.8.17/control-structures.html
- const PanicReasons = new Map();
- PanicReasons.set(0x00, "GENERIC_PANIC");
- PanicReasons.set(0x01, "ASSERT_FALSE");
- PanicReasons.set(0x11, "OVERFLOW");
- PanicReasons.set(0x12, "DIVIDE_BY_ZERO");
- PanicReasons.set(0x21, "ENUM_RANGE_ERROR");
- PanicReasons.set(0x22, "BAD_STORAGE_DATA");
- PanicReasons.set(0x31, "STACK_UNDERFLOW");
- PanicReasons.set(0x32, "ARRAY_RANGE_ERROR");
- PanicReasons.set(0x41, "OUT_OF_MEMORY");
- PanicReasons.set(0x51, "UNINITIALIZED_FUNCTION_CALL");
- const paramTypeBytes = new RegExp(/^bytes([0-9]*)$/);
- const paramTypeNumber = new RegExp(/^(u?int)([0-9]*)$/);
- let defaultCoder = null;
- let defaultMaxInflation = 1024;
- function getBuiltinCallException(action, tx, data, abiCoder) {
- let message = "missing revert data";
- let reason = null;
- const invocation = null;
- let revert = null;
- if (data) {
- message = "execution reverted";
- const bytes = getBytes(data);
- data = hexlify(data);
- if (bytes.length === 0) {
- message += " (no data present; likely require(false) occurred";
- reason = "require(false)";
- }
- else if (bytes.length % 32 !== 4) {
- message += " (could not decode reason; invalid data length)";
- }
- else if (hexlify(bytes.slice(0, 4)) === "0x08c379a0") {
- // Error(string)
- try {
- reason = abiCoder.decode(["string"], bytes.slice(4))[0];
- revert = {
- signature: "Error(string)",
- name: "Error",
- args: [reason]
- };
- message += `: ${JSON.stringify(reason)}`;
- }
- catch (error) {
- message += " (could not decode reason; invalid string data)";
- }
- }
- else if (hexlify(bytes.slice(0, 4)) === "0x4e487b71") {
- // Panic(uint256)
- try {
- const code = Number(abiCoder.decode(["uint256"], bytes.slice(4))[0]);
- revert = {
- signature: "Panic(uint256)",
- name: "Panic",
- args: [code]
- };
- reason = `Panic due to ${PanicReasons.get(code) || "UNKNOWN"}(${code})`;
- message += `: ${reason}`;
- }
- catch (error) {
- message += " (could not decode panic code)";
- }
- }
- else {
- message += " (unknown custom error)";
- }
- }
- const transaction = {
- to: (tx.to ? getAddress(tx.to) : null),
- data: (tx.data || "0x")
- };
- if (tx.from) {
- transaction.from = getAddress(tx.from);
- }
- return makeError(message, "CALL_EXCEPTION", {
- action, data, reason, transaction, invocation, revert
- });
- }
- /**
- * The **AbiCoder** is a low-level class responsible for encoding JavaScript
- * values into binary data and decoding binary data into JavaScript values.
- */
- export class AbiCoder {
- #getCoder(param) {
- if (param.isArray()) {
- return new ArrayCoder(this.#getCoder(param.arrayChildren), param.arrayLength, param.name);
- }
- if (param.isTuple()) {
- return new TupleCoder(param.components.map((c) => this.#getCoder(c)), param.name);
- }
- switch (param.baseType) {
- case "address":
- return new AddressCoder(param.name);
- case "bool":
- return new BooleanCoder(param.name);
- case "string":
- return new StringCoder(param.name);
- case "bytes":
- return new BytesCoder(param.name);
- case "":
- return new NullCoder(param.name);
- }
- // u?int[0-9]*
- let match = param.type.match(paramTypeNumber);
- if (match) {
- let size = parseInt(match[2] || "256");
- assertArgument(size !== 0 && size <= 256 && (size % 8) === 0, "invalid " + match[1] + " bit length", "param", param);
- return new NumberCoder(size / 8, (match[1] === "int"), param.name);
- }
- // bytes[0-9]+
- match = param.type.match(paramTypeBytes);
- if (match) {
- let size = parseInt(match[1]);
- assertArgument(size !== 0 && size <= 32, "invalid bytes length", "param", param);
- return new FixedBytesCoder(size, param.name);
- }
- assertArgument(false, "invalid type", "type", param.type);
- }
- /**
- * Get the default values for the given %%types%%.
- *
- * For example, a ``uint`` is by default ``0`` and ``bool``
- * is by default ``false``.
- */
- getDefaultValue(types) {
- const coders = types.map((type) => this.#getCoder(ParamType.from(type)));
- const coder = new TupleCoder(coders, "_");
- return coder.defaultValue();
- }
- /**
- * Encode the %%values%% as the %%types%% into ABI data.
- *
- * @returns DataHexstring
- */
- encode(types, values) {
- assertArgumentCount(values.length, types.length, "types/values length mismatch");
- const coders = types.map((type) => this.#getCoder(ParamType.from(type)));
- const coder = (new TupleCoder(coders, "_"));
- const writer = new Writer();
- coder.encode(writer, values);
- return writer.data;
- }
- /**
- * Decode the ABI %%data%% as the %%types%% into values.
- *
- * If %%loose%% decoding is enabled, then strict padding is
- * not enforced. Some older versions of Solidity incorrectly
- * padded event data emitted from ``external`` functions.
- */
- decode(types, data, loose) {
- const coders = types.map((type) => this.#getCoder(ParamType.from(type)));
- const coder = new TupleCoder(coders, "_");
- return coder.decode(new Reader(data, loose, defaultMaxInflation));
- }
- static _setDefaultMaxInflation(value) {
- assertArgument(typeof (value) === "number" && Number.isInteger(value), "invalid defaultMaxInflation factor", "value", value);
- defaultMaxInflation = value;
- }
- /**
- * Returns the shared singleton instance of a default [[AbiCoder]].
- *
- * On the first call, the instance is created internally.
- */
- static defaultAbiCoder() {
- if (defaultCoder == null) {
- defaultCoder = new AbiCoder();
- }
- return defaultCoder;
- }
- /**
- * Returns an ethers-compatible [[CallExceptionError]] Error for the given
- * result %%data%% for the [[CallExceptionAction]] %%action%% against
- * the Transaction %%tx%%.
- */
- static getBuiltinCallException(action, tx, data) {
- return getBuiltinCallException(action, tx, data, AbiCoder.defaultAbiCoder());
- }
- }
- //# sourceMappingURL=abi-coder.js.map
|