hardhat-tracer
Advanced tools
Comparing version 1.1.1 to 2.0.0-beta.1
@@ -16,2 +16,5 @@ export declare const colorLabel: import("chalk").Chalk & { | ||
}; | ||
export declare const colorConsole: import("chalk").Chalk & { | ||
supportsColor: import("chalk").ColorSupport; | ||
}; | ||
export declare const colorKey: import("chalk").Chalk & { | ||
@@ -18,0 +21,0 @@ supportsColor: import("chalk").ColorSupport; |
@@ -6,3 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.colorWarning = exports.colorIndexed = exports.colorNameTag = exports.colorSstore = exports.colorSload = exports.colorKey = exports.colorError = exports.colorEvent = exports.colorFunction = exports.colorContract = exports.colorLabel = void 0; | ||
exports.colorWarning = exports.colorIndexed = exports.colorNameTag = exports.colorSstore = exports.colorSload = exports.colorKey = exports.colorConsole = exports.colorError = exports.colorEvent = exports.colorFunction = exports.colorContract = exports.colorLabel = void 0; | ||
const chalk_1 = __importDefault(require("chalk")); | ||
@@ -14,2 +14,3 @@ exports.colorLabel = chalk_1.default.white; | ||
exports.colorError = chalk_1.default.red; | ||
exports.colorConsole = chalk_1.default.blue; | ||
exports.colorKey = chalk_1.default.magenta; | ||
@@ -16,0 +17,0 @@ exports.colorSload = chalk_1.default.blueBright; |
@@ -6,4 +6,29 @@ "use strict"; | ||
(0, config_1.extendConfig)((config, userConfig) => { | ||
config.tracer = (0, utils_1.getTracerEnvFromUserInput)(userConfig.tracer); | ||
// config.tracer = getTracerEnvFromUserInput(userConfig.tracer); | ||
const opcodes = new Map(); | ||
// always active opcodes | ||
const opcodesToActivate = []; | ||
if (userConfig.tracer?.opcodes) { | ||
if (!Array.isArray(userConfig.tracer.opcodes)) { | ||
throw new Error("tracer.opcodes in hardhat user config should be array"); | ||
} | ||
opcodesToActivate.push(...userConfig.tracer.opcodes); | ||
} | ||
for (const opcode of opcodesToActivate) { | ||
opcodes.set(opcode, true); | ||
} | ||
config.tracer = { | ||
enabled: userConfig.tracer?.enabled ?? false, | ||
ignoreNext: false, | ||
verbosity: userConfig.tracer?.defaultVerbosity ?? utils_1.DEFAULT_VERBOSITY, | ||
gasCost: userConfig.tracer?.gasCost ?? false, | ||
opcodes, | ||
nameTags: userConfig.tracer?.nameTags ?? {}, | ||
// @ts-ignore TODO remove, this has no place in "config" | ||
_internal: { | ||
printNameTagTip: undefined, | ||
}, | ||
stateOverrides: userConfig.tracer?.stateOverrides, | ||
}; | ||
}); | ||
//# sourceMappingURL=config.js.map |
@@ -6,6 +6,22 @@ "use strict"; | ||
require("hardhat/types/runtime"); | ||
const get_vm_1 = require("../get-vm"); | ||
const recorder_1 = require("../trace/recorder"); | ||
const decoder_1 = require("../decoder"); | ||
(0, config_1.extendEnvironment)((hre) => { | ||
// copy reference of config.tracer to tracer | ||
// TODO take this properly, env can contain things that config does not need to. | ||
hre.tracer = hre.config.tracer; | ||
hre.tracer.decoder = new decoder_1.Decoder(hre.artifacts); | ||
// @ts-ignore | ||
global.tracerEnv = hre.tracer; | ||
(0, get_vm_1.getVM)(hre).then((vm) => { | ||
hre.tracer.recorder = new recorder_1.TraceRecorder(vm, hre.tracer); | ||
// vm.on("beforeTx", handleBeforeTx); | ||
// vm.on("beforeMessage", handleBeforeMessage); | ||
// vm.on("newContract", handleNewContract); | ||
// vm.on("step", handleStep); | ||
// vm.on("afterMessage", handleAfterMessage); | ||
// vm.on("afterTx", handleAfterTx); | ||
}); | ||
}); | ||
//# sourceMappingURL=hre.js.map |
import { BigNumberish } from "ethers"; | ||
import { TracerDependenciesExtended } from "../types"; | ||
export declare function formatCall(to: string, input: string, ret: string, value: BigNumberish, gas: BigNumberish, dependencies: TracerDependenciesExtended): Promise<string>; | ||
import { TracerDependencies } from "../types"; | ||
export declare function formatCall(to: string, input: string, ret: string, value: BigNumberish, gas: BigNumberish, dependencies: TracerDependencies): Promise<string>; | ||
//# sourceMappingURL=call.d.ts.map |
@@ -5,54 +5,71 @@ "use strict"; | ||
const ethers_1 = require("ethers"); | ||
const utils_1 = require("ethers/lib/utils"); | ||
const colors_1 = require("../colors"); | ||
const utils_2 = require("../utils"); | ||
const utils_1 = require("../utils"); | ||
const param_1 = require("./param"); | ||
const result_1 = require("./result"); | ||
const separator_1 = require("./separator"); | ||
async function formatCall(to, input, ret, value, gas, dependencies) { | ||
const toBytecode = await dependencies.provider.send("eth_getCode", [to]); | ||
const names = await dependencies.artifacts.getAllFullyQualifiedNames(); | ||
// TODO handle if `to` is console.log address | ||
let contractName; | ||
let result; | ||
let result2; | ||
let functionFragment; | ||
for (const name of names) { | ||
const _artifact = await dependencies.artifacts.readArtifact(name); | ||
const iface = new utils_1.Interface(_artifact.abi); | ||
// try to find the contract name | ||
if ((0, utils_2.compareBytecode)(_artifact.deployedBytecode, toBytecode) > 0.5 || | ||
(to === ethers_1.ethers.constants.AddressZero && toBytecode.length <= 2)) { | ||
// if bytecode of "to" is the same as the deployed bytecode | ||
// we can use the artifact name | ||
contractName = _artifact.contractName; | ||
} | ||
// try to parse the arguments | ||
try { | ||
// if this doesnt throw, we likely found an Artifact that recognizes the input | ||
const signature = input.slice(0, 10); | ||
result = iface.decodeFunctionData(signature, input); | ||
try { | ||
result2 = iface.decodeFunctionResult(signature, ret); | ||
let contractDecimals; | ||
let inputResult; | ||
let returnResult; | ||
let fragment; | ||
try { | ||
({ | ||
fragment, | ||
contractName, | ||
inputResult, | ||
returnResult, | ||
} = await dependencies.tracerEnv.decoder.decode(input, ret)); | ||
// use just contract name | ||
contractName = contractName.split(":")[1]; | ||
} | ||
catch { } | ||
// TODO Find a better contract name | ||
// 1. See if there is a name() method that gives string or bytes32 | ||
const contractNameFromNameMethod = await (0, utils_1.fetchContractName)(to, dependencies.provider); | ||
if (contractNameFromNameMethod !== undefined) { | ||
contractName = contractNameFromNameMethod; | ||
} | ||
else { | ||
// 2. Match bytecode | ||
let contractNameFromArtifacts; | ||
const toBytecode = await dependencies.provider.send("eth_getCode", [to]); | ||
for (const name of names) { | ||
const _artifact = await dependencies.artifacts.readArtifact(name); | ||
// try to find the contract name | ||
if ((0, utils_1.compareBytecode)(_artifact.deployedBytecode, toBytecode) > 0.5 || | ||
(to === ethers_1.ethers.constants.AddressZero && toBytecode.length <= 2)) { | ||
// if bytecode of "to" is the same as the deployed bytecode | ||
// we can use the artifact name | ||
contractNameFromArtifacts = _artifact.contractName; | ||
} | ||
catch (_a) { } | ||
functionFragment = iface.getFunction(signature); | ||
// if we got both the contract name and arguments parsed so far, we can stop | ||
if (contractNameFromArtifacts) { | ||
contractName = contractNameFromArtifacts; | ||
break; | ||
} | ||
} | ||
catch (_b) { } | ||
// if we got both the contract name and arguments parsed so far, we can stop | ||
if (contractName && result) { | ||
break; | ||
} | ||
} | ||
if (result && functionFragment) { | ||
const inputArgs = (0, result_1.formatResult)(result, functionFragment, { decimals: -1, isInput: true, shorten: false }, dependencies); | ||
const outputArgs = result2 | ||
? (0, result_1.formatResult)(result2, functionFragment, { decimals: -1, isInput: false, shorten: true }, dependencies) | ||
if (input.slice(0, 10) === "0x70a08231" || // balanceOf | ||
input.slice(0, 10) === "0xa9059cbb" || // transfer | ||
input.slice(0, 10) === "0x23b872dd" // transferFrom | ||
) { | ||
contractDecimals = await (0, utils_1.fetchContractDecimals)(to, dependencies.provider); | ||
} | ||
if (inputResult && fragment) { | ||
const inputArgs = (0, result_1.formatResult)(inputResult, fragment.inputs, { decimals: contractDecimals, shorten: false }, dependencies); | ||
const outputArgs = returnResult | ||
? (0, result_1.formatResult)(returnResult, fragment.outputs, { decimals: contractDecimals, shorten: true }, dependencies) | ||
: ""; | ||
const extra = []; | ||
if ((value = ethers_1.BigNumber.from(value)).gt(0)) { | ||
extra.push(`value: ${(0, param_1.formatParam)(value, dependencies)}`); | ||
extra.push(`value${separator_1.SEPARATOR}${(0, param_1.formatParam)(value, dependencies)}`); | ||
} | ||
if ((gas = ethers_1.BigNumber.from(gas)).gt(0) && dependencies.tracerEnv.gasCost) { | ||
extra.push(`gas: ${(0, param_1.formatParam)(gas, dependencies)}`); | ||
extra.push(`gas${separator_1.SEPARATOR}${(0, param_1.formatParam)(gas, dependencies)}`); | ||
} | ||
const nameTag = (0, utils_2.getFromNameTags)(to, dependencies); | ||
const nameTag = (0, utils_1.getFromNameTags)(to, dependencies); | ||
return `${nameTag | ||
@@ -62,10 +79,10 @@ ? (0, colors_1.colorContract)(nameTag) | ||
? (0, colors_1.colorContract)(contractName) | ||
: `<${(0, colors_1.colorContract)("UnknownContract")} ${(0, param_1.formatParam)(to, dependencies)}>`}.${(0, colors_1.colorFunction)(functionFragment.name)}${extra.length !== 0 ? `{${extra.join(",")}}` : ""}(${inputArgs})${outputArgs ? ` => (${outputArgs})` : ""}`; | ||
: `<${(0, colors_1.colorContract)("UnknownContract")} ${(0, param_1.formatParam)(to, dependencies)}>`}.${(0, colors_1.colorFunction)(fragment.name)}${extra.length !== 0 ? `{${extra.join(",")}}` : ""}(${inputArgs})${outputArgs ? ` => (${outputArgs})` : ""}`; | ||
} | ||
// TODO add flag to hide unrecognized stuff | ||
if (toBytecode.length > 2 && contractName) { | ||
return `${(0, colors_1.colorContract)(contractName)}.<${(0, colors_1.colorFunction)("UnknownFunction")}>(${(0, colors_1.colorKey)("input=")}${input}, ${(0, colors_1.colorKey)("ret=")}${ret})`; | ||
if (contractName) { | ||
return `${(0, colors_1.colorContract)(contractName)}.<${(0, colors_1.colorFunction)("UnknownFunction")}>(${(0, colors_1.colorKey)("input" + separator_1.SEPARATOR)}${input}, ${(0, colors_1.colorKey)("ret" + separator_1.SEPARATOR)}${ret})`; | ||
} | ||
else { | ||
return `${(0, colors_1.colorFunction)("UnknownContractAndFunction")}(${(0, colors_1.colorKey)("to=")}${to}, ${(0, colors_1.colorKey)("input=")}${input}, ${(0, colors_1.colorKey)("ret=")}${ret})`; | ||
return `${(0, colors_1.colorFunction)("UnknownContractAndFunction")}(${(0, colors_1.colorKey)("to" + separator_1.SEPARATOR)}${to}, ${(0, colors_1.colorKey)("input" + separator_1.SEPARATOR)}${input}, ${(0, colors_1.colorKey)("ret" + separator_1.SEPARATOR)}${ret || "0x"})`; | ||
} | ||
@@ -72,0 +89,0 @@ } |
@@ -1,4 +0,4 @@ | ||
import { BigNumber } from "ethers"; | ||
import { TracerDependenciesExtended } from "../types"; | ||
export declare function formatContract(code: string, value: BigNumber, salt: BigNumber | null, deployedAddress: string | null, dependencies: TracerDependenciesExtended): Promise<string>; | ||
import { BigNumberish } from "ethers"; | ||
import { TracerDependencies } from "../types"; | ||
export declare function formatContract(code: string, value: BigNumberish, salt: BigNumberish | null, deployedAddress: string | null, dependencies: TracerDependencies): Promise<string>; | ||
//# sourceMappingURL=contract.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.formatContract = void 0; | ||
const ethers_1 = require("ethers"); | ||
const utils_1 = require("ethers/lib/utils"); | ||
@@ -10,2 +11,5 @@ const colors_1 = require("../colors"); | ||
async function formatContract(code, value, salt, deployedAddress, dependencies) { | ||
value = ethers_1.BigNumber.from(value); | ||
if (salt !== null) | ||
salt = ethers_1.BigNumber.from(salt); | ||
const names = await dependencies.artifacts.getAllFullyQualifiedNames(); | ||
@@ -21,3 +25,3 @@ for (const name of names) { | ||
const constructorParamsDecoded = iface._decodeParams(iface.deploy.inputs, "0x" + code.slice(artifact.bytecode.length)); | ||
const inputArgs = (0, result_1.formatResult)(constructorParamsDecoded, iface.deploy, { decimals: -1, isInput: true, shorten: false }, dependencies); | ||
const inputArgs = (0, result_1.formatResult)(constructorParamsDecoded, iface.deploy.inputs, { decimals: -1, shorten: false }, dependencies); | ||
const extra = []; | ||
@@ -34,3 +38,3 @@ if (value.gt(0)) { | ||
} | ||
catch (_a) { } | ||
catch { } | ||
} | ||
@@ -37,0 +41,0 @@ } |
@@ -1,3 +0,3 @@ | ||
import { TracerDependenciesExtended } from "../types"; | ||
export declare function formatError(revertData: string, dependencies: TracerDependenciesExtended): Promise<string>; | ||
import { TracerDependencies } from "../types"; | ||
export declare function formatError(revertData: string, dependencies: TracerDependencies): Promise<string>; | ||
//# sourceMappingURL=error.d.ts.map |
@@ -56,6 +56,6 @@ "use strict"; | ||
} | ||
const formatted = (0, result_1.formatResult)(parsed.args, parsed.functionFragment, { decimals: -1, isInput: true, shorten: false }, dependencies); | ||
const formatted = (0, result_1.formatResult)(parsed.args, parsed.functionFragment.inputs, { decimals: -1, shorten: false }, dependencies); | ||
return `${(0, colors_1.colorError)(parsed.name)}(${formatted})`; | ||
} | ||
catch (_a) { } | ||
catch { } | ||
// if error not common then try to parse it as a custom error | ||
@@ -68,5 +68,5 @@ const names = await dependencies.artifacts.getAllFullyQualifiedNames(); | ||
const errorDesc = iface.parseError(revertData); | ||
return `${(0, colors_1.colorError)(errorDesc.name)}(${(0, result_1.formatResult)(errorDesc.args, errorDesc.errorFragment, { decimals: -1, isInput: true, shorten: false }, dependencies)})`; | ||
return `${(0, colors_1.colorError)(errorDesc.name)}(${(0, result_1.formatResult)(errorDesc.args, errorDesc.errorFragment.inputs, { decimals: -1, shorten: false }, dependencies)})`; | ||
} | ||
catch (_b) { } | ||
catch { } | ||
} | ||
@@ -73,0 +73,0 @@ return `${(0, colors_1.colorError)("UnknownError")}(${(0, param_1.formatParam)(revertData, dependencies)})`; |
@@ -13,3 +13,3 @@ "use strict"; | ||
if (dependencies.tracerEnv.gasCost) { | ||
return ` (cost: ${gasCost !== null && gasCost !== void 0 ? gasCost : structLog.gasCost})`; | ||
return ` (cost: ${gasCost ?? structLog.gasCost})`; | ||
} | ||
@@ -16,0 +16,0 @@ else { |
@@ -1,6 +0,6 @@ | ||
import { TracerDependenciesExtended } from "../types"; | ||
import { TracerDependencies } from "../types"; | ||
export declare function formatLog(log: { | ||
data: string; | ||
topics: string[]; | ||
}, currentAddress: string | undefined, dependencies: TracerDependenciesExtended): Promise<string>; | ||
}, currentAddress: string | undefined, dependencies: TracerDependencies): Promise<string>; | ||
//# sourceMappingURL=log.d.ts.map |
@@ -10,2 +10,3 @@ "use strict"; | ||
async function formatLog(log, currentAddress, dependencies) { | ||
// TODO make the contractName code common between formatCall and formatLog | ||
const nameTag = currentAddress | ||
@@ -27,3 +28,3 @@ ? (0, utils_2.getFromNameTags)(currentAddress, dependencies) | ||
// try to find the contract name | ||
if ((0, utils_2.compareBytecode)(artifact.deployedBytecode, code !== null && code !== void 0 ? code : "0x") > 0.5) { | ||
if ((0, utils_2.compareBytecode)(artifact.deployedBytecode, code ?? "0x") > 0.5) { | ||
contractName = artifact.contractName; | ||
@@ -35,5 +36,8 @@ } | ||
const decimals = -1; | ||
str = `${(0, colors_1.colorEvent)(parsed.name)}(${(0, result_1.formatResult)(parsed.args, parsed.eventFragment, { decimals, isInput: true, shorten: false }, dependencies)})`; | ||
if (!contractName) { | ||
contractName = artifact.contractName; | ||
} | ||
str = `${(0, colors_1.colorEvent)(parsed.name)}(${(0, result_1.formatResult)(parsed.args, parsed.eventFragment.inputs, { decimals, shorten: false }, dependencies)})`; | ||
} | ||
catch (_a) { } | ||
catch { } | ||
// if we got both the contract name and arguments parsed so far, we can stop | ||
@@ -45,5 +49,6 @@ if (contractName && str) { | ||
return (`<${(0, colors_1.colorContract)("UnknownContract")} ${(0, param_1.formatParam)(currentAddress, dependencies)}>.` + | ||
(str !== null && str !== void 0 ? str : `${(0, colors_1.colorEvent)("UnknownEvent")}(${(0, param_1.formatParam)(log.data, dependencies)}, ${(0, param_1.formatParam)(log.topics, dependencies)})`)); | ||
(str ?? | ||
`${(0, colors_1.colorEvent)("UnknownEvent")}(${(0, param_1.formatParam)(log.data, dependencies)}, ${(0, param_1.formatParam)(log.topics, dependencies)})`)); | ||
} | ||
exports.formatLog = formatLog; | ||
//# sourceMappingURL=log.js.map |
@@ -5,2 +5,3 @@ "use strict"; | ||
const colors_1 = require("../colors"); | ||
const separator_1 = require("./separator"); | ||
function formatObject(obj) { | ||
@@ -19,3 +20,3 @@ return Object.entries(obj) | ||
} | ||
return `${(0, colors_1.colorKey)(key + "=")}${value}`; | ||
return `${(0, colors_1.colorKey)(key + separator_1.SEPARATOR)}${value}`; | ||
}) | ||
@@ -22,0 +23,0 @@ .join(", "); |
@@ -1,3 +0,3 @@ | ||
import { TracerDependenciesExtended } from "../types"; | ||
export declare function formatParam(value: any, dependencies: TracerDependenciesExtended): string; | ||
import { TracerDependencies } from "../types"; | ||
export declare function formatParam(value: any, dependencies: TracerDependencies): string; | ||
//# sourceMappingURL=param.d.ts.map |
@@ -8,4 +8,5 @@ "use strict"; | ||
const utils_2 = require("../utils"); | ||
const separator_1 = require("./separator"); | ||
function formatParam(value, dependencies) { | ||
if (value === null || value === void 0 ? void 0 : value._isBigNumber) { | ||
if (value?._isBigNumber) { | ||
return ethers_1.BigNumber.from(value).toString(); | ||
@@ -32,3 +33,3 @@ } | ||
} | ||
else if (value === null || value === void 0 ? void 0 : value._isIndexed) { | ||
else if (value?._isIndexed) { | ||
return `${(0, colors_1.colorIndexed)("[Indexed]")}${formatParam(value.hash, dependencies)}`; | ||
@@ -40,3 +41,3 @@ } | ||
.map((entry) => { | ||
return `${entry[0]}:${formatParam(entry[1], dependencies)}`; | ||
return `${entry[0]}${separator_1.SEPARATOR}${formatParam(entry[1], dependencies)}`; | ||
}) | ||
@@ -43,0 +44,0 @@ .join(", ") + |
@@ -1,10 +0,9 @@ | ||
import { Fragment, Result } from "ethers/lib/utils"; | ||
import { TracerDependenciesExtended } from "../types"; | ||
import { ParamType, Result } from "ethers/lib/utils"; | ||
import { TracerDependencies } from "../types"; | ||
interface FormatOptions { | ||
decimals?: number; | ||
isInput?: boolean; | ||
shorten?: boolean; | ||
} | ||
export declare function formatResult(result: Result, fragment: Fragment, { decimals, isInput, shorten }: FormatOptions, dependencies: TracerDependenciesExtended): string; | ||
export declare function formatResult(result: Result, params: ParamType[] | undefined, { decimals, shorten }: FormatOptions, dependencies: TracerDependencies): string; | ||
export {}; | ||
//# sourceMappingURL=result.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.formatResult = void 0; | ||
const ethers_1 = require("ethers"); | ||
const utils_1 = require("ethers/lib/utils"); | ||
const colors_1 = require("../colors"); | ||
const param_1 = require("./param"); | ||
function formatResult(result, fragment, { decimals, isInput, shorten }, dependencies) { | ||
var _a; | ||
decimals = decimals !== null && decimals !== void 0 ? decimals : -1; | ||
isInput = isInput !== null && isInput !== void 0 ? isInput : true; | ||
shorten = shorten !== null && shorten !== void 0 ? shorten : false; | ||
function formatResult(result, params, { decimals, shorten }, dependencies) { | ||
decimals = decimals ?? -1; | ||
shorten = shorten ?? false; | ||
const stringifiedArgs = []; | ||
const params = isInput | ||
? fragment.inputs | ||
: fragment === null || fragment === void 0 ? void 0 : fragment.outputs; | ||
// const params = isInput | ||
// ? fragment.inputs | ||
// : (fragment as FunctionFragment)?.outputs; | ||
if (!params) { | ||
@@ -21,12 +20,29 @@ return ""; | ||
const param = params[i]; | ||
const name = (_a = param.name) !== null && _a !== void 0 ? _a : `arg_${i}`; | ||
const name = param.name ?? `arg_${i}`; | ||
let value; | ||
if (decimals !== -1 && ethers_1.BigNumber.isBigNumber(result[i])) { | ||
value = (0, utils_1.formatUnits)(result[i], decimals); | ||
} | ||
else if (Array.isArray(param.components)) { | ||
value = | ||
"[" + | ||
formatResult(result[i], param.components, { decimals, shorten }, dependencies) + | ||
"]"; | ||
} | ||
else { | ||
value = (0, param_1.formatParam)(result[i], dependencies); | ||
} | ||
stringifiedArgs.push([ | ||
name, | ||
decimals !== -1 && i === 2 // display formatted value for erc20 transfer events | ||
? (0, utils_1.formatUnits)(result[2], decimals) | ||
: (0, param_1.formatParam)(result[i], dependencies), | ||
value, | ||
// use decimals if available to format amount | ||
// decimals !== -1 && BigNumber.isBigNumber(result[i]) | ||
// ? formatUnits(result[i], decimals) | ||
// : formatParam(result[i], param.components, dependencies), | ||
]); | ||
} | ||
return `${stringifiedArgs | ||
.map((entry) => `${stringifiedArgs.length > 1 || !shorten ? (0, colors_1.colorKey)(`${entry[0]}=`) : ""}${entry[1]}`) | ||
.map((entry) => `${stringifiedArgs.length > 1 || !shorten | ||
? (0, colors_1.colorKey)(`${entry[0]}: `) | ||
: ""}${entry[1]}`) | ||
.join(", ")}`; | ||
@@ -33,0 +49,0 @@ } |
@@ -11,3 +11,3 @@ "use strict"; | ||
const stack = (0, utils_1.shallowCopyStack)(structLog.stack); | ||
if (stack.length < 2) { | ||
if (stack.length <= 2) { | ||
console.log("Faulty SSTORE"); | ||
@@ -14,0 +14,0 @@ return; |
@@ -27,4 +27,2 @@ "use strict"; | ||
const sha3_1 = require("../opcodes/sha3"); | ||
const mload_1 = require("../opcodes/mload"); | ||
const mstore_1 = require("../opcodes/mstore"); | ||
/** | ||
@@ -79,3 +77,3 @@ * Prints the given structLog to the console. | ||
case "SLOAD": | ||
if (dependencies.tracerEnv.sloads) { | ||
if (dependencies.tracerEnv.opcodes.get("SLOAD")) { | ||
await (0, sload_1.printSload)(structLog, index, structLogs, dependencies); | ||
@@ -85,3 +83,3 @@ } | ||
case "SSTORE": | ||
if (dependencies.tracerEnv.sstores) { | ||
if (dependencies.tracerEnv.opcodes.get("SSTORE")) { | ||
await (0, sstore_1.printSstore)(structLog, dependencies); | ||
@@ -101,3 +99,3 @@ } | ||
default: | ||
if (dependencies.tracerEnv.opcodes.includes(structLog.op)) { | ||
if (dependencies.tracerEnv.opcodes.get(structLog.op)) { | ||
switch (structLog.op) { | ||
@@ -128,8 +126,2 @@ case "ADD": | ||
break; | ||
case "MLOAD": | ||
await (0, mload_1.printMload)(structLog, index, structLogs, dependencies); | ||
break; | ||
case "MSTORE": | ||
await (0, mstore_1.printMstore)(structLog, dependencies); | ||
break; | ||
default: | ||
@@ -136,0 +128,0 @@ console.log(constants_1.DEPTH_INDENTATION.repeat(structLog.depth) + structLog.op); |
@@ -10,3 +10,2 @@ "use strict"; | ||
async function printTopLevelTx(txHash, addressStack, dependencies) { | ||
var _a; | ||
const tx = await dependencies.provider.send("eth_getTransactionByHash", [ | ||
@@ -26,3 +25,3 @@ txHash, | ||
// contract deploy transaction | ||
const str = await (0, contract_1.formatContract)(tx.input, (0, utils_2.parseUint)((_a = tx.value) !== null && _a !== void 0 ? _a : "0x"), null, (0, utils_1.getContractAddress)(tx), dependencies); | ||
const str = await (0, contract_1.formatContract)(tx.input, (0, utils_2.parseUint)(tx.value ?? "0x"), null, (0, utils_1.getContractAddress)(tx), dependencies); | ||
addressStack.push((0, utils_1.getContractAddress)(tx)); | ||
@@ -29,0 +28,0 @@ console.log((0, colors_1.colorLabel)("CREATE") + " " + str); |
@@ -5,4 +5,16 @@ "use strict"; | ||
const config_1 = require("hardhat/config"); | ||
const print_1 = require("../print"); | ||
const get_vm_1 = require("../get-vm"); | ||
const utils_1 = require("../utils"); | ||
const ethereumjs_vm_1 = require("@nomicfoundation/ethereumjs-vm"); | ||
const recorder_1 = require("../trace/recorder"); | ||
const originalCreate = ethereumjs_vm_1.VM.create; | ||
ethereumjs_vm_1.VM.create = async function (...args) { | ||
const vm = await originalCreate.bind(ethereumjs_vm_1.VM)(...args); | ||
// @ts-ignore | ||
const tracerEnv = global.tracerEnv; | ||
const recorder = new recorder_1.TraceRecorder(vm, tracerEnv); | ||
// @ts-ignore | ||
global._hardhat_tracer_recorder = recorder; | ||
return vm; | ||
}; | ||
(0, utils_1.addCliParams)((0, config_1.task)("trace", "Traces a transaction hash")) | ||
@@ -12,3 +24,2 @@ .addParam("hash", "transaction hash to view trace of") | ||
.setAction(async (args, hre, runSuper) => { | ||
var _a; | ||
(0, utils_1.applyCliArgsToTracer)(args, hre); | ||
@@ -20,9 +31,26 @@ const tx = await hre.network.provider.send("eth_getTransactionByHash", [ | ||
if (tx == null) { | ||
if (!args.rpc) { | ||
const mainnetForkUrl = (_a = hre.network.config.forking) === null || _a === void 0 ? void 0 : _a.url; | ||
if (!mainnetForkUrl) { | ||
throw new Error("Transaction not found on current network, please pass an archive node with --rpc option"); | ||
// try using url specified in network as rpc url | ||
if (args.network) { | ||
const userNetworks = hre.userConfig.networks; | ||
if (userNetworks === undefined) { | ||
throw new Error("No networks found in hardhat config"); | ||
} | ||
if (userNetworks[args.network] === undefined) { | ||
throw new Error(`Network ${args.network} not found in hardhat config`); | ||
} | ||
const url = userNetworks[args.network].url; | ||
if (url === undefined) { | ||
throw new Error(`Url not found in hardhat-config->networks->${args.network}`); | ||
} | ||
args.rpc = url; | ||
} | ||
// try using current mainnet fork url as rpc url | ||
const mainnetForkUrl = hre.network.config.forking?.url; | ||
if (mainnetForkUrl) { | ||
args.rpc = mainnetForkUrl; | ||
} | ||
if (!args.rpc) { | ||
// TODO add auto-detect network | ||
throw new Error("rpc url not provided, please either use --network <network-name> or --rpc <rpc-url>"); | ||
} | ||
const provider = new ethers_1.ethers.providers.StaticJsonRpcProvider(args.rpc); | ||
@@ -36,46 +64,37 @@ const txFromRpc = await provider.getTransaction(args.hash); | ||
} | ||
try { | ||
console.warn("Trying with rpc"); | ||
await (0, print_1.printDebugTrace)(args.hash, { | ||
provider, | ||
tracerEnv: hre.tracer, | ||
artifacts: hre.artifacts, | ||
nameTags: hre.tracer.nameTags, | ||
}); | ||
// if printing was successful, then stop here | ||
return; | ||
// TODO add support for decoding using debug_tt on the RPC if present, otherwise use hardhat mainnet fork | ||
if (false) { | ||
// decode response of debug_traceTransaction | ||
return; // should halt execution here | ||
} | ||
catch (error) { | ||
console.warn("Using debug_tt on rpc failed, activating mainnet fork at block", txFromRpc.blockNumber); | ||
await hre.network.provider.send("hardhat_reset", [ | ||
{ | ||
forking: { | ||
jsonRpcUrl: args.rpc, | ||
blockNumber: txFromRpc.blockNumber, | ||
}, | ||
console.warn("Activating mainnet fork at block", txFromRpc.blockNumber); | ||
await hre.network.provider.send("hardhat_reset", [ | ||
{ | ||
forking: { | ||
jsonRpcUrl: args.rpc, | ||
blockNumber: txFromRpc.blockNumber, | ||
}, | ||
]); | ||
} | ||
}, | ||
]); | ||
// after the above hardhat reset, tx should be present on the local node | ||
} | ||
// using hardhat for getting the trace. if tx was previously not found on hardhat local, | ||
// but now it will be available, due to mainnet fork activation | ||
console.warn("Trying with hardhat mainnet fork"); | ||
const tracePromise = (0, print_1.printDebugTraceOrLogs)(args.hash, { | ||
provider: hre.network.provider, | ||
const node = await (0, get_vm_1.getNode)(hre); | ||
// we cant use this resp because stack and memory is not there (takes up lot of memory if enabled) | ||
await node.traceTransaction(Buffer.from(args.hash.slice(2), "hex"), { | ||
disableStorage: true, | ||
disableMemory: true, | ||
disableStack: true, | ||
}); | ||
// TODO try to do this properly | ||
// @ts-ignore | ||
const recorder = global?._hardhat_tracer_recorder; | ||
await recorder.previousTraces[recorder.previousTraces.length - 1].print({ | ||
artifacts: hre.artifacts, | ||
tracerEnv: hre.tracer, | ||
artifacts: hre.artifacts, | ||
nameTags: hre.tracer.nameTags, | ||
provider: hre.ethers.provider, | ||
}); | ||
const delayPromise = new Promise((resolve) => { | ||
setTimeout(() => resolve("delay"), 20000); | ||
}); | ||
const resolved = await Promise.race([delayPromise, tracePromise]); | ||
if (resolved === "delay") { | ||
console.log("Seems that it is taking time to fetch the state involved. Please note that this process may take several minutes. A lot of eth_getStorageAt requests are currently being made to the rpc."); | ||
} | ||
const traceResult = await tracePromise; | ||
if (!traceResult) { | ||
throw new Error("Transaction could not be traced"); | ||
} | ||
await new Promise((resolve) => setTimeout(resolve, 1000)); | ||
return; | ||
}); | ||
//# sourceMappingURL=trace.js.map |
import { Artifacts } from "hardhat/types"; | ||
import { TraceRecorder } from "./trace/recorder"; | ||
import { Decoder } from "./decoder"; | ||
import { BigNumberish } from "ethers"; | ||
export interface NameTags { | ||
@@ -7,18 +10,14 @@ [address: string]: string; | ||
enabled?: boolean; | ||
logs?: boolean; | ||
calls?: boolean; | ||
sstores?: boolean; | ||
sloads?: boolean; | ||
defaultVerbosity?: number; | ||
gasCost?: boolean; | ||
opcodes?: string[]; | ||
nameTags?: NameTags; | ||
stateOverrides?: StateOverrides; | ||
} | ||
export interface TracerEnv { | ||
enabled: boolean; | ||
logs: boolean; | ||
calls: boolean; | ||
sstores: boolean; | ||
sloads: boolean; | ||
ignoreNext: boolean; | ||
verbosity: number; | ||
gasCost: boolean; | ||
opcodes: string[]; | ||
opcodes: Map<string, boolean>; | ||
nameTags: NameTags; | ||
@@ -28,2 +27,5 @@ _internal: { | ||
}; | ||
recorder?: TraceRecorder; | ||
decoder?: Decoder; | ||
stateOverrides?: StateOverrides; | ||
} | ||
@@ -52,2 +54,12 @@ export interface TracerDependencies { | ||
} | ||
export interface StateOverrides { | ||
[address: string]: { | ||
storage?: { | ||
[slot: string | number]: BigNumberish; | ||
}; | ||
bytecode?: string; | ||
balance?: BigNumberish; | ||
nonce?: BigNumberish; | ||
}; | ||
} | ||
//# sourceMappingURL=types.d.ts.map |
import { BigNumber } from "ethers"; | ||
import { VM } from "@nomicfoundation/ethereumjs-vm"; | ||
import { ConfigurableTaskDefinition, HardhatRuntimeEnvironment } from "hardhat/types"; | ||
import { StructLog, TracerDependenciesExtended, TracerEnv, TracerEnvUser } from "./types"; | ||
export declare function getTracerEnvFromUserInput(userInput?: TracerEnvUser): TracerEnv; | ||
import { ProviderLike, StateOverrides, StructLog, TracerDependencies, TracerEnv } from "./types"; | ||
import { Item } from "./trace/transaction"; | ||
export declare function addCliParams(task: ConfigurableTaskDefinition): ConfigurableTaskDefinition; | ||
export declare const DEFAULT_VERBOSITY = 3; | ||
export declare function applyCliArgsToTracer(args: any, hre: HardhatRuntimeEnvironment): void; | ||
export declare function isOnlyLogs(env: TracerEnv): boolean; | ||
export declare function getFromNameTags(address: string, dependencies: TracerDependenciesExtended): string | undefined; | ||
export declare function getFromNameTags(address: string, dependencies: TracerDependencies): string | undefined; | ||
export declare function findNextStructLogInDepth(structLogs: StructLog[], depth: number, startIndex: number): [StructLog, StructLog]; | ||
@@ -16,4 +18,16 @@ export declare function parseHex(str: string): string; | ||
export declare function shallowCopyStack(stack: string[]): string[]; | ||
export declare function shallowCopyStack2(stack: bigint[]): string[]; | ||
export declare function compareBytecode(artifactBytecode: string, contractBytecode: string): number; | ||
export declare function removeColor(str: string): string; | ||
/** | ||
* Ensures 0x prefix to a hex string which may or may not | ||
* @param str A hex string that may or may not have 0x prepended | ||
*/ | ||
export declare function hexPrefix(str: string): string; | ||
export declare function checkIfOpcodesAreValid(opcodes: Map<string, boolean>, vm: VM): void; | ||
export declare function isItem(item: any): item is Item<any>; | ||
export declare function applyStateOverrides(stateOverrides: StateOverrides, vm: VM): Promise<void>; | ||
export declare function fetchContractName(to: string, provider: ProviderLike): Promise<string | undefined>; | ||
export declare function fetchContractNameFromMethodName(to: string, methodName: string, provider: ProviderLike): Promise<string | undefined>; | ||
export declare function fetchContractDecimals(to: string, provider: ProviderLike): Promise<number | undefined>; | ||
//# sourceMappingURL=utils.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.removeColor = exports.compareBytecode = exports.shallowCopyStack = exports.parseMemory = exports.parseAddress = exports.parseUint = exports.parseNumber = exports.parseHex = exports.findNextStructLogInDepth = exports.getFromNameTags = exports.isOnlyLogs = exports.applyCliArgsToTracer = exports.addCliParams = exports.getTracerEnvFromUserInput = void 0; | ||
exports.fetchContractDecimals = exports.fetchContractNameFromMethodName = exports.fetchContractName = exports.applyStateOverrides = exports.isItem = exports.checkIfOpcodesAreValid = exports.hexPrefix = exports.removeColor = exports.compareBytecode = exports.shallowCopyStack2 = exports.shallowCopyStack = exports.parseMemory = exports.parseAddress = exports.parseUint = exports.parseNumber = exports.parseHex = exports.findNextStructLogInDepth = exports.getFromNameTags = exports.isOnlyLogs = exports.applyCliArgsToTracer = exports.DEFAULT_VERBOSITY = exports.addCliParams = void 0; | ||
const utils_1 = require("ethers/lib/utils"); | ||
const ethers_1 = require("ethers"); | ||
const utils_1 = require("ethers/lib/utils"); | ||
function getTracerEnvFromUserInput(userInput) { | ||
var _a, _b, _c, _d, _e, _f, _g, _h; | ||
return { | ||
enabled: (_a = userInput === null || userInput === void 0 ? void 0 : userInput.enabled) !== null && _a !== void 0 ? _a : false, | ||
logs: (_b = userInput === null || userInput === void 0 ? void 0 : userInput.logs) !== null && _b !== void 0 ? _b : false, | ||
calls: (_c = userInput === null || userInput === void 0 ? void 0 : userInput.calls) !== null && _c !== void 0 ? _c : false, | ||
sstores: (_d = userInput === null || userInput === void 0 ? void 0 : userInput.sstores) !== null && _d !== void 0 ? _d : false, | ||
sloads: (_e = userInput === null || userInput === void 0 ? void 0 : userInput.sloads) !== null && _e !== void 0 ? _e : false, | ||
gasCost: (_f = userInput === null || userInput === void 0 ? void 0 : userInput.gasCost) !== null && _f !== void 0 ? _f : false, | ||
opcodes: (_g = userInput === null || userInput === void 0 ? void 0 : userInput.opcodes) !== null && _g !== void 0 ? _g : [], | ||
nameTags: (_h = userInput === null || userInput === void 0 ? void 0 : userInput.nameTags) !== null && _h !== void 0 ? _h : {}, | ||
_internal: { | ||
printNameTagTip: undefined, | ||
}, | ||
}; | ||
} | ||
exports.getTracerEnvFromUserInput = getTracerEnvFromUserInput; | ||
const ethereumjs_util_1 = require("@nomicfoundation/ethereumjs-util"); | ||
const opcodes_1 = require("@nomicfoundation/ethereumjs-evm/dist/opcodes"); | ||
function addCliParams(task) { | ||
return (task | ||
// verbosity flags | ||
.addFlag("v", "set verbosity to 1, prints calls for only failed txs") | ||
.addFlag("vv", "set verbosity to 2, prints calls and storage for only failed txs") | ||
.addFlag("vvv", "set verbosity to 3, prints calls for all txs") | ||
.addFlag("vvvv", "set verbosity to 4, prints calls and storage for all txs") | ||
.addFlag("gascost", "display gas cost") | ||
.addFlag("disabletracer", "do not enable tracer at the start (for inline enabling tracer)") | ||
// params | ||
.addOptionalParam("opcodes", "specify more opcodes to print") | ||
// feature flags | ||
.addFlag("logs", "print logs emitted during transactions") | ||
.addFlag("calls", "print calls during transactions") | ||
.addFlag("sloads", "print SLOADs during calls") | ||
.addFlag("sstores", "print SSTOREs during transactions") | ||
.addFlag("gascost", "display gas cost") | ||
.addFlag("disabletracer", "do not enable tracer at the start (for inline enabling tracer)") | ||
// feature group flags | ||
.addFlag("trace", "trace logs and calls in transactions") | ||
.addFlag("fulltrace", "trace logs, calls and storage writes in transactions") | ||
// aliases | ||
.addFlag("tracefull", "alias for fulltrace") | ||
.addFlag("gas", "alias for gascost")); | ||
// alias | ||
.addFlag("trace", "enable tracer with verbosity 3") | ||
.addFlag("fulltrace", "enable tracer with verbosity 4")); | ||
} | ||
exports.addCliParams = addCliParams; | ||
exports.DEFAULT_VERBOSITY = 3; | ||
function applyCliArgsToTracer(args, hre) { | ||
// populating aliases | ||
const fulltrace = args.fulltrace || args.tracefull; | ||
const gascost = args.gascost || args.gas; | ||
// if any flag is present, then enable tracer | ||
if (args.logs || args.trace || fulltrace || args.disabletracer === true) { | ||
hre.tracer.enabled = true; | ||
// enabled by default | ||
hre.tracer.enabled = true; | ||
// for not enabling tracer from the start | ||
if (args.disabletracer) { | ||
hre.tracer.enabled = false; | ||
} | ||
// enabling config by flags passed | ||
if (args.logs) { | ||
hre.tracer.logs = true; | ||
// always active opcodes | ||
const opcodesToActivate = ["RETURN", "REVERT"]; | ||
const logOpcodes = ["LOG0", "LOG1", "LOG2", "LOG3", "LOG4"]; | ||
const storageOpcodes = ["SLOAD", "SSTORE"]; | ||
// setting verbosity | ||
if (args.vvvv || args.fulltrace) { | ||
hre.tracer.verbosity = 4; | ||
opcodesToActivate.push(...logOpcodes, ...storageOpcodes); | ||
} | ||
if (args.calls) { | ||
hre.tracer.calls = true; | ||
else if (args.vvv || args.trace) { | ||
hre.tracer.verbosity = 3; | ||
opcodesToActivate.push(...logOpcodes); | ||
} | ||
if (args.sloads) { | ||
hre.tracer.sloads = true; | ||
else if (args.vv) { | ||
hre.tracer.verbosity = 2; | ||
opcodesToActivate.push(...logOpcodes, ...storageOpcodes); | ||
} | ||
if (args.sstores) { | ||
hre.tracer.sstores = true; | ||
else if (args.v) { | ||
opcodesToActivate.push(...logOpcodes); | ||
hre.tracer.verbosity = 1; | ||
} | ||
for (const opcode of opcodesToActivate) { | ||
hre.tracer.opcodes.set(opcode, true); | ||
} | ||
if (args.opcodes) { | ||
hre.tracer.opcodes = [...args.opcodes.split(",")]; | ||
// hre.tracer.opcodes = [hre.tracer.opcodes, ...args.opcodes.split(",")]; | ||
for (const opcode of args.opcodes.split(",")) { | ||
hre.tracer.opcodes.set(opcode, true); | ||
} | ||
if (hre.tracer.recorder === undefined) { | ||
throw new Error(`hardhat-tracer/utils/applyCliArgsToTracer: hre.tracer.recorder is undefined`); | ||
} | ||
checkIfOpcodesAreValid(hre.tracer.opcodes, hre.tracer.recorder.vm); | ||
} | ||
// enabling config by mode of operation | ||
if (args.trace) { | ||
hre.tracer.logs = true; | ||
hre.tracer.calls = true; | ||
} | ||
if (fulltrace) { | ||
hre.tracer.logs = true; | ||
hre.tracer.calls = true; | ||
hre.tracer.sloads = true; | ||
hre.tracer.sstores = true; | ||
} | ||
if (gascost) { | ||
if (args.gascost) { | ||
hre.tracer.gasCost = true; | ||
@@ -82,11 +71,12 @@ } | ||
exports.applyCliArgsToTracer = applyCliArgsToTracer; | ||
// TODO remove | ||
function isOnlyLogs(env) { | ||
return env.logs && !env.calls && !env.sstores && !env.sloads && !env.gasCost; | ||
return env.verbosity === 1; | ||
} | ||
exports.isOnlyLogs = isOnlyLogs; | ||
function getFromNameTags(address, dependencies) { | ||
return (dependencies.nameTags[address] || | ||
dependencies.nameTags[address.toLowerCase()] || | ||
dependencies.nameTags[address.toUpperCase()] || | ||
dependencies.nameTags[ethers_1.ethers.utils.getAddress(address)]); | ||
return (dependencies.tracerEnv.nameTags[address] || | ||
dependencies.tracerEnv.nameTags[address.toLowerCase()] || | ||
dependencies.tracerEnv.nameTags[address.toUpperCase()] || | ||
dependencies.tracerEnv.nameTags[ethers_1.ethers.utils.getAddress(address)]); | ||
} | ||
@@ -137,2 +127,6 @@ exports.getFromNameTags = getFromNameTags; | ||
exports.shallowCopyStack = shallowCopyStack; | ||
function shallowCopyStack2(stack) { | ||
return [...stack].map((x) => ethers_1.BigNumber.from(x).toHexString()); | ||
} | ||
exports.shallowCopyStack2 = shallowCopyStack2; | ||
function compareBytecode(artifactBytecode, contractBytecode) { | ||
@@ -159,2 +153,116 @@ if (artifactBytecode.length <= 2 || contractBytecode.length <= 2) | ||
exports.removeColor = removeColor; | ||
/** | ||
* Ensures 0x prefix to a hex string which may or may not | ||
* @param str A hex string that may or may not have 0x prepended | ||
*/ | ||
function hexPrefix(str) { | ||
if (!str.startsWith("0x")) | ||
str = "0x" + str; | ||
return str; | ||
} | ||
exports.hexPrefix = hexPrefix; | ||
function checkIfOpcodesAreValid(opcodes, vm) { | ||
// fetch the opcodes which work on this VM | ||
let activeOpcodesMap = new Map(); | ||
for (const opcode of (0, opcodes_1.getOpcodesForHF)(vm._common).opcodes.values()) { | ||
activeOpcodesMap.set(opcode.fullName, true); | ||
} | ||
// check if there are any opcodes specified in tracer which do not work | ||
for (const opcode of opcodes.keys()) { | ||
if (!activeOpcodesMap.get(opcode)) { | ||
throw new Error(`The opcode "${opcode}" is not active on this VM. If the opcode name is misspelled in the config, please correct it.`); | ||
} | ||
} | ||
} | ||
exports.checkIfOpcodesAreValid = checkIfOpcodesAreValid; | ||
function isItem(item) { | ||
return item && typeof item.opcode === "string"; | ||
} | ||
exports.isItem = isItem; | ||
async function applyStateOverrides(stateOverrides, vm) { | ||
for (const [_address, overrides] of Object.entries(stateOverrides)) { | ||
const address = ethereumjs_util_1.Address.fromString(_address); | ||
// for balance and nonce | ||
if (overrides.balance !== undefined || overrides.nonce !== undefined) { | ||
const account = await vm.stateManager.getAccount(address); | ||
if (overrides.nonce !== undefined) { | ||
account.nonce = ethers_1.BigNumber.from(overrides.nonce).toBigInt(); | ||
} | ||
if (overrides.balance) { | ||
account.balance = ethers_1.BigNumber.from(overrides.balance).toBigInt(); | ||
} | ||
await vm.stateManager.putAccount(address, account); | ||
} | ||
// for bytecode | ||
if (overrides.bytecode) { | ||
await vm.stateManager.putContractCode(address, Buffer.from(overrides.bytecode, "hex")); | ||
} | ||
// for storage slots | ||
if (overrides.storage) { | ||
for (const [key, value] of Object.entries(overrides.storage)) { | ||
await vm.stateManager.putContractStorage(address, Buffer.from((0, utils_1.hexZeroPad)(ethers_1.BigNumber.from(key).toHexString(), 32).slice(2), "hex"), Buffer.from((0, utils_1.hexZeroPad)(ethers_1.BigNumber.from(value).toHexString(), 32).slice(2), "hex")); | ||
} | ||
} | ||
} | ||
} | ||
exports.applyStateOverrides = applyStateOverrides; | ||
async function fetchContractName(to, provider) { | ||
let name = await fetchContractNameFromMethodName(to, "symbol", provider); | ||
if (!name) { | ||
name = await fetchContractNameFromMethodName(to, "name", provider); | ||
} | ||
if (name) { | ||
name = name.split(" ").join(""); | ||
} | ||
return name; | ||
} | ||
exports.fetchContractName = fetchContractName; | ||
async function fetchContractNameFromMethodName(to, methodName, provider) { | ||
const iface1 = new utils_1.Interface([ | ||
`function ${methodName}() public view returns (string)`, | ||
]); | ||
let result1; | ||
try { | ||
result1 = await provider.send("eth_call", [ | ||
{ | ||
to, | ||
data: iface1.encodeFunctionData(methodName, []), | ||
}, | ||
]); | ||
const d = iface1.decodeFunctionResult(methodName, result1); | ||
return d[0]; | ||
} | ||
catch { | ||
try { | ||
const iface2 = new utils_1.Interface([ | ||
`function ${methodName}() public view returns (bytes32)`, | ||
]); | ||
const d = iface2.decodeFunctionResult(methodName, result1); | ||
const bytes32 = d[0]; | ||
return ethers_1.ethers.utils.toUtf8String(bytes32); | ||
} | ||
catch { } | ||
} | ||
return undefined; | ||
} | ||
exports.fetchContractNameFromMethodName = fetchContractNameFromMethodName; | ||
async function fetchContractDecimals(to, provider) { | ||
const iface1 = new utils_1.Interface([ | ||
`function decimals() public view returns (uint8)`, | ||
]); | ||
let result1; | ||
try { | ||
result1 = await provider.send("eth_call", [ | ||
{ | ||
to, | ||
data: iface1.encodeFunctionData("decimals", []), | ||
}, | ||
]); | ||
const d = iface1.decodeFunctionResult("decimals", result1); | ||
return d[0]; | ||
} | ||
catch { } | ||
return undefined; | ||
} | ||
exports.fetchContractDecimals = fetchContractDecimals; | ||
//# sourceMappingURL=utils.js.map |
@@ -7,3 +7,3 @@ "use strict"; | ||
const wrapper_1 = require("hardhat/internal/core/providers/wrapper"); | ||
const print_1 = require("./print"); | ||
const decoder_1 = require("./decoder"); | ||
const utils_1 = require("./utils"); | ||
@@ -20,5 +20,5 @@ /** | ||
async request(args) { | ||
var _a; | ||
let result; | ||
let error; | ||
// console.log("wrapper->args.method", args.method); | ||
try { | ||
@@ -30,26 +30,35 @@ result = await this.dependencies.provider.send(args.method, args.params); | ||
} | ||
if (this.dependencies.tracerEnv.enabled && | ||
(result != null || error != null) && | ||
(args.method === "eth_sendTransaction" || | ||
args.method === "eth_sendRawTransaction" || | ||
args.method === "eth_getTransactionReceipt")) { | ||
let hash = result !== null && result !== void 0 ? result : error === null || error === void 0 ? void 0 : error.transactionHash; | ||
let receipt; | ||
if (typeof result === "object" && result !== null) { | ||
hash = (_a = result.transactionHash) !== null && _a !== void 0 ? _a : result.hash; | ||
receipt = result; | ||
// TODO take decision whether to print or not | ||
// if estimateGas fails then print it | ||
// sendTx should be printing it regardless of success or failure | ||
const isSendTransaction = args.method === "eth_sendTransaction"; | ||
const isEthCall = args.method === "eth_call"; | ||
const isEstimateGas = args.method === "eth_estimateGas"; | ||
const isSendTransactionFailed = isSendTransaction && !!error; | ||
const isEthCallFailed = isEthCall && !!error; | ||
const isEstimateGasFailed = isEstimateGas && !!error; | ||
let shouldPrint; | ||
switch (this.dependencies.tracerEnv.verbosity) { | ||
case 0: | ||
shouldPrint = false; | ||
break; | ||
case 1: | ||
case 2: | ||
shouldPrint = | ||
isSendTransactionFailed || isEthCallFailed || isEstimateGasFailed; | ||
break; | ||
case 3: | ||
case 4: | ||
shouldPrint = true; | ||
break; | ||
default: | ||
throw new Error("Invalid verbosity value: " + this.dependencies.tracerEnv.verbosity); | ||
} | ||
if (this.dependencies.tracerEnv.ignoreNext) { | ||
this.dependencies.tracerEnv.ignoreNext = false; | ||
} | ||
else { | ||
if (this.dependencies.tracerEnv.enabled && shouldPrint) { | ||
await this.dependencies.tracerEnv.recorder?.previousTraces?.[this.dependencies.tracerEnv.recorder?.previousTraces.length - 1]?.print?.(this.dependencies); | ||
} | ||
else { | ||
receipt = await this.dependencies.provider.send("eth_getTransactionReceipt", [hash]); | ||
} | ||
if (!this.txPrinted[hash] && receipt !== null) { | ||
this.txPrinted[hash] = true; | ||
const dependenciesExtended = Object.assign(Object.assign({}, this.dependencies), { nameTags: Object.assign({}, this.dependencies.tracerEnv.nameTags) }); | ||
try { | ||
await (0, print_1.printDebugTraceOrLogs)(hash, dependenciesExtended); | ||
} | ||
catch (error) { | ||
console.log("error in request wrapper:", error); | ||
} | ||
} | ||
} | ||
@@ -67,3 +76,2 @@ if (error) { | ||
function wrapHardhatProvider(hre) { | ||
var _a; | ||
const tracerProvider = new TracerWrapper({ | ||
@@ -77,3 +85,3 @@ artifacts: hre.artifacts, | ||
// ensure env is present | ||
hre.tracer = (_a = hre.tracer) !== null && _a !== void 0 ? _a : (0, utils_1.getTracerEnvFromUserInput)(hre.tracer); | ||
// hre.tracer = hre.tracer ?? getTracerEnvFromUserInput(hre.tracer); | ||
} | ||
@@ -90,3 +98,18 @@ exports.wrapHardhatProvider = wrapHardhatProvider; | ||
// ensure env is present | ||
tracerEnv = (0, utils_1.getTracerEnvFromUserInput)(tracerEnv); | ||
// const tracerEnv = getTracerEnvFromUserInput(tracerEnvUser); | ||
if (!tracerEnv) { | ||
tracerEnv = { | ||
enabled: false, | ||
ignoreNext: false, | ||
verbosity: utils_1.DEFAULT_VERBOSITY, | ||
gasCost: false, | ||
opcodes: new Map(), | ||
nameTags: {}, | ||
// @ts-ignore TODO remove, this has no place in "config" | ||
_internal: { | ||
printNameTagTip: undefined, | ||
}, | ||
decoder: new decoder_1.Decoder(artifacts), | ||
}; | ||
} | ||
const tracerProvider = new TracerWrapper({ provider, artifacts, tracerEnv }); | ||
@@ -93,0 +116,0 @@ const compatibleProvider = new backwards_compatibility_1.BackwardsCompatibilityProviderAdapter(tracerProvider); |
{ | ||
"name": "hardhat-tracer", | ||
"version": "1.1.1", | ||
"version": "2.0.0-beta.1", | ||
"description": "Hardhat Tracer plugin", | ||
@@ -35,19 +35,13 @@ "repository": "github:zemse/hardhat-tracer", | ||
"devDependencies": { | ||
"@nomicfoundation/hardhat-chai-matchers": "^1.0.3", | ||
"@nomicfoundation/hardhat-network-helpers": "^1.0.4", | ||
"@nomicfoundation/hardhat-toolbox": "^1.0.2", | ||
"@nomiclabs/hardhat-ethers": "^2.1.1", | ||
"@nomiclabs/hardhat-etherscan": "^3.1.0", | ||
"@typechain/ethers-v5": "^10.1.0", | ||
"@typechain/hardhat": "^6.1.2", | ||
"@types/chai": "^4.3.3", | ||
"@nomiclabs/hardhat-ethers": "^2.1.0", | ||
"@types/chai": "^4.3.0", | ||
"@types/debug": "^4.1.7", | ||
"@types/fs-extra": "^5.0.4", | ||
"@types/mocha": "^9.1.1", | ||
"@types/node": "^18.7.11", | ||
"@types/mocha": "^5.2.6", | ||
"@types/node": "^8.10.38", | ||
"@types/readable-stream": "^2.3.14", | ||
"chai": "^4.2.0", | ||
"hardhat": "^2.9.2", | ||
"hardhat-gas-reporter": "^1.0.8", | ||
"hardhat": "^2.11.2", | ||
"mocha": "^7.1.2", | ||
"prettier": "2.0.5", | ||
"solidity-coverage": "^0.7.21", | ||
"ts-node": "^10.7.0", | ||
@@ -57,3 +51,2 @@ "tslint": "^5.16.0", | ||
"tslint-plugin-prettier": "^2.0.1", | ||
"typechain": "^8.1.0", | ||
"typescript": "^4.6.3" | ||
@@ -60,0 +53,0 @@ }, |
# hardhat-tracer 🕵️ | ||
> This is a beta release. Some things might not work. | ||
Allows you to see events, calls and storage operations when running your tests. | ||
- [Installation](#installation) | ||
- [Usage](#usage) | ||
- [Test](#test): See debug trace when running test | ||
- [Trace](#trace): Task to generate call tree for a transaction | ||
- [Calldata decoder](#calldata-decoder): Task to simply decode an encoded data | ||
- [Address name tags](#address-name-tags): Set alias for addresses with name | ||
- [hardhat-tracer 🕵️](#hardhat-tracer-️) | ||
- [Installation](#installation) | ||
- [Usage](#usage) | ||
- [Test](#test) | ||
- [Trace](#trace) | ||
- [Calldata decoder](#calldata-decoder) | ||
- [Address name tags](#address-name-tags) | ||
- [State overrides](#state-overrides) | ||
@@ -30,8 +34,14 @@ ## Installation | ||
Just add the `--trace` or `--fulltrace` after your test command. | ||
```shell | ||
npx hardhat test --trace # same as --vvv | ||
npx hardhat test --fulltrace # same as --vvvv | ||
```shell | ||
npx hardhat test --trace # shows logs + calls | ||
npx hardhat test --fulltrace # shows logs + calls + sloads + sstores | ||
npx hardhat test --trace --opcodes ADD,SUB # shows any opcode specified | ||
npx hardhat test --v # shows logs + calls for only failed txs | ||
npx hardhat test --vv # shows logs + calls + storage for only failed txs | ||
npx hardhat test --vvv # shows logs + calls for all txs | ||
npx hardhat test --vvvv # shows logs + calls + storage for all txs | ||
# specify opcode | ||
npx hardhat test --v --opcodes ADD,SUB # shows any opcode specified for only failed txs | ||
npx hardhat test --vvv --opcodes ADD,SUB # shows any opcode specified for all txs | ||
``` | ||
@@ -60,3 +70,3 @@ | ||
If you are just looking for a quick decode of calldata or [Solidity's Custom Error](https://blog.soliditylang.org/2021/04/21/custom-errors/): | ||
If you are just looking for a quick decode of calldata or [Solidity"s Custom Error](https://blog.soliditylang.org/2021/04/21/custom-errors/): | ||
@@ -80,7 +90,27 @@ ``` | ||
tracer: { | ||
nameTags: { | ||
'0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266': 'Hunter', | ||
[someVariable]: 'MyContract', | ||
nameTags: { | ||
"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": "Hunter", | ||
[someVariable]: "MyContract", | ||
}, | ||
}, | ||
``` | ||
### State overrides | ||
These state overrides are applied when the EthereumJS/VM is created inside hardhat. | ||
```ts | ||
tracer: { | ||
stateOverrides: { | ||
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { | ||
storage: { | ||
"0": 100, | ||
"0x1abf42a573070203916aa7bf9118741d8da5f9522f66b5368aa0a2644f487b38": 0, | ||
}, | ||
bytecode: "0x30FF", | ||
balance: parseEther("2"), | ||
nonce: 2 | ||
}, | ||
}, | ||
}, | ||
``` |
@@ -9,2 +9,3 @@ import chalk from "chalk"; | ||
export const colorError = chalk.red; | ||
export const colorConsole = chalk.blue; | ||
export const colorKey = chalk.magenta; | ||
@@ -11,0 +12,0 @@ export const colorSload = chalk.blueBright; |
@@ -1,6 +0,11 @@ | ||
import { extendConfig } from "hardhat/config"; | ||
import { | ||
extendConfig, | ||
extendEnvironment, | ||
experimentalAddHardhatNetworkMessageTraceHook, | ||
} from "hardhat/config"; | ||
import { MessageTrace } from "hardhat/internal/hardhat-network/stack-traces/message-trace"; | ||
import { HardhatConfig, HardhatUserConfig } from "hardhat/types"; | ||
import { TracerEnv, TracerEnvUser } from "../types"; | ||
import { getTracerEnvFromUserInput } from "../utils"; | ||
import { DEFAULT_VERBOSITY } from "../utils"; | ||
@@ -19,4 +24,34 @@ declare module "hardhat/types/config" { | ||
(config: HardhatConfig, userConfig: Readonly<HardhatUserConfig>) => { | ||
config.tracer = getTracerEnvFromUserInput(userConfig.tracer); | ||
// config.tracer = getTracerEnvFromUserInput(userConfig.tracer); | ||
const opcodes = new Map<string, boolean>(); | ||
// always active opcodes | ||
const opcodesToActivate = []; | ||
if (userConfig.tracer?.opcodes) { | ||
if (!Array.isArray(userConfig.tracer.opcodes)) { | ||
throw new Error( | ||
"tracer.opcodes in hardhat user config should be array" | ||
); | ||
} | ||
opcodesToActivate.push(...userConfig.tracer.opcodes); | ||
} | ||
for (const opcode of opcodesToActivate) { | ||
opcodes.set(opcode, true); | ||
} | ||
config.tracer = { | ||
enabled: userConfig.tracer?.enabled ?? false, | ||
ignoreNext: false, | ||
verbosity: userConfig.tracer?.defaultVerbosity ?? DEFAULT_VERBOSITY, | ||
gasCost: userConfig.tracer?.gasCost ?? false, | ||
opcodes, | ||
nameTags: userConfig.tracer?.nameTags ?? {}, | ||
// @ts-ignore TODO remove, this has no place in "config" | ||
_internal: { | ||
printNameTagTip: undefined, | ||
}, | ||
stateOverrides: userConfig.tracer?.stateOverrides, | ||
}; | ||
} | ||
); |
import { extendEnvironment } from "hardhat/config"; | ||
import "hardhat/types/config"; | ||
import "hardhat/types/runtime"; | ||
import { getVM } from "../get-vm"; | ||
import { TracerEnv } from "../types"; | ||
import { TraceRecorder } from "../trace/recorder"; | ||
import { Decoder } from "../decoder"; | ||
@@ -15,3 +17,18 @@ declare module "hardhat/types/runtime" { | ||
// copy reference of config.tracer to tracer | ||
// TODO take this properly, env can contain things that config does not need to. | ||
hre.tracer = hre.config.tracer; | ||
hre.tracer.decoder = new Decoder(hre.artifacts); | ||
// @ts-ignore | ||
global.tracerEnv = hre.tracer; | ||
getVM(hre).then((vm) => { | ||
hre.tracer.recorder = new TraceRecorder(vm, hre.tracer); | ||
// vm.on("beforeTx", handleBeforeTx); | ||
// vm.on("beforeMessage", handleBeforeMessage); | ||
// vm.on("newContract", handleNewContract); | ||
// vm.on("step", handleStep); | ||
// vm.on("afterMessage", handleAfterMessage); | ||
// vm.on("afterTx", handleAfterTx); | ||
}); | ||
}); |
import { BigNumber, BigNumberish, ethers } from "ethers"; | ||
import { FunctionFragment, Interface, Result } from "ethers/lib/utils"; | ||
import { | ||
Fragment, | ||
FunctionFragment, | ||
Interface, | ||
Result, | ||
} from "ethers/lib/utils"; | ||
import { Artifact } from "hardhat/types"; | ||
import { colorContract, colorFunction, colorKey } from "../colors"; | ||
import { TracerDependenciesExtended } from "../types"; | ||
import { compareBytecode, getFromNameTags } from "../utils"; | ||
import { ProviderLike, TracerDependencies } from "../types"; | ||
import { | ||
compareBytecode, | ||
fetchContractDecimals, | ||
fetchContractName, | ||
getFromNameTags, | ||
} from "../utils"; | ||
import { formatParam } from "./param"; | ||
import { formatResult } from "./result"; | ||
import { SEPARATOR } from "./separator"; | ||
@@ -18,55 +29,79 @@ export async function formatCall( | ||
gas: BigNumberish, | ||
dependencies: TracerDependenciesExtended | ||
dependencies: TracerDependencies | ||
) { | ||
const toBytecode = await dependencies.provider.send("eth_getCode", [to]); | ||
const names = await dependencies.artifacts.getAllFullyQualifiedNames(); | ||
// TODO handle if `to` is console.log address | ||
let contractName: string | undefined; | ||
let result: Result | undefined; | ||
let result2: Result | undefined; | ||
let functionFragment: FunctionFragment | undefined; | ||
for (const name of names) { | ||
const _artifact = await dependencies.artifacts.readArtifact(name); | ||
const iface = new Interface(_artifact.abi); | ||
let contractDecimals: number | undefined; | ||
let inputResult: Result | undefined; | ||
let returnResult: Result | undefined; | ||
let fragment: Fragment | undefined; | ||
// try to find the contract name | ||
if ( | ||
compareBytecode(_artifact.deployedBytecode, toBytecode) > 0.5 || | ||
(to === ethers.constants.AddressZero && toBytecode.length <= 2) | ||
) { | ||
// if bytecode of "to" is the same as the deployed bytecode | ||
// we can use the artifact name | ||
contractName = _artifact.contractName; | ||
} | ||
try { | ||
({ | ||
fragment, | ||
contractName, | ||
inputResult, | ||
returnResult, | ||
} = await dependencies.tracerEnv.decoder!.decode(input, ret)); | ||
// try to parse the arguments | ||
try { | ||
// if this doesnt throw, we likely found an Artifact that recognizes the input | ||
const signature = input.slice(0, 10); | ||
result = iface.decodeFunctionData(signature, input); | ||
try { | ||
result2 = iface.decodeFunctionResult(signature, ret); | ||
} catch {} | ||
// use just contract name | ||
contractName = contractName.split(":")[1]; | ||
} catch {} | ||
functionFragment = iface.getFunction(signature); | ||
} catch {} | ||
// TODO Find a better contract name | ||
// 1. See if there is a name() method that gives string or bytes32 | ||
const contractNameFromNameMethod = await fetchContractName( | ||
to, | ||
dependencies.provider | ||
); | ||
if (contractNameFromNameMethod !== undefined) { | ||
contractName = contractNameFromNameMethod; | ||
} else { | ||
// 2. Match bytecode | ||
let contractNameFromArtifacts; | ||
const toBytecode = await dependencies.provider.send("eth_getCode", [to]); | ||
for (const name of names) { | ||
const _artifact = await dependencies.artifacts.readArtifact(name); | ||
// if we got both the contract name and arguments parsed so far, we can stop | ||
if (contractName && result) { | ||
break; | ||
// try to find the contract name | ||
if ( | ||
compareBytecode(_artifact.deployedBytecode, toBytecode) > 0.5 || | ||
(to === ethers.constants.AddressZero && toBytecode.length <= 2) | ||
) { | ||
// if bytecode of "to" is the same as the deployed bytecode | ||
// we can use the artifact name | ||
contractNameFromArtifacts = _artifact.contractName; | ||
} | ||
// if we got both the contract name and arguments parsed so far, we can stop | ||
if (contractNameFromArtifacts) { | ||
contractName = contractNameFromArtifacts; | ||
break; | ||
} | ||
} | ||
} | ||
if (result && functionFragment) { | ||
if ( | ||
input.slice(0, 10) === "0x70a08231" || // balanceOf | ||
input.slice(0, 10) === "0xa9059cbb" || // transfer | ||
input.slice(0, 10) === "0x23b872dd" // transferFrom | ||
) { | ||
contractDecimals = await fetchContractDecimals(to, dependencies.provider); | ||
} | ||
if (inputResult && fragment) { | ||
const inputArgs = formatResult( | ||
result, | ||
functionFragment, | ||
{ decimals: -1, isInput: true, shorten: false }, | ||
inputResult, | ||
fragment.inputs, | ||
{ decimals: contractDecimals, shorten: false }, | ||
dependencies | ||
); | ||
const outputArgs = result2 | ||
const outputArgs = returnResult | ||
? formatResult( | ||
result2, | ||
functionFragment, | ||
{ decimals: -1, isInput: false, shorten: true }, | ||
returnResult, | ||
(fragment as FunctionFragment).outputs, | ||
{ decimals: contractDecimals, shorten: true }, | ||
dependencies | ||
@@ -78,6 +113,6 @@ ) | ||
if ((value = BigNumber.from(value)).gt(0)) { | ||
extra.push(`value: ${formatParam(value, dependencies)}`); | ||
extra.push(`value${SEPARATOR}${formatParam(value, dependencies)}`); | ||
} | ||
if ((gas = BigNumber.from(gas)).gt(0) && dependencies.tracerEnv.gasCost) { | ||
extra.push(`gas: ${formatParam(gas, dependencies)}`); | ||
extra.push(`gas${SEPARATOR}${formatParam(gas, dependencies)}`); | ||
} | ||
@@ -94,3 +129,3 @@ const nameTag = getFromNameTags(to, dependencies); | ||
)}>` | ||
}.${colorFunction(functionFragment.name)}${ | ||
}.${colorFunction(fragment.name)}${ | ||
extra.length !== 0 ? `{${extra.join(",")}}` : "" | ||
@@ -101,11 +136,15 @@ }(${inputArgs})${outputArgs ? ` => (${outputArgs})` : ""}`; | ||
// TODO add flag to hide unrecognized stuff | ||
if (toBytecode.length > 2 && contractName) { | ||
if (contractName) { | ||
return `${colorContract(contractName)}.<${colorFunction( | ||
"UnknownFunction" | ||
)}>(${colorKey("input=")}${input}, ${colorKey("ret=")}${ret})`; | ||
)}>(${colorKey("input" + SEPARATOR)}${input}, ${colorKey( | ||
"ret" + SEPARATOR | ||
)}${ret})`; | ||
} else { | ||
return `${colorFunction("UnknownContractAndFunction")}(${colorKey( | ||
"to=" | ||
)}${to}, ${colorKey("input=")}${input}, ${colorKey("ret=")}${ret})`; | ||
"to" + SEPARATOR | ||
)}${to}, ${colorKey("input" + SEPARATOR)}${input}, ${colorKey( | ||
"ret" + SEPARATOR | ||
)}${ret || "0x"})`; | ||
} | ||
} |
@@ -1,6 +0,6 @@ | ||
import { BigNumber } from "ethers"; | ||
import { BigNumber, BigNumberish } from "ethers"; | ||
import { arrayify, Interface } from "ethers/lib/utils"; | ||
import { colorContract, colorFunction, colorKey } from "../colors"; | ||
import { TracerDependenciesExtended } from "../types"; | ||
import { TracerDependencies } from "../types"; | ||
import { compareBytecode } from "../utils"; | ||
@@ -13,7 +13,9 @@ | ||
code: string, | ||
value: BigNumber, | ||
salt: BigNumber | null, | ||
value: BigNumberish, | ||
salt: BigNumberish | null, | ||
deployedAddress: string | null, | ||
dependencies: TracerDependenciesExtended | ||
dependencies: TracerDependencies | ||
) { | ||
value = BigNumber.from(value); | ||
if (salt !== null) salt = BigNumber.from(salt); | ||
const names = await dependencies.artifacts.getAllFullyQualifiedNames(); | ||
@@ -38,4 +40,4 @@ | ||
constructorParamsDecoded, | ||
iface.deploy, | ||
{ decimals: -1, isInput: true, shorten: false }, | ||
iface.deploy.inputs, | ||
{ decimals: -1, shorten: false }, | ||
dependencies | ||
@@ -42,0 +44,0 @@ ); |
import { Interface } from "ethers/lib/utils"; | ||
import { colorError } from "../colors"; | ||
import { TracerDependenciesExtended } from "../types"; | ||
import { TracerDependencies } from "../types"; | ||
import { formatObject } from "./object"; | ||
@@ -12,3 +12,3 @@ | ||
revertData: string, | ||
dependencies: TracerDependenciesExtended | ||
dependencies: TracerDependencies | ||
) { | ||
@@ -65,4 +65,4 @@ const commonErrors = [ | ||
parsed.args, | ||
parsed.functionFragment, | ||
{ decimals: -1, isInput: true, shorten: false }, | ||
parsed.functionFragment.inputs, | ||
{ decimals: -1, shorten: false }, | ||
dependencies | ||
@@ -85,4 +85,4 @@ ); | ||
errorDesc.args, | ||
errorDesc.errorFragment, | ||
{ decimals: -1, isInput: true, shorten: false }, | ||
errorDesc.errorFragment.inputs, | ||
{ decimals: -1, shorten: false }, | ||
dependencies | ||
@@ -89,0 +89,0 @@ )})`; |
@@ -5,3 +5,3 @@ import { Interface, LogDescription } from "ethers/lib/utils"; | ||
import { colorContract, colorEvent } from "../colors"; | ||
import { TracerDependenciesExtended } from "../types"; | ||
import { TracerDependencies, TracerDependenciesExtended } from "../types"; | ||
import { compareBytecode, getFromNameTags } from "../utils"; | ||
@@ -15,4 +15,5 @@ | ||
currentAddress: string | undefined, | ||
dependencies: TracerDependenciesExtended | ||
dependencies: TracerDependencies | ||
) { | ||
// TODO make the contractName code common between formatCall and formatLog | ||
const nameTag = currentAddress | ||
@@ -46,6 +47,10 @@ ? getFromNameTags(currentAddress, dependencies) | ||
if (!contractName) { | ||
contractName = artifact.contractName; | ||
} | ||
str = `${colorEvent(parsed.name)}(${formatResult( | ||
parsed.args, | ||
parsed.eventFragment, | ||
{ decimals, isInput: true, shorten: false }, | ||
parsed.eventFragment.inputs, | ||
{ decimals, shorten: false }, | ||
dependencies | ||
@@ -52,0 +57,0 @@ )})`; |
import { colorKey } from "../colors"; | ||
import { SEPARATOR } from "./separator"; | ||
@@ -14,5 +15,5 @@ export function formatObject(obj: any) { | ||
} | ||
return `${colorKey(key + "=")}${value}`; | ||
return `${colorKey(key + SEPARATOR)}${value}`; | ||
}) | ||
.join(", "); | ||
} |
@@ -5,8 +5,9 @@ import { BigNumber } from "ethers"; | ||
import { colorIndexed, colorNameTag } from "../colors"; | ||
import { TracerDependenciesExtended } from "../types"; | ||
import { TracerDependencies } from "../types"; | ||
import { getFromNameTags } from "../utils"; | ||
import { SEPARATOR } from "./separator"; | ||
export function formatParam( | ||
value: any, | ||
dependencies: TracerDependenciesExtended | ||
dependencies: TracerDependencies | ||
): string { | ||
@@ -44,3 +45,6 @@ if (value?._isBigNumber) { | ||
.map((entry) => { | ||
return `${entry[0]}:${formatParam(entry[1], dependencies)}`; | ||
return `${entry[0]}${SEPARATOR}${formatParam( | ||
entry[1], | ||
dependencies | ||
)}`; | ||
}) | ||
@@ -47,0 +51,0 @@ .join(", ") + |
@@ -0,1 +1,2 @@ | ||
import { BigNumber } from "ethers"; | ||
import { | ||
@@ -5,2 +6,3 @@ formatUnits, | ||
FunctionFragment, | ||
ParamType, | ||
Result, | ||
@@ -10,3 +12,3 @@ } from "ethers/lib/utils"; | ||
import { colorKey } from "../colors"; | ||
import { TracerDependenciesExtended } from "../types"; | ||
import { TracerDependencies } from "../types"; | ||
@@ -17,3 +19,2 @@ import { formatParam } from "./param"; | ||
decimals?: number; | ||
isInput?: boolean; | ||
shorten?: boolean; | ||
@@ -24,13 +25,12 @@ } | ||
result: Result, | ||
fragment: Fragment, | ||
{ decimals, isInput, shorten }: FormatOptions, | ||
dependencies: TracerDependenciesExtended | ||
params: ParamType[] | undefined, | ||
{ decimals, shorten }: FormatOptions, | ||
dependencies: TracerDependencies | ||
) { | ||
decimals = decimals ?? -1; | ||
isInput = isInput ?? true; | ||
shorten = shorten ?? false; | ||
const stringifiedArgs: Array<[string, string]> = []; | ||
const params = isInput | ||
? fragment.inputs | ||
: (fragment as FunctionFragment)?.outputs; | ||
// const params = isInput | ||
// ? fragment.inputs | ||
// : (fragment as FunctionFragment)?.outputs; | ||
if (!params) { | ||
@@ -42,7 +42,26 @@ return ""; | ||
const name = param.name ?? `arg_${i}`; | ||
let value; | ||
if (decimals !== -1 && BigNumber.isBigNumber(result[i])) { | ||
value = formatUnits(result[i], decimals); | ||
} else if (Array.isArray(param.components)) { | ||
value = | ||
"[" + | ||
formatResult( | ||
result[i], | ||
param.components, | ||
{ decimals, shorten }, | ||
dependencies | ||
) + | ||
"]"; | ||
} else { | ||
value = formatParam(result[i], dependencies); | ||
} | ||
stringifiedArgs.push([ | ||
name, | ||
decimals !== -1 && i === 2 // display formatted value for erc20 transfer events | ||
? formatUnits(result[2], decimals) | ||
: formatParam(result[i], dependencies), | ||
value, | ||
// use decimals if available to format amount | ||
// decimals !== -1 && BigNumber.isBigNumber(result[i]) | ||
// ? formatUnits(result[i], decimals) | ||
// : formatParam(result[i], param.components, dependencies), | ||
]); | ||
@@ -54,3 +73,5 @@ } | ||
`${ | ||
stringifiedArgs.length > 1 || !shorten ? colorKey(`${entry[0]}=`) : "" | ||
stringifiedArgs.length > 1 || !shorten | ||
? colorKey(`${entry[0]}: `) | ||
: "" | ||
}${entry[1]}` | ||
@@ -57,0 +78,0 @@ ) |
@@ -13,3 +13,3 @@ import { colorLabel, colorSstore } from "../colors"; | ||
const stack = shallowCopyStack(structLog.stack); | ||
if (stack.length < 2) { | ||
if (stack.length <= 2) { | ||
console.log("Faulty SSTORE"); | ||
@@ -16,0 +16,0 @@ return; |
@@ -25,4 +25,2 @@ import { DEPTH_INDENTATION } from "../constants"; | ||
import { printSha3 } from "../opcodes/sha3"; | ||
import { printMload } from "../opcodes/mload"; | ||
import { printMstore } from "../opcodes/mstore"; | ||
@@ -113,3 +111,3 @@ /** | ||
case "SLOAD": | ||
if (dependencies.tracerEnv.sloads) { | ||
if (dependencies.tracerEnv.opcodes.get("SLOAD")) { | ||
await printSload(structLog, index, structLogs, dependencies); | ||
@@ -119,3 +117,3 @@ } | ||
case "SSTORE": | ||
if (dependencies.tracerEnv.sstores) { | ||
if (dependencies.tracerEnv.opcodes.get("SSTORE")) { | ||
await printSstore(structLog, dependencies); | ||
@@ -137,3 +135,3 @@ } | ||
default: | ||
if (dependencies.tracerEnv.opcodes.includes(structLog.op)) { | ||
if (dependencies.tracerEnv.opcodes.get(structLog.op)) { | ||
switch (structLog.op) { | ||
@@ -164,8 +162,2 @@ case "ADD": | ||
break; | ||
case "MLOAD": | ||
await printMload(structLog, index, structLogs, dependencies); | ||
break; | ||
case "MSTORE": | ||
await printMstore(structLog, dependencies); | ||
break; | ||
default: | ||
@@ -172,0 +164,0 @@ console.log( |
import { ethers } from "ethers"; | ||
import { arrayify } from "ethers/lib/utils"; | ||
import { task } from "hardhat/config"; | ||
import { getNode } from "../get-vm"; | ||
import { printDebugTrace, printDebugTraceOrLogs } from "../print"; | ||
import { addCliParams, applyCliArgsToTracer } from "../utils"; | ||
import { VM } from "@nomicfoundation/ethereumjs-vm"; | ||
import { TraceRecorder } from "../trace/recorder"; | ||
import { HttpNetworkUserConfig } from "hardhat/types"; | ||
const originalCreate = VM.create; | ||
VM.create = async function (...args) { | ||
const vm = await originalCreate.bind(VM)(...args); | ||
// @ts-ignore | ||
const tracerEnv = global.tracerEnv; | ||
const recorder = new TraceRecorder(vm, tracerEnv); | ||
// @ts-ignore | ||
global._hardhat_tracer_recorder = recorder; | ||
return vm; | ||
}; | ||
addCliParams(task("trace", "Traces a transaction hash")) | ||
@@ -20,11 +39,34 @@ .addParam("hash", "transaction hash to view trace of") | ||
if (tx == null) { | ||
if (!args.rpc) { | ||
const mainnetForkUrl = (hre.network.config as any).forking?.url; | ||
if (!mainnetForkUrl) { | ||
// try using url specified in network as rpc url | ||
if (args.network) { | ||
const userNetworks = hre.userConfig.networks; | ||
if (userNetworks === undefined) { | ||
throw new Error("No networks found in hardhat config"); | ||
} | ||
if (userNetworks[args.network] === undefined) { | ||
throw new Error( | ||
"Transaction not found on current network, please pass an archive node with --rpc option" | ||
`Network ${args.network} not found in hardhat config` | ||
); | ||
} | ||
const url = (userNetworks[args.network] as HttpNetworkUserConfig).url; | ||
if (url === undefined) { | ||
throw new Error( | ||
`Url not found in hardhat-config->networks->${args.network}` | ||
); | ||
} | ||
args.rpc = url; | ||
} | ||
// try using current mainnet fork url as rpc url | ||
const mainnetForkUrl = (hre.network.config as any).forking?.url; | ||
if (mainnetForkUrl) { | ||
args.rpc = mainnetForkUrl; | ||
} | ||
if (!args.rpc) { | ||
// TODO add auto-detect network | ||
throw new Error( | ||
"rpc url not provided, please either use --network <network-name> or --rpc <rpc-url>" | ||
); | ||
} | ||
const provider = new ethers.providers.StaticJsonRpcProvider(args.rpc); | ||
@@ -45,53 +87,43 @@ const txFromRpc = await provider.getTransaction(args.hash); | ||
try { | ||
console.warn("Trying with rpc"); | ||
await printDebugTrace(args.hash, { | ||
provider, | ||
tracerEnv: hre.tracer, | ||
artifacts: hre.artifacts, | ||
nameTags: hre.tracer.nameTags, | ||
}); | ||
// if printing was successful, then stop here | ||
return; | ||
} catch (error) { | ||
console.warn( | ||
"Using debug_tt on rpc failed, activating mainnet fork at block", | ||
txFromRpc.blockNumber | ||
); | ||
await hre.network.provider.send("hardhat_reset", [ | ||
{ | ||
forking: { | ||
jsonRpcUrl: args.rpc, | ||
blockNumber: txFromRpc.blockNumber, | ||
}, | ||
// TODO add support for decoding using debug_tt on the RPC if present, otherwise use hardhat mainnet fork | ||
if (false) { | ||
// decode response of debug_traceTransaction | ||
return; // should halt execution here | ||
} | ||
console.warn("Activating mainnet fork at block", txFromRpc.blockNumber); | ||
await hre.network.provider.send("hardhat_reset", [ | ||
{ | ||
forking: { | ||
jsonRpcUrl: args.rpc, | ||
blockNumber: txFromRpc.blockNumber, | ||
}, | ||
]); | ||
} | ||
}, | ||
]); | ||
// after the above hardhat reset, tx should be present on the local node | ||
} | ||
// using hardhat for getting the trace. if tx was previously not found on hardhat local, | ||
// but now it will be available, due to mainnet fork activation | ||
console.warn("Trying with hardhat mainnet fork"); | ||
const tracePromise = printDebugTraceOrLogs(args.hash, { | ||
provider: hre.network.provider, | ||
tracerEnv: hre.tracer, | ||
artifacts: hre.artifacts, | ||
nameTags: hre.tracer.nameTags, | ||
const node = await getNode(hre); | ||
// we cant use this resp because stack and memory is not there (takes up lot of memory if enabled) | ||
await node.traceTransaction(Buffer.from(args.hash.slice(2), "hex"), { | ||
disableStorage: true, | ||
disableMemory: true, | ||
disableStack: true, | ||
}); | ||
const delayPromise = new Promise((resolve) => { | ||
setTimeout(() => resolve("delay"), 20000); | ||
// TODO try to do this properly | ||
// @ts-ignore | ||
const recorder = (global?._hardhat_tracer_recorder as unknown) as TraceRecorder; | ||
await recorder.previousTraces[recorder.previousTraces.length - 1].print({ | ||
artifacts: hre.artifacts, | ||
tracerEnv: hre.tracer, | ||
provider: hre.ethers.provider, | ||
}); | ||
const resolved = await Promise.race([delayPromise, tracePromise]); | ||
if (resolved === "delay") { | ||
console.log( | ||
"Seems that it is taking time to fetch the state involved. Please note that this process may take several minutes. A lot of eth_getStorageAt requests are currently being made to the rpc." | ||
); | ||
} | ||
const traceResult = await tracePromise; | ||
if (!traceResult) { | ||
throw new Error("Transaction could not be traced"); | ||
} | ||
await new Promise((resolve) => setTimeout(resolve, 1000)); | ||
return; | ||
}); |
@@ -0,2 +1,6 @@ | ||
import { VM } from "@nomicfoundation/ethereumjs-vm"; | ||
import { Artifacts, EthereumProvider } from "hardhat/types"; | ||
import { TraceRecorder } from "./trace/recorder"; | ||
import { Decoder } from "./decoder"; | ||
import { BigNumberish } from "ethers"; | ||
@@ -9,9 +13,7 @@ export interface NameTags { | ||
enabled?: boolean; | ||
logs?: boolean; | ||
calls?: boolean; | ||
sstores?: boolean; | ||
sloads?: boolean; | ||
defaultVerbosity?: number; | ||
gasCost?: boolean; | ||
opcodes?: string[]; | ||
nameTags?: NameTags; | ||
stateOverrides?: StateOverrides; | ||
} | ||
@@ -21,9 +23,8 @@ | ||
enabled: boolean; | ||
logs: boolean; | ||
calls: boolean; | ||
sstores: boolean; | ||
sloads: boolean; | ||
ignoreNext: boolean; | ||
verbosity: number; | ||
gasCost: boolean; | ||
opcodes: string[]; // TODO have a map of opcode to boolean | ||
opcodes: Map<string, boolean>; // string[]; // TODO have a map of opcode to boolean | ||
nameTags: NameTags; | ||
// todo remove internal | ||
_internal: { | ||
@@ -35,2 +36,5 @@ printNameTagTip: | ||
}; | ||
recorder?: TraceRecorder; | ||
decoder?: Decoder; | ||
stateOverrides?: StateOverrides; | ||
} | ||
@@ -63,1 +67,12 @@ | ||
} | ||
export interface StateOverrides { | ||
[address: string]: { | ||
storage?: { | ||
[slot: string | number]: BigNumberish; | ||
}; | ||
bytecode?: string; | ||
balance?: BigNumberish; | ||
nonce?: BigNumberish; | ||
}; | ||
} |
295
src/utils.ts
@@ -0,3 +1,9 @@ | ||
import { | ||
arrayify, | ||
hexStripZeros, | ||
hexZeroPad, | ||
Interface, | ||
} from "ethers/lib/utils"; | ||
import { BigNumber, ethers } from "ethers"; | ||
import { arrayify, hexStripZeros, hexZeroPad } from "ethers/lib/utils"; | ||
import { VM } from "@nomicfoundation/ethereumjs-vm"; | ||
import { | ||
@@ -7,5 +13,8 @@ ConfigurableTaskDefinition, | ||
} from "hardhat/types"; | ||
import { Address } from "@nomicfoundation/ethereumjs-util"; | ||
import { | ||
ProviderLike, | ||
StateOverrides, | ||
StructLog, | ||
TracerDependencies, | ||
TracerDependenciesExtended, | ||
@@ -16,19 +25,7 @@ TracerEnv, | ||
export function getTracerEnvFromUserInput( | ||
userInput?: TracerEnvUser | ||
): TracerEnv { | ||
return { | ||
enabled: userInput?.enabled ?? false, | ||
logs: userInput?.logs ?? false, | ||
calls: userInput?.calls ?? false, | ||
sstores: userInput?.sstores ?? false, | ||
sloads: userInput?.sloads ?? false, | ||
gasCost: userInput?.gasCost ?? false, | ||
opcodes: userInput?.opcodes ?? [], | ||
nameTags: userInput?.nameTags ?? {}, | ||
_internal: { | ||
printNameTagTip: undefined, | ||
}, | ||
}; | ||
} | ||
import { | ||
getOpcodesForHF, | ||
Opcode, | ||
} from "@nomicfoundation/ethereumjs-evm/dist/opcodes"; | ||
import { Item } from "./trace/transaction"; | ||
@@ -38,10 +35,13 @@ export function addCliParams(task: ConfigurableTaskDefinition) { | ||
task | ||
// params | ||
.addOptionalParam("opcodes", "specify more opcodes to print") | ||
// feature flags | ||
.addFlag("logs", "print logs emitted during transactions") | ||
.addFlag("calls", "print calls during transactions") | ||
.addFlag("sloads", "print SLOADs during calls") | ||
.addFlag("sstores", "print SSTOREs during transactions") | ||
// verbosity flags | ||
.addFlag("v", "set verbosity to 1, prints calls for only failed txs") | ||
.addFlag( | ||
"vv", | ||
"set verbosity to 2, prints calls and storage for only failed txs" | ||
) | ||
.addFlag("vvv", "set verbosity to 3, prints calls for all txs") | ||
.addFlag( | ||
"vvvv", | ||
"set verbosity to 4, prints calls and storage for all txs" | ||
) | ||
.addFlag("gascost", "display gas cost") | ||
@@ -53,14 +53,13 @@ .addFlag( | ||
// feature group flags | ||
.addFlag("trace", "trace logs and calls in transactions") | ||
.addFlag( | ||
"fulltrace", | ||
"trace logs, calls and storage writes in transactions" | ||
) | ||
// aliases | ||
.addFlag("tracefull", "alias for fulltrace") | ||
.addFlag("gas", "alias for gascost") | ||
// params | ||
.addOptionalParam("opcodes", "specify more opcodes to print") | ||
// alias | ||
.addFlag("trace", "enable tracer with verbosity 3") | ||
.addFlag("fulltrace", "enable tracer with verbosity 4") | ||
); | ||
} | ||
export const DEFAULT_VERBOSITY = 3; | ||
export function applyCliArgsToTracer( | ||
@@ -70,41 +69,50 @@ args: any, | ||
) { | ||
// populating aliases | ||
const fulltrace = args.fulltrace || args.tracefull; | ||
const gascost = args.gascost || args.gas; | ||
// enabled by default | ||
hre.tracer.enabled = true; | ||
// if any flag is present, then enable tracer | ||
if (args.logs || args.trace || fulltrace || args.disabletracer === true) { | ||
hre.tracer.enabled = true; | ||
// for not enabling tracer from the start | ||
if (args.disabletracer) { | ||
hre.tracer.enabled = false; | ||
} | ||
// enabling config by flags passed | ||
if (args.logs) { | ||
hre.tracer.logs = true; | ||
// always active opcodes | ||
const opcodesToActivate = ["RETURN", "REVERT"]; | ||
const logOpcodes = ["LOG0", "LOG1", "LOG2", "LOG3", "LOG4"]; | ||
const storageOpcodes = ["SLOAD", "SSTORE"]; | ||
// setting verbosity | ||
if (args.vvvv || args.fulltrace) { | ||
hre.tracer.verbosity = 4; | ||
opcodesToActivate.push(...logOpcodes, ...storageOpcodes); | ||
} else if (args.vvv || args.trace) { | ||
hre.tracer.verbosity = 3; | ||
opcodesToActivate.push(...logOpcodes); | ||
} else if (args.vv) { | ||
hre.tracer.verbosity = 2; | ||
opcodesToActivate.push(...logOpcodes, ...storageOpcodes); | ||
} else if (args.v) { | ||
opcodesToActivate.push(...logOpcodes); | ||
hre.tracer.verbosity = 1; | ||
} | ||
if (args.calls) { | ||
hre.tracer.calls = true; | ||
for (const opcode of opcodesToActivate) { | ||
hre.tracer.opcodes.set(opcode, true); | ||
} | ||
if (args.sloads) { | ||
hre.tracer.sloads = true; | ||
} | ||
if (args.sstores) { | ||
hre.tracer.sstores = true; | ||
} | ||
if (args.opcodes) { | ||
hre.tracer.opcodes = [...args.opcodes.split(",")]; | ||
} | ||
// hre.tracer.opcodes = [hre.tracer.opcodes, ...args.opcodes.split(",")]; | ||
for (const opcode of args.opcodes.split(",")) { | ||
hre.tracer.opcodes.set(opcode, true); | ||
} | ||
// enabling config by mode of operation | ||
if (args.trace) { | ||
hre.tracer.logs = true; | ||
hre.tracer.calls = true; | ||
if (hre.tracer.recorder === undefined) { | ||
throw new Error( | ||
`hardhat-tracer/utils/applyCliArgsToTracer: hre.tracer.recorder is undefined` | ||
); | ||
} | ||
checkIfOpcodesAreValid(hre.tracer.opcodes, hre.tracer.recorder.vm); | ||
} | ||
if (fulltrace) { | ||
hre.tracer.logs = true; | ||
hre.tracer.calls = true; | ||
hre.tracer.sloads = true; | ||
hre.tracer.sstores = true; | ||
} | ||
if (gascost) { | ||
if (args.gascost) { | ||
hre.tracer.gasCost = true; | ||
@@ -114,4 +122,5 @@ } | ||
// TODO remove | ||
export function isOnlyLogs(env: TracerEnv): boolean { | ||
return env.logs && !env.calls && !env.sstores && !env.sloads && !env.gasCost; | ||
return env.verbosity === 1; | ||
} | ||
@@ -121,9 +130,9 @@ | ||
address: string, | ||
dependencies: TracerDependenciesExtended | ||
dependencies: TracerDependencies | ||
): string | undefined { | ||
return ( | ||
dependencies.nameTags[address] || | ||
dependencies.nameTags[address.toLowerCase()] || | ||
dependencies.nameTags[address.toUpperCase()] || | ||
dependencies.nameTags[ethers.utils.getAddress(address)] | ||
dependencies.tracerEnv.nameTags[address] || | ||
dependencies.tracerEnv.nameTags[address.toLowerCase()] || | ||
dependencies.tracerEnv.nameTags[address.toUpperCase()] || | ||
dependencies.tracerEnv.nameTags[ethers.utils.getAddress(address)] | ||
); | ||
@@ -185,2 +194,6 @@ } | ||
export function shallowCopyStack2(stack: bigint[]): string[] { | ||
return [...stack].map((x) => BigNumber.from(x).toHexString()); | ||
} | ||
export function compareBytecode( | ||
@@ -213,1 +226,139 @@ artifactBytecode: string, | ||
} | ||
/** | ||
* Ensures 0x prefix to a hex string which may or may not | ||
* @param str A hex string that may or may not have 0x prepended | ||
*/ | ||
export function hexPrefix(str: string): string { | ||
if (!str.startsWith("0x")) str = "0x" + str; | ||
return str; | ||
} | ||
export function checkIfOpcodesAreValid(opcodes: Map<string, boolean>, vm: VM) { | ||
// fetch the opcodes which work on this VM | ||
let activeOpcodesMap = new Map<string, boolean>(); | ||
for (const opcode of getOpcodesForHF(vm._common).opcodes.values()) { | ||
activeOpcodesMap.set(opcode.fullName, true); | ||
} | ||
// check if there are any opcodes specified in tracer which do not work | ||
for (const opcode of opcodes.keys()) { | ||
if (!activeOpcodesMap.get(opcode)) { | ||
throw new Error( | ||
`The opcode "${opcode}" is not active on this VM. If the opcode name is misspelled in the config, please correct it.` | ||
); | ||
} | ||
} | ||
} | ||
export function isItem(item: any): item is Item<any> { | ||
return item && typeof item.opcode === "string"; | ||
} | ||
export async function applyStateOverrides( | ||
stateOverrides: StateOverrides, | ||
vm: VM | ||
) { | ||
for (const [_address, overrides] of Object.entries(stateOverrides)) { | ||
const address = Address.fromString(_address); | ||
// for balance and nonce | ||
if (overrides.balance !== undefined || overrides.nonce !== undefined) { | ||
const account = await vm.stateManager.getAccount(address); | ||
if (overrides.nonce !== undefined) { | ||
account.nonce = BigNumber.from(overrides.nonce).toBigInt(); | ||
} | ||
if (overrides.balance) { | ||
account.balance = BigNumber.from(overrides.balance).toBigInt(); | ||
} | ||
await vm.stateManager.putAccount(address, account); | ||
} | ||
// for bytecode | ||
if (overrides.bytecode) { | ||
await vm.stateManager.putContractCode( | ||
address, | ||
Buffer.from(overrides.bytecode, "hex") | ||
); | ||
} | ||
// for storage slots | ||
if (overrides.storage) { | ||
for (const [key, value] of Object.entries(overrides.storage)) { | ||
await vm.stateManager.putContractStorage( | ||
address, | ||
Buffer.from( | ||
hexZeroPad(BigNumber.from(key).toHexString(), 32).slice(2), | ||
"hex" | ||
), | ||
Buffer.from( | ||
hexZeroPad(BigNumber.from(value).toHexString(), 32).slice(2), | ||
"hex" | ||
) | ||
); | ||
} | ||
} | ||
} | ||
} | ||
export async function fetchContractName(to: string, provider: ProviderLike) { | ||
let name = await fetchContractNameFromMethodName(to, "symbol", provider); | ||
if (!name) { | ||
name = await fetchContractNameFromMethodName(to, "name", provider); | ||
} | ||
if (name) { | ||
name = name.split(" ").join(""); | ||
} | ||
return name; | ||
} | ||
export async function fetchContractNameFromMethodName( | ||
to: string, | ||
methodName: string, | ||
provider: ProviderLike | ||
): Promise<string | undefined> { | ||
const iface1 = new Interface([ | ||
`function ${methodName}() public view returns (string)`, | ||
]); | ||
let result1; | ||
try { | ||
result1 = await provider.send("eth_call", [ | ||
{ | ||
to, | ||
data: iface1.encodeFunctionData(methodName, []), | ||
}, | ||
]); | ||
const d = iface1.decodeFunctionResult(methodName, result1); | ||
return d[0]; | ||
} catch { | ||
try { | ||
const iface2 = new Interface([ | ||
`function ${methodName}() public view returns (bytes32)`, | ||
]); | ||
const d = iface2.decodeFunctionResult(methodName, result1); | ||
const bytes32 = d[0]; | ||
return ethers.utils.toUtf8String(bytes32); | ||
} catch {} | ||
} | ||
return undefined; | ||
} | ||
export async function fetchContractDecimals( | ||
to: string, | ||
provider: ProviderLike | ||
): Promise<number | undefined> { | ||
const iface1 = new Interface([ | ||
`function decimals() public view returns (uint8)`, | ||
]); | ||
let result1; | ||
try { | ||
result1 = await provider.send("eth_call", [ | ||
{ | ||
to, | ||
data: iface1.encodeFunctionData("decimals", []), | ||
}, | ||
]); | ||
const d = iface1.decodeFunctionResult("decimals", result1); | ||
return d[0]; | ||
} catch {} | ||
return undefined; | ||
} |
@@ -10,11 +10,6 @@ import { ethers } from "ethers"; | ||
} from "hardhat/types"; | ||
import { Decoder } from "./decoder"; | ||
import { printDebugTraceOrLogs } from "./print"; | ||
import { | ||
ProviderLike, | ||
TracerDependencies, | ||
TracerEnv, | ||
TracerEnvUser, | ||
} from "./types"; | ||
import { getTracerEnvFromUserInput, isOnlyLogs } from "./utils"; | ||
import { ProviderLike, TracerDependencies, TracerEnv } from "./types"; | ||
import { DEFAULT_VERBOSITY } from "./utils"; | ||
@@ -36,2 +31,4 @@ /** | ||
let error: any; | ||
// console.log("wrapper->args.method", args.method); | ||
try { | ||
@@ -46,33 +43,44 @@ result = await this.dependencies.provider.send( | ||
if ( | ||
this.dependencies.tracerEnv.enabled && | ||
(result != null || error != null) && | ||
(args.method === "eth_sendTransaction" || | ||
args.method === "eth_sendRawTransaction" || | ||
args.method === "eth_getTransactionReceipt") | ||
) { | ||
let hash = result ?? error?.transactionHash; | ||
let receipt; | ||
if (typeof result === "object" && result !== null) { | ||
hash = result.transactionHash ?? result.hash; | ||
receipt = result; | ||
} else { | ||
receipt = await this.dependencies.provider.send( | ||
"eth_getTransactionReceipt", | ||
[hash] | ||
// TODO take decision whether to print or not | ||
// if estimateGas fails then print it | ||
// sendTx should be printing it regardless of success or failure | ||
const isSendTransaction = args.method === "eth_sendTransaction"; | ||
const isEthCall = args.method === "eth_call"; | ||
const isEstimateGas = args.method === "eth_estimateGas"; | ||
const isSendTransactionFailed = isSendTransaction && !!error; | ||
const isEthCallFailed = isEthCall && !!error; | ||
const isEstimateGasFailed = isEstimateGas && !!error; | ||
let shouldPrint: boolean; | ||
switch (this.dependencies.tracerEnv.verbosity) { | ||
case 0: | ||
shouldPrint = false; | ||
break; | ||
case 1: | ||
case 2: | ||
shouldPrint = | ||
isSendTransactionFailed || isEthCallFailed || isEstimateGasFailed; | ||
break; | ||
case 3: | ||
case 4: | ||
shouldPrint = true; | ||
break; | ||
default: | ||
throw new Error( | ||
"Invalid verbosity value: " + this.dependencies.tracerEnv.verbosity | ||
); | ||
} | ||
if (this.dependencies.tracerEnv.ignoreNext) { | ||
this.dependencies.tracerEnv.ignoreNext = false; | ||
} else { | ||
if (this.dependencies.tracerEnv.enabled && shouldPrint) { | ||
await this.dependencies.tracerEnv.recorder?.previousTraces?.[ | ||
this.dependencies.tracerEnv.recorder?.previousTraces.length - 1 | ||
]?.print?.(this.dependencies); | ||
} | ||
if (!this.txPrinted[hash] && receipt !== null) { | ||
this.txPrinted[hash] = true; | ||
const dependenciesExtended = { | ||
...this.dependencies, | ||
nameTags: { ...this.dependencies.tracerEnv.nameTags }, | ||
}; | ||
try { | ||
await printDebugTraceOrLogs(hash, dependenciesExtended); | ||
} catch (error) { | ||
console.log("error in request wrapper:", error); | ||
} | ||
} | ||
} | ||
if (error) { | ||
@@ -101,3 +109,3 @@ throw error; | ||
// ensure env is present | ||
hre.tracer = hre.tracer ?? getTracerEnvFromUserInput(hre.tracer); | ||
// hre.tracer = hre.tracer ?? getTracerEnvFromUserInput(hre.tracer); | ||
} | ||
@@ -118,3 +126,18 @@ | ||
// ensure env is present | ||
tracerEnv = getTracerEnvFromUserInput(tracerEnv); | ||
// const tracerEnv = getTracerEnvFromUserInput(tracerEnvUser); | ||
if (!tracerEnv) { | ||
tracerEnv = { | ||
enabled: false, | ||
ignoreNext: false, | ||
verbosity: DEFAULT_VERBOSITY, | ||
gasCost: false, | ||
opcodes: new Map(), | ||
nameTags: {}, | ||
// @ts-ignore TODO remove, this has no place in "config" | ||
_internal: { | ||
printNameTagTip: undefined, | ||
}, | ||
decoder: new Decoder(artifacts), | ||
}; | ||
} | ||
@@ -121,0 +144,0 @@ const tracerProvider = new TracerWrapper({ provider, artifacts, tracerEnv }); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
443703
16
356
7765
114
2