Added convenience functions for creating CALL_EXCEPTION Errors.
This commit is contained in:
parent
a667dcbebe
commit
9c45482fad
@ -14,7 +14,13 @@ import { StringCoder } from "./coders/string.js";
|
||||
import { TupleCoder } from "./coders/tuple.js";
|
||||
import { ParamType } from "./fragments.js";
|
||||
|
||||
import type { BytesLike } from "../utils/index.js";
|
||||
import { getAddress } from "../address/index.js";
|
||||
import { getBytes, hexlify, makeError } from "../utils/index.js";
|
||||
|
||||
import type {
|
||||
BytesLike,
|
||||
CallExceptionAction, CallExceptionError, CallExceptionTransaction
|
||||
} from "../utils/index.js";
|
||||
|
||||
|
||||
const paramTypeBytes = new RegExp(/^bytes([0-9]*)$/);
|
||||
@ -91,5 +97,80 @@ export class AbiCoder {
|
||||
}
|
||||
}
|
||||
|
||||
// https://docs.soliditylang.org/en/v0.8.17/control-structures.html
|
||||
const PanicReasons: Map<number, string> = 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");
|
||||
|
||||
export function getBuiltinCallException(action: CallExceptionAction, tx: { to?: null | string, from?: null | string, data?: string }, data: null | BytesLike): CallExceptionError {
|
||||
let message = "missing revert data";
|
||||
|
||||
let reason: null | string = null;
|
||||
const invocation = null;
|
||||
let revert: null | { signature: string, name: string, args: Array<any> } = null;
|
||||
|
||||
if (data) {
|
||||
message = "execution reverted";
|
||||
|
||||
const bytes = getBytes(data);
|
||||
data = hexlify(data);
|
||||
|
||||
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 = defaultAbiCoder.decode([ "string" ], bytes.slice(4))[0]
|
||||
revert = {
|
||||
signature: "Error(string)",
|
||||
name: "Error",
|
||||
args: [ reason ]
|
||||
};
|
||||
message += `: ${ JSON.stringify(reason) }`;
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
message += " (could not decode reason; invalid data)";
|
||||
}
|
||||
|
||||
} else if (hexlify(bytes.slice(0, 4)) === "0x4e487b71") {
|
||||
// Panic(uint256)
|
||||
try {
|
||||
const code = Number(defaultAbiCoder.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) {
|
||||
console.log(error);
|
||||
message += " (could not decode panic reason)";
|
||||
}
|
||||
} else {
|
||||
message += " (unknown custom error)";
|
||||
}
|
||||
}
|
||||
|
||||
const transaction: CallExceptionTransaction = {
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
export const defaultAbiCoder: AbiCoder = new AbiCoder();
|
||||
|
@ -613,7 +613,8 @@ export class ParamType {
|
||||
return value[param.name];
|
||||
});
|
||||
}
|
||||
if (value.length !== this.components.length) {
|
||||
|
||||
if (result.length !== this.components.length) {
|
||||
throw new Error("array is wrong length");
|
||||
}
|
||||
|
||||
@ -718,7 +719,7 @@ export class ParamType {
|
||||
return new ParamType(_guard, name, type, "array", indexed, null, arrayLength, arrayChildren);
|
||||
}
|
||||
|
||||
if (type.substring(0, 5) === "tuple(" || type[0] === "(") {
|
||||
if (type === "tuple" || type.substring(0, 5) === "tuple(" || type[0] === "(") {
|
||||
const comps = (obj.components != null) ? obj.components.map((c: any) => ParamType.from(c)): null;
|
||||
const tuple = new ParamType(_guard, name, type, "tuple", indexed, comps, null, null);
|
||||
// @TODO: use lexer to validate and normalize type
|
||||
|
@ -4,7 +4,8 @@
|
||||
//////
|
||||
export {
|
||||
AbiCoder,
|
||||
defaultAbiCoder
|
||||
defaultAbiCoder,
|
||||
getBuiltinCallException
|
||||
} from "./abi-coder.js";
|
||||
|
||||
export { decodeBytes32String, encodeBytes32String } from "./bytes32.js";
|
||||
|
@ -1,17 +1,17 @@
|
||||
import { keccak256 } from "../crypto/index.js"
|
||||
import { id } from "../hash/index.js"
|
||||
import {
|
||||
concat, dataSlice, getBigInt, getBytes, getBytesCopy, hexlify,
|
||||
zeroPadValue, isHexString, defineProperties, throwArgumentError, toHex,
|
||||
throwError, makeError
|
||||
concat, dataSlice, getBigInt, getBytes, getBytesCopy,
|
||||
hexlify, zeroPadValue, isHexString, defineProperties, throwArgumentError, toHex,
|
||||
throwError
|
||||
} from "../utils/index.js";
|
||||
|
||||
import { AbiCoder, defaultAbiCoder } from "./abi-coder.js";
|
||||
import { AbiCoder, defaultAbiCoder, getBuiltinCallException } from "./abi-coder.js";
|
||||
import { checkResultErrors, Result } from "./coders/abstract-coder.js";
|
||||
import { ConstructorFragment, ErrorFragment, EventFragment, Fragment, FunctionFragment, ParamType } from "./fragments.js";
|
||||
import { Typed } from "./typed.js";
|
||||
|
||||
import type { BigNumberish, BytesLike } from "../utils/index.js";
|
||||
import type { BigNumberish, BytesLike, CallExceptionError, CallExceptionTransaction } from "../utils/index.js";
|
||||
|
||||
import type { JsonFragment } from "./fragments.js";
|
||||
|
||||
@ -662,61 +662,45 @@ export class Interface {
|
||||
});
|
||||
}
|
||||
|
||||
makeError(fragment: FunctionFragment | string, _data: BytesLike, tx?: { data: string }): Error {
|
||||
if (typeof(fragment) === "string") { fragment = this.getFunction(fragment); }
|
||||
makeError(_data: BytesLike, tx: CallExceptionTransaction): CallExceptionError {
|
||||
const data = getBytes(_data, "data");
|
||||
|
||||
const data = getBytes(_data);
|
||||
const error = getBuiltinCallException("call", tx, data);
|
||||
|
||||
let args: undefined | Result = undefined;
|
||||
if (tx) {
|
||||
try {
|
||||
args = this.#abiCoder.decode(fragment.inputs, tx.data || "0x");
|
||||
} catch (error) { console.log(error); }
|
||||
}
|
||||
|
||||
let errorArgs: undefined | Result = undefined;
|
||||
let errorName: undefined | string = undefined;
|
||||
let errorSignature: undefined | string = undefined;
|
||||
let reason: string = "unknown reason";
|
||||
|
||||
if (data.length === 0) {
|
||||
reason = "missing error reason";
|
||||
|
||||
} else if ((data.length % 32) === 4) {
|
||||
// Not a built-in error; try finding a custom error
|
||||
if (!error.message.match(/could not decode/)) {
|
||||
const selector = hexlify(data.slice(0, 4));
|
||||
const builtin = BuiltinErrors[selector];
|
||||
if (builtin) {
|
||||
|
||||
error.message = "execution reverted (unknown custom error)";
|
||||
try {
|
||||
const ef = this.getError(selector);
|
||||
try {
|
||||
errorName = builtin.name;
|
||||
errorSignature = builtin.signature;
|
||||
errorArgs = this.#abiCoder.decode(builtin.inputs, data.slice(4));
|
||||
reason = builtin.reason(...errorArgs);
|
||||
} catch (error) {
|
||||
console.log(error); // @TODO: remove
|
||||
}
|
||||
} else {
|
||||
reason = "unknown custom error";
|
||||
try {
|
||||
const error = this.getError(selector);
|
||||
errorName = error.name;
|
||||
errorSignature = error.format();
|
||||
reason = `custom error: ${ errorSignature }`;
|
||||
try {
|
||||
errorArgs = this.#abiCoder.decode(error.inputs, data.slice(4));
|
||||
} catch (error) {
|
||||
reason = `custom error: ${ errorSignature } (coult not decode error data)`
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error); // @TODO: remove
|
||||
error.revert = {
|
||||
name: ef.name,
|
||||
signature: ef.format(),
|
||||
args: this.#abiCoder.decode(ef.inputs, data.slice(4))
|
||||
};
|
||||
error.reason = error.revert.signature;
|
||||
error.message = `execution reverted: ${ error.reason }`
|
||||
} catch (e) {
|
||||
error.message = `execution reverted (coult not decode custom error)`
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error); // @TODO: remove
|
||||
}
|
||||
}
|
||||
|
||||
return makeError("call revert exception", "CALL_EXCEPTION", {
|
||||
data: hexlify(data), transaction: null,
|
||||
method: fragment.name, signature: fragment.format(), args,
|
||||
errorArgs, errorName, errorSignature, reason
|
||||
});
|
||||
// Add the invocation, if available
|
||||
const parsed = this.parseTransaction(tx);
|
||||
if (parsed) {
|
||||
error.invocation = {
|
||||
method: parsed.name,
|
||||
signature: parsed.signature,
|
||||
args: parsed.args
|
||||
};
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user