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 { TupleCoder } from "./coders/tuple.js";
|
||||||
import { ParamType } from "./fragments.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]*)$/);
|
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();
|
export const defaultAbiCoder: AbiCoder = new AbiCoder();
|
||||||
|
@ -613,7 +613,8 @@ export class ParamType {
|
|||||||
return value[param.name];
|
return value[param.name];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (value.length !== this.components.length) {
|
|
||||||
|
if (result.length !== this.components.length) {
|
||||||
throw new Error("array is wrong 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);
|
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 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);
|
const tuple = new ParamType(_guard, name, type, "tuple", indexed, comps, null, null);
|
||||||
// @TODO: use lexer to validate and normalize type
|
// @TODO: use lexer to validate and normalize type
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
//////
|
//////
|
||||||
export {
|
export {
|
||||||
AbiCoder,
|
AbiCoder,
|
||||||
defaultAbiCoder
|
defaultAbiCoder,
|
||||||
|
getBuiltinCallException
|
||||||
} from "./abi-coder.js";
|
} from "./abi-coder.js";
|
||||||
|
|
||||||
export { decodeBytes32String, encodeBytes32String } from "./bytes32.js";
|
export { decodeBytes32String, encodeBytes32String } from "./bytes32.js";
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { keccak256 } from "../crypto/index.js"
|
import { keccak256 } from "../crypto/index.js"
|
||||||
import { id } from "../hash/index.js"
|
import { id } from "../hash/index.js"
|
||||||
import {
|
import {
|
||||||
concat, dataSlice, getBigInt, getBytes, getBytesCopy, hexlify,
|
concat, dataSlice, getBigInt, getBytes, getBytesCopy,
|
||||||
zeroPadValue, isHexString, defineProperties, throwArgumentError, toHex,
|
hexlify, zeroPadValue, isHexString, defineProperties, throwArgumentError, toHex,
|
||||||
throwError, makeError
|
throwError
|
||||||
} from "../utils/index.js";
|
} 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 { checkResultErrors, Result } from "./coders/abstract-coder.js";
|
||||||
import { ConstructorFragment, ErrorFragment, EventFragment, Fragment, FunctionFragment, ParamType } from "./fragments.js";
|
import { ConstructorFragment, ErrorFragment, EventFragment, Fragment, FunctionFragment, ParamType } from "./fragments.js";
|
||||||
import { Typed } from "./typed.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";
|
import type { JsonFragment } from "./fragments.js";
|
||||||
|
|
||||||
@ -662,61 +662,45 @@ export class Interface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
makeError(fragment: FunctionFragment | string, _data: BytesLike, tx?: { data: string }): Error {
|
makeError(_data: BytesLike, tx: CallExceptionTransaction): CallExceptionError {
|
||||||
if (typeof(fragment) === "string") { fragment = this.getFunction(fragment); }
|
const data = getBytes(_data, "data");
|
||||||
|
|
||||||
const data = getBytes(_data);
|
const error = getBuiltinCallException("call", tx, data);
|
||||||
|
|
||||||
let args: undefined | Result = undefined;
|
// Not a built-in error; try finding a custom error
|
||||||
if (tx) {
|
if (!error.message.match(/could not decode/)) {
|
||||||
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) {
|
|
||||||
const selector = hexlify(data.slice(0, 4));
|
const selector = hexlify(data.slice(0, 4));
|
||||||
const builtin = BuiltinErrors[selector];
|
|
||||||
if (builtin) {
|
error.message = "execution reverted (unknown custom error)";
|
||||||
try {
|
try {
|
||||||
errorName = builtin.name;
|
const ef = this.getError(selector);
|
||||||
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 {
|
try {
|
||||||
const error = this.getError(selector);
|
error.revert = {
|
||||||
errorName = error.name;
|
name: ef.name,
|
||||||
errorSignature = error.format();
|
signature: ef.format(),
|
||||||
reason = `custom error: ${ errorSignature }`;
|
args: this.#abiCoder.decode(ef.inputs, data.slice(4))
|
||||||
try {
|
};
|
||||||
errorArgs = this.#abiCoder.decode(error.inputs, data.slice(4));
|
error.reason = error.revert.signature;
|
||||||
} catch (error) {
|
error.message = `execution reverted: ${ error.reason }`
|
||||||
reason = `custom error: ${ errorSignature } (coult not decode error data)`
|
} catch (e) {
|
||||||
|
error.message = `execution reverted (coult not decode custom error)`
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error); // @TODO: remove
|
console.log(error); // @TODO: remove
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return makeError("call revert exception", "CALL_EXCEPTION", {
|
// Add the invocation, if available
|
||||||
data: hexlify(data), transaction: null,
|
const parsed = this.parseTransaction(tx);
|
||||||
method: fragment.name, signature: fragment.format(), args,
|
if (parsed) {
|
||||||
errorArgs, errorName, errorSignature, reason
|
error.invocation = {
|
||||||
});
|
method: parsed.name,
|
||||||
|
signature: parsed.signature,
|
||||||
|
args: parsed.args
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user