hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
312 lines • 14.8 kB
JavaScript
import { ReturnData } from "@nomicfoundation/edr";
import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors";
import { bytesToHexString } from "@nomicfoundation/hardhat-utils/bytes";
import { panicErrorCodeToMessage } from "@nomicfoundation/hardhat-utils/panic-errors";
import { StackTraceEntryType, CONSTRUCTOR_FUNCTION_NAME, PRECOMPILE_FUNCTION_NAME, UNKNOWN_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, UNRECOGNIZED_FUNCTION_NAME, } from "./solidity-stack-trace.js";
export function createSolidityErrorWithStackTrace(fallbackMessage, stackTrace, data, transactionHash) {
const originalPrepareStackTrace = Error.prepareStackTrace;
try {
Error.prepareStackTrace = (error, stack) => {
// Skip error management related stack traces
const adjustedStack = stack.slice(1);
for (const entry of stackTrace) {
const callsite = encodeStackTraceEntry(entry);
if (callsite !== undefined) {
adjustedStack.unshift(callsite);
}
}
assertHardhatInvariant(originalPrepareStackTrace !== undefined, "Error.prepareStackTrace should be defined");
return originalPrepareStackTrace(error, adjustedStack);
};
const message = getMessageFromLastStackTraceEntry(stackTrace[stackTrace.length - 1]) ??
fallbackMessage;
const solidityError = new SolidityError(message, stackTrace, data, transactionHash);
/* eslint-disable-next-line @typescript-eslint/no-unused-expressions
-- As the stack property is lazy-loaded in v8, we need to access it
to trigger the custom prepareStackTrace logic */
solidityError.stack;
return solidityError;
}
finally {
Error.prepareStackTrace = originalPrepareStackTrace;
}
}
export function encodeStackTraceEntry(stackTraceEntry) {
switch (stackTraceEntry.type) {
case StackTraceEntryType.UNRECOGNIZED_FUNCTION_WITHOUT_FALLBACK_ERROR:
case StackTraceEntryType.MISSING_FALLBACK_OR_RECEIVE_ERROR:
return sourceReferenceToSolidityCallsite({
...stackTraceEntry.sourceReference,
function: UNRECOGNIZED_FUNCTION_NAME,
});
case StackTraceEntryType.CALLSTACK_ENTRY:
case StackTraceEntryType.REVERT_ERROR:
case StackTraceEntryType.CUSTOM_ERROR:
case StackTraceEntryType.FUNCTION_NOT_PAYABLE_ERROR:
case StackTraceEntryType.INVALID_PARAMS_ERROR:
case StackTraceEntryType.FALLBACK_NOT_PAYABLE_ERROR:
case StackTraceEntryType.FALLBACK_NOT_PAYABLE_AND_NO_RECEIVE_ERROR:
case StackTraceEntryType.RETURNDATA_SIZE_ERROR:
case StackTraceEntryType.NONCONTRACT_ACCOUNT_CALLED_ERROR:
case StackTraceEntryType.CALL_FAILED_ERROR:
case StackTraceEntryType.CHEATCODE_ERROR:
case StackTraceEntryType.DIRECT_LIBRARY_CALL_ERROR:
return sourceReferenceToSolidityCallsite(stackTraceEntry.sourceReference);
case StackTraceEntryType.UNRECOGNIZED_CREATE_CALLSTACK_ENTRY:
return new SolidityCallSite(undefined, UNRECOGNIZED_CONTRACT_NAME, CONSTRUCTOR_FUNCTION_NAME, undefined);
case StackTraceEntryType.UNRECOGNIZED_CONTRACT_CALLSTACK_ENTRY:
return new SolidityCallSite(bytesToHexString(stackTraceEntry.address), UNRECOGNIZED_CONTRACT_NAME, UNKNOWN_FUNCTION_NAME, undefined);
case StackTraceEntryType.PRECOMPILE_ERROR:
return new SolidityCallSite(undefined, `<PrecompileContract ${stackTraceEntry.precompile}>`, PRECOMPILE_FUNCTION_NAME, undefined);
case StackTraceEntryType.UNRECOGNIZED_CREATE_ERROR:
return new SolidityCallSite(undefined, UNRECOGNIZED_CONTRACT_NAME, CONSTRUCTOR_FUNCTION_NAME, undefined);
case StackTraceEntryType.UNRECOGNIZED_CONTRACT_ERROR:
return new SolidityCallSite(bytesToHexString(stackTraceEntry.address), UNRECOGNIZED_CONTRACT_NAME, UNKNOWN_FUNCTION_NAME, undefined);
case StackTraceEntryType.INTERNAL_FUNCTION_CALLSTACK_ENTRY:
return new SolidityCallSite(stackTraceEntry.sourceReference.sourceName, stackTraceEntry.sourceReference.contract, `internal@${stackTraceEntry.pc}`, undefined);
case StackTraceEntryType.CONTRACT_CALL_RUN_OUT_OF_GAS_ERROR:
if (stackTraceEntry.sourceReference !== undefined) {
return sourceReferenceToSolidityCallsite(stackTraceEntry.sourceReference);
}
return new SolidityCallSite(undefined, UNRECOGNIZED_CONTRACT_NAME, UNKNOWN_FUNCTION_NAME, undefined);
case StackTraceEntryType.OTHER_EXECUTION_ERROR:
case StackTraceEntryType.CONTRACT_TOO_LARGE_ERROR:
case StackTraceEntryType.PANIC_ERROR:
case StackTraceEntryType.UNMAPPED_SOLC_0_6_3_REVERT_ERROR:
if (stackTraceEntry.sourceReference === undefined) {
return new SolidityCallSite(undefined, UNRECOGNIZED_CONTRACT_NAME, UNKNOWN_FUNCTION_NAME, undefined);
}
return sourceReferenceToSolidityCallsite(stackTraceEntry.sourceReference);
}
}
function sourceReferenceToSolidityCallsite(sourceReference) {
return new SolidityCallSite(sourceReference.sourceName, sourceReference.contract, sourceReference.function ?? UNKNOWN_FUNCTION_NAME, sourceReference.line);
}
function getMessageFromLastStackTraceEntry(stackTraceEntry) {
switch (stackTraceEntry.type) {
case StackTraceEntryType.PRECOMPILE_ERROR:
return `Transaction reverted: call to precompile ${stackTraceEntry.precompile} failed`;
case StackTraceEntryType.FUNCTION_NOT_PAYABLE_ERROR:
return `Transaction reverted: non-payable function was called with value ${stackTraceEntry.value.toString(10)}`;
case StackTraceEntryType.INVALID_PARAMS_ERROR:
return `Transaction reverted: function was called with incorrect parameters`;
case StackTraceEntryType.FALLBACK_NOT_PAYABLE_ERROR:
return `Transaction reverted: fallback function is not payable and was called with value ${stackTraceEntry.value.toString(10)}`;
case StackTraceEntryType.FALLBACK_NOT_PAYABLE_AND_NO_RECEIVE_ERROR:
return `Transaction reverted: there's no receive function, fallback function is not payable and was called with value ${stackTraceEntry.value.toString(10)}`;
case StackTraceEntryType.UNRECOGNIZED_FUNCTION_WITHOUT_FALLBACK_ERROR:
return `Transaction reverted: function selector was not recognized and there's no fallback function`;
case StackTraceEntryType.MISSING_FALLBACK_OR_RECEIVE_ERROR:
return `Transaction reverted: function selector was not recognized and there's no fallback nor receive function`;
case StackTraceEntryType.RETURNDATA_SIZE_ERROR:
return `Transaction reverted: function returned an unexpected amount of data`;
case StackTraceEntryType.NONCONTRACT_ACCOUNT_CALLED_ERROR:
return `Transaction reverted: function call to a non-contract account`;
case StackTraceEntryType.CALL_FAILED_ERROR:
return `Transaction reverted: function call failed to execute`;
case StackTraceEntryType.DIRECT_LIBRARY_CALL_ERROR:
return `Transaction reverted: library was called directly`;
case StackTraceEntryType.UNRECOGNIZED_CREATE_ERROR:
case StackTraceEntryType.UNRECOGNIZED_CONTRACT_ERROR: {
const returnData = new ReturnData(stackTraceEntry.returnData);
if (returnData.isErrorReturnData()) {
return `VM Exception while processing transaction: reverted with reason string '${returnData.decodeError()}'`;
}
if (returnData.isPanicReturnData()) {
const message = panicErrorCodeToMessage(returnData.decodePanic());
return `VM Exception while processing transaction: ${message}`;
}
if (!returnData.isEmpty()) {
return `VM Exception while processing transaction: reverted with an unrecognized custom error (return data: ${bytesToHexString(returnData.value)})`;
}
if (stackTraceEntry.isInvalidOpcodeError) {
return "VM Exception while processing transaction: invalid opcode";
}
return "Transaction reverted without a reason string";
}
case StackTraceEntryType.REVERT_ERROR: {
const returnData = new ReturnData(stackTraceEntry.returnData);
if (returnData.isErrorReturnData()) {
return `VM Exception while processing transaction: reverted with reason string '${returnData.decodeError()}'`;
}
if (stackTraceEntry.isInvalidOpcodeError) {
return "VM Exception while processing transaction: invalid opcode";
}
return "Transaction reverted without a reason string";
}
case StackTraceEntryType.PANIC_ERROR:
const panicMessage = panicErrorCodeToMessage(stackTraceEntry.errorCode);
return `VM Exception while processing transaction: ${panicMessage}`;
case StackTraceEntryType.CUSTOM_ERROR:
case StackTraceEntryType.CHEATCODE_ERROR:
return `VM Exception while processing transaction: ${stackTraceEntry.message}`;
case StackTraceEntryType.OTHER_EXECUTION_ERROR:
// TODO: What if there was returnData?
return `Transaction reverted and Hardhat couldn't infer the reason.`;
case StackTraceEntryType.UNMAPPED_SOLC_0_6_3_REVERT_ERROR:
return "Transaction reverted without a reason string and without a valid sourcemap provided by the compiler. Some line numbers may be off. We strongly recommend upgrading solc and always using revert reasons.";
case StackTraceEntryType.CONTRACT_TOO_LARGE_ERROR:
return "Transaction reverted: trying to deploy a contract whose code is too large";
case StackTraceEntryType.CONTRACT_CALL_RUN_OUT_OF_GAS_ERROR:
return "Transaction reverted: contract call run out of gas and made the transaction revert";
/* These types are not expected to be the last entry in the stack trace, as
their presence indicates that another frame should follow in the call stack. */
case StackTraceEntryType.CALLSTACK_ENTRY:
case StackTraceEntryType.UNRECOGNIZED_CREATE_CALLSTACK_ENTRY:
case StackTraceEntryType.UNRECOGNIZED_CONTRACT_CALLSTACK_ENTRY:
case StackTraceEntryType.INTERNAL_FUNCTION_CALLSTACK_ENTRY:
return undefined;
}
}
/**
* Note: This error class MUST NOT extend ProviderError, as libraries use the
* code property to detect if they are dealing with a JSON-RPC error, and take
* control of errors.
**/
export class SolidityError extends Error {
stackTrace;
data;
transactionHash;
constructor(message, stackTrace, data, transactionHash) {
super(message);
this.stackTrace = stackTrace;
this.data = data;
this.transactionHash = transactionHash;
Object.defineProperty(this, Symbol.for("nodejs.util.inspect.custom"), {
value: () => this.stack !== undefined
? this.stack
: "Internal error when encoding SolidityError",
writable: false,
enumerable: false,
configurable: true,
});
}
}
export class SolidityCallSite {
#sourceName;
#contract;
#functionName;
#line;
constructor(_sourceName, _contract, _functionName, _line) {
// If the source name starts with `project/` that means that's a local
// source name, and we remove that.
const LOCAL_SOURCE_NAME_PREFIX = "project/";
this.#sourceName =
_sourceName?.startsWith(LOCAL_SOURCE_NAME_PREFIX) === true
? _sourceName.substring(LOCAL_SOURCE_NAME_PREFIX.length)
: _sourceName;
this.#contract = _contract;
this.#functionName = _functionName;
this.#line = _line;
}
getColumnNumber() {
return null;
}
getEvalOrigin() {
return undefined;
}
getFileName() {
return this.#sourceName ?? "unknown";
}
getFunction() {
return undefined;
}
getFunctionName() {
// if it's a top-level function, we print its name
if (this.#contract === undefined) {
return this.#functionName ?? null;
}
return null;
}
getLineNumber() {
return this.#line !== undefined ? this.#line : null;
}
getMethodName() {
if (this.#contract !== undefined) {
return this.#functionName ?? null;
}
return null;
}
getPosition() {
return 0;
}
getPromiseIndex() {
return 0;
}
getScriptNameOrSourceURL() {
return "";
}
getThis() {
return undefined;
}
getTypeName() {
return this.#contract ?? null;
}
isAsync() {
return false;
}
isConstructor() {
return false;
}
isEval() {
return false;
}
isNative() {
return false;
}
isPromiseAll() {
return false;
}
isToplevel() {
return false;
}
getScriptHash() {
return "";
}
getEnclosingColumnNumber() {
return 0;
}
getEnclosingLineNumber() {
return 0;
}
// Extracted and adapted from source-map-support package, which extracts it from V8
toString() {
let fileLocation = this.getFileName();
if (this.getLineNumber() !== null) {
fileLocation += `:${this.getLineNumber()}`;
}
let line = "";
const functionName = this.getFunctionName();
let addSuffix = true;
const isConstructor = this.isConstructor();
const isMethodCall = !(this.isToplevel() || isConstructor);
if (isMethodCall) {
const typeName = this.getTypeName();
const methodName = this.getMethodName();
if (functionName !== null) {
if (typeName !== null) {
line += typeName + ".";
}
line += functionName;
}
else {
line += typeName + "." + (methodName ?? "<anonymous>");
}
}
else if (isConstructor) {
line += "new " + (functionName ?? "<anonymous>");
}
else if (functionName !== null) {
line += functionName;
}
else {
line += fileLocation;
addSuffix = false;
}
if (addSuffix) {
line += " (" + fileLocation + ")";
}
return line;
}
}
//# sourceMappingURL=stack-trace-solidity-errors.js.map