Socket
Socket
Sign inDemoInstall

hardhat-tracer

Package Overview
Dependencies
277
Maintainers
1
Versions
52
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.0.0-beta.2 to 2.0.0-beta.3

dist/src/tasks/compile.d.ts

22

dist/src/decoder.d.ts

@@ -1,2 +0,2 @@

import { ErrorFragment, Fragment, FunctionFragment, Result } from "ethers/lib/utils";
import { ErrorFragment, EventFragment, Fragment, FunctionFragment, Result } from "ethers/lib/utils";
import { Artifacts } from "hardhat/types";

@@ -10,5 +10,7 @@ declare type Mapping<FragmentType> = Map<string, Array<{

errorFragmentsBySelector: Mapping<ErrorFragment>;
eventFragmentsByTopic0: Mapping<EventFragment>;
ready: Promise<void>;
constructor(artifacts: Artifacts);
_constructor(artifacts: Artifacts): Promise<void>;
updateArtifacts(artifacts: Artifacts): Promise<void>;
_updateArtifacts(artifacts: Artifacts): Promise<void>;
decode(inputData: string, returnData: string): Promise<{

@@ -20,4 +22,20 @@ fragment: Fragment;

}>;
decodeFunction(inputData: string, returnData: string): Promise<{
fragment: Fragment;
inputResult: Result;
returnResult: Result | undefined;
contractName: string;
}>;
decodeError(revertData: string): Promise<{
fragment: Fragment;
revertResult: Result;
contractName: string;
}>;
decodeEvent(topics: string[], data: string): Promise<{
fragment: EventFragment;
result: Result;
contractName: string;
}>;
}
export {};
//# sourceMappingURL=decoder.d.ts.map

@@ -10,5 +10,9 @@ "use strict";

this.errorFragmentsBySelector = new Map();
this.ready = this._constructor(artifacts);
this.eventFragmentsByTopic0 = new Map();
this.ready = this._updateArtifacts(artifacts);
}
async _constructor(artifacts) {
async updateArtifacts(artifacts) {
this.ready = this._updateArtifacts(artifacts);
}
async _updateArtifacts(artifacts) {
const names = await artifacts.getAllFullyQualifiedNames();

@@ -20,3 +24,11 @@ for (const name of names) {

copyFragments(name, iface.errors, this.errorFragmentsBySelector);
copyFragments(name, iface.events, this.eventFragmentsByTopic0);
}
// common errors, these are in function format because Ethers.js does not accept them as errors
const commonErrors = [
"function Error(string reason)",
"function Panic(uint256 code)",
];
const iface = new utils_1.Interface(commonErrors);
copyFragments("", iface.functions, this.errorFragmentsBySelector);
}

@@ -31,2 +43,30 @@ async decode(inputData, returnData) {

}
async decodeFunction(inputData, returnData) {
await this.ready;
return decode(inputData, returnData, "function", this.functionFragmentsBySelector);
}
async decodeError(revertData) {
await this.ready;
const { fragment, inputResult, contractName } = await decode(revertData, "0x", "error", this.errorFragmentsBySelector);
return { fragment, revertResult: inputResult, contractName };
}
async decodeEvent(topics, data) {
await this.ready;
if (topics.length === 0) {
throw new Error("No topics, cannot decode");
}
const topic0 = topics[0];
const fragments = this.eventFragmentsByTopic0.get(topic0);
if (fragments) {
for (const { contractName, fragment } of fragments) {
try {
const iface = new ethers_1.ethers.utils.Interface([fragment]);
const result = iface.parseLog({ data, topics });
return { fragment, result: result.args, contractName };
}
catch { }
}
}
throw decodeError(topic0);
}
}

@@ -40,3 +80,5 @@ exports.Decoder = Decoder;

function addFragmentToMapping(contractName, fragment, mapping) {
const selector = ethers_1.ethers.utils.Interface.getSighash(fragment);
const selector = utils_1.EventFragment.isEventFragment(fragment)
? ethers_1.ethers.utils.Interface.getEventTopic(fragment)
: ethers_1.ethers.utils.Interface.getSighash(fragment);
let fragments = mapping.get(selector);

@@ -46,2 +88,3 @@ if (!fragments) {

}
// TODO while adding, see if we already have a same signature fragment
fragments.push({ contractName, fragment });

@@ -76,7 +119,10 @@ }

// we couldn't decode it using local ABI, try 4byte.directory
try {
const { fragment, inputResult } = await decodeUsing4byteDirectory(selector, inputData, mapping);
return { fragment, inputResult };
// currently only supports function calls
if (type === "function") {
try {
const { fragment, inputResult } = await decodeUsing4byteDirectory(selector, inputData, mapping);
return { fragment, inputResult };
}
catch { }
}
catch { }
// we couldn't decode it after even using 4byte.directory, give up

@@ -83,0 +129,0 @@ throw decodeError(selector);

@@ -24,2 +24,3 @@ "use strict";

verbosity: userConfig.tracer?.defaultVerbosity ?? utils_1.DEFAULT_VERBOSITY,
showAddresses: userConfig.tracer?.showAddresses ?? false,
gasCost: userConfig.tracer?.gasCost ?? false,

@@ -31,2 +32,3 @@ opcodes,

printNameTagTip: undefined,
tokenDecimalsCache: new Map(),
},

@@ -33,0 +35,0 @@ stateOverrides: userConfig.tracer?.stateOverrides,

17

dist/src/extend/hre.js

@@ -16,12 +16,13 @@ "use strict";

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);
// @ts-ignore
global.hreArtifacts = hre.artifacts;
(0, get_vm_1.getVM)(hre)
.then((vm) => {
hre.tracer.recorder = new recorder_1.TraceRecorder(vm, hre.tracer, hre.artifacts);
})
.catch(() => {
// if for some reason we can't get the vm, disable hardhat-tracer
hre.tracer.enabled = false;
});
});
//# sourceMappingURL=hre.js.map
import { BigNumberish } from "ethers";
import { TracerDependencies } from "../types";
export declare function formatCall(to: string, input: string, ret: string, value: BigNumberish, gas: BigNumberish, dependencies: TracerDependencies): Promise<string>;
export declare function formatCall(to: string, input: string, ret: string, value: BigNumberish, gas: BigNumberish, success: boolean, dependencies: TracerDependencies): Promise<string>;
//# sourceMappingURL=call.d.ts.map

@@ -9,6 +9,5 @@ "use strict";

const result_1 = require("./result");
const utils_2 = require("ethers/lib/utils");
const separator_1 = require("./separator");
async function formatCall(to, input, ret, value, gas, dependencies) {
const names = await dependencies.artifacts.getAllFullyQualifiedNames();
// TODO handle if `to` is console.log address
async function formatCall(to, input, ret, value, gas, success, dependencies) {
let contractName;

@@ -25,3 +24,3 @@ let contractDecimals;

returnResult,
} = await dependencies.tracerEnv.decoder.decode(input, ret));
} = await dependencies.tracerEnv.decoder.decodeFunction(input, ret));
// use just contract name

@@ -31,28 +30,11 @@ 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;
// find a better contract name
const betterContractName = await (0, utils_1.getBetterContractName)(to, dependencies);
if (betterContractName) {
contractName = betterContractName;
}
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;
}
// if we got both the contract name and arguments parsed so far, we can stop
if (contractNameFromArtifacts) {
contractName = contractNameFromArtifacts;
break;
}
}
else if (contractName) {
dependencies.tracerEnv.nameTags[to] = contractName;
}
// if ERC20 method found then fetch decimals
if (input.slice(0, 10) === "0x70a08231" || // balanceOf

@@ -62,4 +44,28 @@ input.slice(0, 10) === "0xa9059cbb" || // transfer

) {
contractDecimals = await (0, utils_1.fetchContractDecimals)(to, dependencies.provider);
// see if we already know the decimals
const { tokenDecimalsCache } = dependencies.tracerEnv._internal;
const decimals = tokenDecimalsCache.get(to);
if (decimals) {
// if we know decimals then use it
contractDecimals = decimals !== -1 ? decimals : undefined;
}
else {
// otherwise fetch it
contractDecimals = await (0, utils_1.fetchContractDecimals)(to, dependencies.provider);
// and cache it
if (contractDecimals !== undefined) {
tokenDecimalsCache.set(to, contractDecimals);
}
else {
tokenDecimalsCache.set(to, -1);
}
}
}
const extra = [];
if ((value = ethers_1.BigNumber.from(value)).gt(0)) {
extra.push(`value${separator_1.SEPARATOR}${(0, utils_2.formatEther)(value)}`);
}
if ((gas = ethers_1.BigNumber.from(gas)).gt(0) && dependencies.tracerEnv.gasCost) {
extra.push(`gas${separator_1.SEPARATOR}${(0, param_1.formatParam)(gas, dependencies)}`);
}
if (inputResult && fragment) {

@@ -69,23 +75,19 @@ const inputArgs = (0, result_1.formatResult)(inputResult, fragment.inputs, { decimals: contractDecimals, shorten: false }, dependencies);

? (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${separator_1.SEPARATOR}${(0, param_1.formatParam)(value, dependencies)}`);
}
if ((gas = ethers_1.BigNumber.from(gas)).gt(0) && dependencies.tracerEnv.gasCost) {
extra.push(`gas${separator_1.SEPARATOR}${(0, param_1.formatParam)(gas, dependencies)}`);
}
const nameTag = (0, utils_1.getFromNameTags)(to, dependencies);
return `${nameTag
? (0, colors_1.colorContract)(nameTag)
: contractName
? (0, colors_1.colorContract)(contractName)
: `<${(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})` : ""}`;
: // if return data is not decoded, then show return data only if call was success
ret !== "0x" && success !== false // success can be undefined
? ret
: "";
let nameToPrint = contractName ?? "UnknownContract";
return `${dependencies.tracerEnv.showAddresses || nameToPrint === "UnknownContract"
? `${(0, colors_1.colorContract)(nameToPrint)}(${to})`
: (0, colors_1.colorContract)(nameToPrint)}.${(0, colors_1.colorFunction)(fragment.name)}${extra.length !== 0 ? `{${extra.join(",")}}` : ""}(${inputArgs})${outputArgs ? ` => (${outputArgs})` : ""}`;
}
// TODO add flag to hide unrecognized stuff
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})`;
return `${dependencies.tracerEnv.showAddresses
? `${(0, colors_1.colorContract)(contractName)}(${to})`
: (0, colors_1.colorContract)(contractName)}.<${(0, colors_1.colorFunction)("UnknownFunction")}>${extra.length !== 0 ? `{${extra.join(",")}}` : ""}(${(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" + separator_1.SEPARATOR)}${to}, ${(0, colors_1.colorKey)("input" + separator_1.SEPARATOR)}${input}, ${(0, colors_1.colorKey)("ret" + separator_1.SEPARATOR)}${ret || "0x"})`;
return `${(0, colors_1.colorFunction)("UnknownContractAndFunction")}${extra.length !== 0 ? `{${extra.join(",")}}` : ""}(${(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"})`;
}

@@ -92,0 +94,0 @@ }

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatError = void 0;
const utils_1 = require("ethers/lib/utils");
const colors_1 = require("../colors");

@@ -10,13 +9,6 @@ const object_1 = require("./object");

async function formatError(revertData, dependencies) {
const commonErrors = [
"function Error(string reason)",
"function Panic(uint256 code)",
];
try {
const iface = new utils_1.Interface(commonErrors);
const parsed = iface.parseTransaction({
data: revertData,
});
if (parsed.name === "Panic") {
const panicCode = parsed.args.code.toNumber();
const { fragment, revertResult, } = await dependencies.tracerEnv.decoder.decodeError(revertData);
if (fragment.name === "Panic") {
const panicCode = revertResult.code.toNumber();
let situation = "";

@@ -52,3 +44,3 @@ switch (panicCode) {

}
return `${(0, colors_1.colorError)(parsed.name)}(${(0, object_1.formatObject)({
return `${(0, colors_1.colorError)(fragment.name)}(${(0, object_1.formatObject)({
code: panicCode,

@@ -58,17 +50,7 @@ situation,

}
const formatted = (0, result_1.formatResult)(parsed.args, parsed.functionFragment.inputs, { decimals: -1, shorten: false }, dependencies);
return `${(0, colors_1.colorError)(parsed.name)}(${formatted})`;
const formatted = (0, result_1.formatResult)(revertResult, fragment.inputs, { decimals: -1, shorten: false }, dependencies);
return `${(0, colors_1.colorError)(fragment.name)}(${formatted})`;
}
catch { }
// if error not common then try to parse it as a custom error
const names = await dependencies.artifacts.getAllFullyQualifiedNames();
for (const name of names) {
const artifact = await dependencies.artifacts.readArtifact(name);
const iface = new utils_1.Interface(artifact.abi);
try {
const errorDesc = iface.parseError(revertData);
return `${(0, colors_1.colorError)(errorDesc.name)}(${(0, result_1.formatResult)(errorDesc.args, errorDesc.errorFragment.inputs, { decimals: -1, shorten: false }, dependencies)})`;
}
catch { }
}
// if error could not be decoded, then just show the data
return `${(0, colors_1.colorError)("UnknownError")}(${(0, param_1.formatParam)(revertData, dependencies)})`;

@@ -75,0 +57,0 @@ }

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatLog = void 0;
const utils_1 = require("ethers/lib/utils");
const colors_1 = require("../colors");
const utils_2 = require("../utils");
const param_1 = require("./param");
const result_1 = require("./result");
const utils_1 = require("../utils");
async function formatLog(log, currentAddress, dependencies) {
// TODO make the contractName code common between formatCall and formatLog
const nameTag = currentAddress
? (0, utils_2.getFromNameTags)(currentAddress, dependencies)
: undefined;
const names = await dependencies.artifacts.getAllFullyQualifiedNames();
const code = !nameTag && currentAddress
? (await dependencies.provider.send("eth_getCode", [
currentAddress,
"latest",
]))
: undefined;
let str;
let contractName = nameTag;
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, code ?? "0x") > 0.5) {
contractName = artifact.contractName;
let fragment, result, contractName;
try {
({
fragment,
result,
contractName,
} = await dependencies.tracerEnv.decoder.decodeEvent(log.topics, log.data));
// use just contract name
contractName = contractName.split(":")[1];
}
catch { }
// find a better contract name
if (currentAddress) {
const betterContractName = await (0, utils_1.getBetterContractName)(currentAddress, dependencies);
if (betterContractName) {
contractName = betterContractName;
}
// try to parse the arguments
try {
const parsed = iface.parseLog(log);
const decimals = -1;
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)})`;
else if (contractName) {
dependencies.tracerEnv.nameTags[currentAddress] = contractName;
}
catch { }
// if we got both the contract name and arguments parsed so far, we can stop
if (contractName && str) {
return (0, colors_1.colorContract)(contractName) + "." + str;
}
}
return (`<${(0, colors_1.colorContract)("UnknownContract")} ${(0, param_1.formatParam)(currentAddress, dependencies)}>.` +
(str ??
`${(0, colors_1.colorEvent)("UnknownEvent")}(${(0, param_1.formatParam)(log.data, dependencies)}, ${(0, param_1.formatParam)(log.topics, dependencies)})`));
const firstPart = `${(0, colors_1.colorContract)(contractName ? contractName : "UnknownContract")}${dependencies.tracerEnv.showAddresses || !contractName
? `(${currentAddress})`
: ""}`;
const secondPart = fragment && result
? `${(0, colors_1.colorEvent)(fragment.name)}(${(0, result_1.formatResult)(result, fragment.inputs, { decimals: -1, shorten: false }, dependencies)})`
: `${(0, colors_1.colorEvent)("UnknownEvent")}(${(0, param_1.formatParam)(log.data, dependencies)}, ${(0, param_1.formatParam)(log.topics, dependencies)})`;
return `${firstPart}.${secondPart}`;
}
exports.formatLog = formatLog;
//# sourceMappingURL=log.js.map

@@ -18,3 +18,3 @@ "use strict";

// see if the data is a call
const formattedCallPromise = (0, call_1.formatCall)(ethers_1.ethers.constants.AddressZero, args.data, "0x", 0, 0, td);
const formattedCallPromise = (0, call_1.formatCall)(ethers_1.ethers.constants.AddressZero, args.data, "0x", 0, 0, true, td);
const formattedErrorPromise = (0, error_1.formatError)(args.data, td);

@@ -21,0 +21,0 @@ const formattedCall = await formattedCallPromise;

@@ -0,4 +1,6 @@

import "./compile";
import "./decode";
import "./test";
import "./trace";
import "./tracecall";
//# sourceMappingURL=index.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
require("./compile");
require("./decode");
require("./test");
require("./trace");
require("./tracecall");
//# sourceMappingURL=index.js.map

@@ -14,3 +14,5 @@ "use strict";

const tracerEnv = global.tracerEnv;
const recorder = new recorder_1.TraceRecorder(vm, tracerEnv);
// @ts-ignore
const hreArtifacts = global.hreArtifacts;
const recorder = new recorder_1.TraceRecorder(vm, tracerEnv, hreArtifacts);
tracerEnv.recorder = recorder;

@@ -17,0 +19,0 @@ // @ts-ignore

@@ -10,2 +10,3 @@ import { TracerDependencies } from "../../types";

gasUsed?: number;
success?: boolean;
}

@@ -12,0 +13,0 @@ declare function format(item: Item<CALL>, dependencies: TracerDependencies): Promise<string>;

@@ -8,5 +8,6 @@ "use strict";

// TODO refactor these input types or order
item.params.returnData ?? "0x", item.params.value, item.params.gasLimit, dependencies)));
item.params.returnData ?? "0x", item.params.value, item.params.gasLimit, item.params.success ?? true, // if we don't have success, assume it was successful
dependencies)));
}
exports.default = { format };
//# sourceMappingURL=call.js.map

@@ -9,2 +9,3 @@ import { TracerDependencies } from "../../types";

gasUsed?: number;
success?: boolean;
}

@@ -11,0 +12,0 @@ declare function format(item: Item<DELEGATECALL>, dependencies: TracerDependencies): Promise<string>;

@@ -9,5 +9,6 @@ "use strict";

item.params.returnData ?? "0x", 0, // TODO show some how that msg.value
item.params.gasLimit, dependencies)));
item.params.gasLimit, item.params.success ?? true, // if we don't have success, assume it was successful
dependencies)));
}
exports.default = { format };
//# sourceMappingURL=delegatecall.js.map

@@ -21,2 +21,3 @@ "use strict";

const log_1 = __importDefault(require("./log"));
const selfdestruct_1 = __importDefault(require("./selfdestruct"));
function parse(step, currentAddress) {

@@ -71,2 +72,4 @@ switch (step.opcode.name) {

return await revert_1.default.format(item, dependencies);
case "SELFDESTRUCT":
return await selfdestruct_1.default.format(item);
default:

@@ -73,0 +76,0 @@ return item.opcode + " not implemented";

@@ -9,2 +9,3 @@ import { TracerDependencies } from "../../types";

gasUsed?: number;
success?: boolean;
}

@@ -11,0 +12,0 @@ declare function format(item: Item<STATICCALL>, dependencies: TracerDependencies): Promise<string>;

@@ -19,5 +19,6 @@ "use strict";

// TODO refactor these input types or order
item.params.returnData ?? "0x", 0, item.params.gasLimit, dependencies)));
item.params.returnData ?? "0x", 0, item.params.gasLimit, item.params.success ?? true, // if we don't have success, assume it was successful
dependencies)));
}
exports.default = { format };
//# sourceMappingURL=staticcall.js.map

@@ -10,2 +10,3 @@ /// <reference types="node" />

import { TracerEnv } from "../types";
import { Artifacts } from "hardhat/types";
interface NewContractEvent {

@@ -23,3 +24,3 @@ address: Address;

addressStack: string[];
constructor(vm: VM, tracerEnv: TracerEnv);
constructor(vm: VM, tracerEnv: TracerEnv, artifacts: Artifacts);
handleBeforeTx(tx: TypedTransaction, resolve: ((result?: any) => void) | undefined): void;

@@ -26,0 +27,0 @@ handleBeforeMessage(message: Message, resolve: ((result?: any) => void) | undefined): void;

@@ -9,3 +9,3 @@ "use strict";

class TraceRecorder {
constructor(vm, tracerEnv) {
constructor(vm, tracerEnv, artifacts) {
this.previousTraces = [];

@@ -16,3 +16,3 @@ this.vm = vm;

if (tracerEnv.stateOverrides) {
(0, utils_1.applyStateOverrides)(tracerEnv.stateOverrides, vm);
(0, utils_1.applyStateOverrides)(tracerEnv.stateOverrides, vm, artifacts);
}

@@ -200,7 +200,30 @@ this.awaitedItems = [];

handleAfterMessage(evmResult, resolve) {
// console.log("handleAfterMessage");
// console.log("handleAfterMessage", !evmResult?.execResult?.exceptionError);
if (!this.trace) {
throw new Error("internal error: trace is undefined");
}
this.trace.returnCurrentCall("0x" + evmResult.execResult.returnValue.toString("hex"));
if (evmResult.execResult.selfdestruct) {
const selfdestructs = Object.entries(evmResult.execResult.selfdestruct);
for (const [address, beneficiary] of selfdestructs) {
console.log("selfdestruct");
// console.log(
// "selfdestruct recorded",
// address,
// hexPrefix(beneficiary.toString("hex"))
// );
this.trace.insertItem({
opcode: "SELFDESTRUCT",
params: {
beneficiary: (0, utils_1.hexPrefix)(beneficiary.toString("hex")),
},
});
}
}
// this.trace.insertItem({
// opcode: "SELFDESTRUCT",
// params: {
// beneficiary: hexPrefix("1234"),
// },
// });
this.trace.returnCurrentCall("0x" + evmResult.execResult.returnValue.toString("hex"), !evmResult?.execResult?.exceptionError);
this.addressStack.pop();

@@ -207,0 +230,0 @@ resolve?.();

import { InterpreterStep } from "@nomicfoundation/ethereumjs-evm";
import { TracerDependencies } from "../types";
import { CALL } from "./opcodes/call";
export interface Item<Params> {

@@ -15,11 +16,3 @@ opcode: string;

};
export interface CallParams {
to?: string;
inputData: string;
value: string;
returnData?: string;
gasLimit: number;
gasUsed?: number;
}
export interface CallItem extends Item<CallParams> {
export interface CallItem extends Item<CALL> {
opcode: CALL_OPCODES;

@@ -35,3 +28,3 @@ children: Item<any>[];

}): void;
returnCurrentCall(returnData: string): void;
returnCurrentCall(returnData: string, success: boolean): void;
print(dependencies: TracerDependencies): Promise<void>;

@@ -38,0 +31,0 @@ }

@@ -38,6 +38,7 @@ "use strict";

// TODO see how to do this
returnCurrentCall(returnData) {
returnCurrentCall(returnData, success) {
if (!this.parent)
throw new Error("this.parent is undefined");
this.parent.params.returnData = returnData;
this.parent.params.success = success;
this.parent = this.parent.parent;

@@ -44,0 +45,0 @@ }

@@ -11,2 +11,3 @@ import { Artifacts } from "hardhat/types";

defaultVerbosity?: number;
showAddresses?: boolean;
gasCost?: boolean;

@@ -22,2 +23,3 @@ opcodes?: string[];

verbosity: number;
showAddresses: boolean;
gasCost: boolean;

@@ -27,2 +29,3 @@ opcodes: Map<string, boolean>;

_internal: {
tokenDecimalsCache: Map<string, number>;
printNameTagTip: undefined | "print it" | "already printed";

@@ -61,3 +64,3 @@ };

};
bytecode?: string;
bytecode?: ContractInfo;
balance?: BigNumberish;

@@ -67,2 +70,8 @@ nonce?: BigNumberish;

}
export declare type ContractInfo = string | {
name: string;
libraries?: {
[libraryName: string]: ContractInfo;
};
};
//# sourceMappingURL=types.d.ts.map
import { BigNumber } from "ethers";
import { VM } from "@nomicfoundation/ethereumjs-vm";
import { ConfigurableTaskDefinition, HardhatRuntimeEnvironment } from "hardhat/types";
import { Artifacts, ConfigurableTaskDefinition, HardhatRuntimeEnvironment } from "hardhat/types";
import { ProviderLike, StateOverrides, StructLog, TracerDependencies, TracerEnv } from "./types";

@@ -28,6 +28,8 @@ import { Item } from "./trace/transaction";

export declare function isItem(item: any): item is Item<any>;
export declare function applyStateOverrides(stateOverrides: StateOverrides, vm: VM): Promise<void>;
export declare function applyStateOverrides(stateOverrides: StateOverrides, vm: VM, artifacts: Artifacts): 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>;
export declare function fetchContractNameUsingArtifacts(address: string, dependencies: TracerDependencies): Promise<string | undefined>;
export declare function getBetterContractName(address: string, dependencies: TracerDependencies): Promise<string | undefined>;
//# sourceMappingURL=utils.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
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;
exports.getBetterContractName = exports.fetchContractNameUsingArtifacts = 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");

@@ -61,6 +61,6 @@ const ethers_1 = require("ethers");

}
if (hre.tracer.recorder === undefined) {
throw new Error(`hardhat-tracer/utils/applyCliArgsToTracer: hre.tracer.recorder is undefined`);
// if recorder was already created, then check opcodes, else it will be checked later
if (hre.tracer.recorder !== undefined) {
checkIfOpcodesAreValid(hre.tracer.opcodes, hre.tracer.recorder.vm);
}
checkIfOpcodesAreValid(hre.tracer.opcodes, hre.tracer.recorder.vm);
}

@@ -180,4 +180,30 @@ if (args.gascost) {

exports.isItem = isItem;
async function applyStateOverrides(stateOverrides, vm) {
function getBytecode(contractInfo, artifacts, addressThis) {
if (typeof contractInfo === "string") {
if (ethers_1.ethers.utils.isHexString(contractInfo)) {
// directly bytecode was given
return contractInfo;
}
else {
// name was given
contractInfo = {
name: contractInfo,
};
}
}
const artifact = artifacts.readArtifactSync(contractInfo.name);
let bytecode = artifact.deployedBytecode;
if (bytecode.startsWith("0x730000000000000000000000000000000000000000")) {
// this is a library, so we need to replace the placeholder address
bytecode = "0x" + addressThis.slice(2) + bytecode.slice(44);
}
// TODO add support for linking libraries
// artifact.deployedLinkReferences;
return bytecode;
}
async function applyStateOverrides(stateOverrides, vm, artifacts) {
for (const [_address, overrides] of Object.entries(stateOverrides)) {
if (!ethers_1.ethers.utils.isAddress(_address)) {
throw new Error(`Invalid address ${_address} in stateOverrides`);
}
const address = ethereumjs_util_1.Address.fromString(_address);

@@ -197,3 +223,4 @@ // for balance and nonce

if (overrides.bytecode) {
await vm.stateManager.putContractCode(address, Buffer.from(overrides.bytecode, "hex"));
const bytecode = getBytecode(overrides.bytecode, artifacts, _address);
await vm.stateManager.putContractCode(address, Buffer.from(bytecode.slice(2), "hex"));
}

@@ -268,2 +295,39 @@ // for storage slots

exports.fetchContractDecimals = fetchContractDecimals;
async function fetchContractNameUsingArtifacts(address, dependencies) {
const toBytecode = await dependencies.provider.send("eth_getCode", [address]);
const names = await dependencies.artifacts.getAllFullyQualifiedNames();
for (const name of names) {
const _artifact = await dependencies.artifacts.readArtifact(name);
// try to find the contract name
if (compareBytecode(_artifact.deployedBytecode, toBytecode) > 0.5 ||
(address === 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
return _artifact.contractName;
}
}
}
exports.fetchContractNameUsingArtifacts = fetchContractNameUsingArtifacts;
async function getBetterContractName(address, dependencies) {
// 1. See if nameTag exists already
const nameTag = getFromNameTags(address, dependencies);
if (nameTag) {
return nameTag;
}
// 2. See if there is a name() method that gives string or bytes32
dependencies.tracerEnv.enabled = false; // disable tracer to avoid tracing these calls
const contractNameFromNameMethod = await fetchContractName(address, dependencies.provider);
dependencies.tracerEnv.enabled = true; // enable tracer back
if (contractNameFromNameMethod) {
dependencies.tracerEnv.nameTags[address] = contractNameFromNameMethod;
return contractNameFromNameMethod;
}
// 3. Match bytecode
const contractNameFromArtifacts = await fetchContractNameUsingArtifacts(address, dependencies);
if (contractNameFromArtifacts) {
dependencies.tracerEnv.nameTags[address] = contractNameFromArtifacts;
return contractNameFromArtifacts;
}
}
exports.getBetterContractName = getBetterContractName;
//# sourceMappingURL=utils.js.map

@@ -63,2 +63,3 @@ "use strict";

else {
this.dependencies.tracerEnv.printNext = false;
await this.dependencies.tracerEnv.recorder?.previousTraces?.[this.dependencies.tracerEnv.recorder?.previousTraces.length - 1]?.print?.(this.dependencies);

@@ -105,2 +106,3 @@ }

verbosity: utils_1.DEFAULT_VERBOSITY,
showAddresses: false,
gasCost: false,

@@ -112,2 +114,3 @@ opcodes: new Map(),

printNameTagTip: undefined,
tokenDecimalsCache: new Map(),
},

@@ -114,0 +117,0 @@ decoder: new decoder_1.Decoder(artifacts),

{
"name": "hardhat-tracer",
"version": "2.0.0-beta.2",
"version": "2.0.0-beta.3",
"description": "Hardhat Tracer plugin",

@@ -43,3 +43,4 @@ "repository": "github:zemse/hardhat-tracer",

"chai": "^4.2.0",
"hardhat": "^2.11.2",
"hardhat": "^2.12.2",
"hardhat-deploy": "^0.11.20",
"mocha": "^7.1.2",

@@ -46,0 +47,0 @@ "prettier": "2.0.5",

import { ethers } from "ethers";
import {
ErrorFragment,
EventFragment,
fetchJson,

@@ -10,3 +11,2 @@ Fragment,

} from "ethers/lib/utils";
import { string } from "hardhat/internal/core/params/argumentTypes";
import { Artifacts } from "hardhat/types";

@@ -22,2 +22,3 @@

errorFragmentsBySelector: Mapping<ErrorFragment> = new Map();
eventFragmentsByTopic0: Mapping<EventFragment> = new Map();

@@ -27,6 +28,10 @@ ready: Promise<void>;

constructor(artifacts: Artifacts) {
this.ready = this._constructor(artifacts);
this.ready = this._updateArtifacts(artifacts);
}
async _constructor(artifacts: Artifacts) {
async updateArtifacts(artifacts: Artifacts) {
this.ready = this._updateArtifacts(artifacts);
}
async _updateArtifacts(artifacts: Artifacts) {
const names = await artifacts.getAllFullyQualifiedNames();

@@ -40,3 +45,12 @@

copyFragments(name, iface.errors, this.errorFragmentsBySelector);
copyFragments(name, iface.events, this.eventFragmentsByTopic0);
}
// common errors, these are in function format because Ethers.js does not accept them as errors
const commonErrors = [
"function Error(string reason)",
"function Panic(uint256 code)",
];
const iface = new Interface(commonErrors);
copyFragments("", iface.functions, this.errorFragmentsBySelector);
}

@@ -71,2 +85,67 @@

}
async decodeFunction(
inputData: string,
returnData: string
): Promise<{
fragment: Fragment;
inputResult: Result;
returnResult: Result | undefined;
contractName: string;
}> {
await this.ready;
return decode(
inputData,
returnData,
"function",
this.functionFragmentsBySelector
);
}
async decodeError(
revertData: string
): Promise<{
fragment: Fragment;
revertResult: Result;
contractName: string;
}> {
await this.ready;
const { fragment, inputResult, contractName } = await decode(
revertData,
"0x",
"error",
this.errorFragmentsBySelector
);
return { fragment, revertResult: inputResult, contractName };
}
async decodeEvent(
topics: string[],
data: string
): Promise<{
fragment: EventFragment;
result: Result;
contractName: string;
}> {
await this.ready;
if (topics.length === 0) {
throw new Error("No topics, cannot decode");
}
const topic0 = topics[0];
const fragments = this.eventFragmentsByTopic0.get(topic0);
if (fragments) {
for (const { contractName, fragment } of fragments) {
try {
const iface = new ethers.utils.Interface([fragment]);
const result = iface.parseLog({ data, topics });
return { fragment, result: result.args, contractName };
} catch {}
}
}
throw decodeError(topic0);
}
}

@@ -89,3 +168,5 @@

) {
const selector = ethers.utils.Interface.getSighash(fragment);
const selector = EventFragment.isEventFragment(fragment)
? ethers.utils.Interface.getEventTopic(fragment)
: ethers.utils.Interface.getSighash(fragment);
let fragments = mapping.get(selector);

@@ -95,2 +176,3 @@ if (!fragments) {

}
// TODO while adding, see if we already have a same signature fragment
fragments.push({ contractName, fragment });

@@ -165,10 +247,13 @@ }

// we couldn't decode it using local ABI, try 4byte.directory
try {
const { fragment, inputResult } = await decodeUsing4byteDirectory(
selector,
inputData,
mapping
);
return { fragment, inputResult };
} catch {}
// currently only supports function calls
if (type === "function") {
try {
const { fragment, inputResult } = await decodeUsing4byteDirectory(
selector,
inputData,
mapping
);
return { fragment, inputResult };
} catch {}
}

@@ -175,0 +260,0 @@ // we couldn't decode it after even using 4byte.directory, give up

@@ -47,2 +47,3 @@ import {

verbosity: userConfig.tracer?.defaultVerbosity ?? DEFAULT_VERBOSITY,
showAddresses: userConfig.tracer?.showAddresses ?? false,
gasCost: userConfig.tracer?.gasCost ?? false,

@@ -54,2 +55,3 @@ opcodes,

printNameTagTip: undefined,
tokenDecimalsCache: new Map(),
},

@@ -56,0 +58,0 @@ stateOverrides: userConfig.tracer?.stateOverrides,

@@ -8,2 +8,3 @@ import { extendEnvironment } from "hardhat/config";

import { Decoder } from "../decoder";
import { hardhatArguments } from "hardhat";

@@ -24,12 +25,13 @@ declare module "hardhat/types/runtime" {

global.tracerEnv = hre.tracer;
// @ts-ignore
global.hreArtifacts = hre.artifacts;
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);
});
getVM(hre)
.then((vm) => {
hre.tracer.recorder = new TraceRecorder(vm, hre.tracer, hre.artifacts);
})
.catch(() => {
// if for some reason we can't get the vm, disable hardhat-tracer
hre.tracer.enabled = false;
});
});

@@ -1,22 +0,14 @@

import { BigNumber, BigNumberish, ethers } from "ethers";
import { BigNumber, BigNumberish } from "ethers";
import { colorContract, colorFunction, colorKey } from "../colors";
import { fetchContractDecimals, getBetterContractName } from "../utils";
import { formatParam } from "./param";
import { formatResult } from "./result";
import {
formatEther,
Fragment,
FunctionFragment,
Interface,
Result,
} from "ethers/lib/utils";
import { Artifact } from "hardhat/types";
import { colorContract, colorFunction, colorKey } from "../colors";
import { ProviderLike, TracerDependencies } from "../types";
import {
compareBytecode,
fetchContractDecimals,
fetchContractName,
getFromNameTags,
} from "../utils";
import { formatParam } from "./param";
import { formatResult } from "./result";
import { SEPARATOR } from "./separator";
import { TracerDependencies } from "../types";

@@ -29,8 +21,5 @@ export async function formatCall(

gas: BigNumberish,
success: boolean,
dependencies: TracerDependencies
) {
const names = await dependencies.artifacts.getAllFullyQualifiedNames();
// TODO handle if `to` is console.log address
let contractName: string | undefined;

@@ -48,3 +37,3 @@ let contractDecimals: number | undefined;

returnResult,
} = await dependencies.tracerEnv.decoder!.decode(input, ret));
} = await dependencies.tracerEnv.decoder!.decodeFunction(input, ret));

@@ -55,35 +44,11 @@ // use just contract name

// 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);
// 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;
}
}
// find a better contract name
const betterContractName = await getBetterContractName(to, dependencies);
if (betterContractName) {
contractName = betterContractName;
} else if (contractName) {
dependencies.tracerEnv.nameTags[to] = contractName;
}
// if ERC20 method found then fetch decimals
if (

@@ -94,5 +59,28 @@ input.slice(0, 10) === "0x70a08231" || // balanceOf

) {
contractDecimals = await fetchContractDecimals(to, dependencies.provider);
// see if we already know the decimals
const { tokenDecimalsCache } = dependencies.tracerEnv._internal;
const decimals = tokenDecimalsCache.get(to);
if (decimals) {
// if we know decimals then use it
contractDecimals = decimals !== -1 ? decimals : undefined;
} else {
// otherwise fetch it
contractDecimals = await fetchContractDecimals(to, dependencies.provider);
// and cache it
if (contractDecimals !== undefined) {
tokenDecimalsCache.set(to, contractDecimals);
} else {
tokenDecimalsCache.set(to, -1);
}
}
}
const extra = [];
if ((value = BigNumber.from(value)).gt(0)) {
extra.push(`value${SEPARATOR}${formatEther(value)}`);
}
if ((gas = BigNumber.from(gas)).gt(0) && dependencies.tracerEnv.gasCost) {
extra.push(`gas${SEPARATOR}${formatParam(gas, dependencies)}`);
}
if (inputResult && fragment) {

@@ -112,21 +100,13 @@ const inputArgs = formatResult(

)
: // if return data is not decoded, then show return data only if call was success
ret !== "0x" && success !== false // success can be undefined
? ret
: "";
const extra = [];
if ((value = BigNumber.from(value)).gt(0)) {
extra.push(`value${SEPARATOR}${formatParam(value, dependencies)}`);
}
if ((gas = BigNumber.from(gas)).gt(0) && dependencies.tracerEnv.gasCost) {
extra.push(`gas${SEPARATOR}${formatParam(gas, dependencies)}`);
}
const nameTag = getFromNameTags(to, dependencies);
let nameToPrint = contractName ?? "UnknownContract";
return `${
nameTag
? colorContract(nameTag)
: contractName
? colorContract(contractName)
: `<${colorContract("UnknownContract")} ${formatParam(
to,
dependencies
)}>`
dependencies.tracerEnv.showAddresses || nameToPrint === "UnknownContract"
? `${colorContract(nameToPrint)}(${to})`
: colorContract(nameToPrint)
}.${colorFunction(fragment.name)}${

@@ -139,14 +119,18 @@ extra.length !== 0 ? `{${extra.join(",")}}` : ""

if (contractName) {
return `${colorContract(contractName)}.<${colorFunction(
"UnknownFunction"
)}>(${colorKey("input" + SEPARATOR)}${input}, ${colorKey(
return `${
dependencies.tracerEnv.showAddresses
? `${colorContract(contractName)}(${to})`
: colorContract(contractName)
}.<${colorFunction("UnknownFunction")}>${
extra.length !== 0 ? `{${extra.join(",")}}` : ""
}(${colorKey("input" + SEPARATOR)}${input}, ${colorKey(
"ret" + SEPARATOR
)}${ret})`;
} else {
return `${colorFunction("UnknownContractAndFunction")}(${colorKey(
"to" + SEPARATOR
)}${to}, ${colorKey("input" + SEPARATOR)}${input}, ${colorKey(
"ret" + SEPARATOR
)}${ret || "0x"})`;
return `${colorFunction("UnknownContractAndFunction")}${
extra.length !== 0 ? `{${extra.join(",")}}` : ""
}(${colorKey("to" + SEPARATOR)}${to}, ${colorKey(
"input" + SEPARATOR
)}${input}, ${colorKey("ret" + SEPARATOR)}${ret || "0x"})`;
}
}

@@ -14,14 +14,10 @@ import { Interface } from "ethers/lib/utils";

) {
const commonErrors = [
"function Error(string reason)",
"function Panic(uint256 code)",
];
try {
const iface = new Interface(commonErrors);
const parsed = iface.parseTransaction({
data: revertData,
});
const {
fragment,
revertResult,
} = await dependencies.tracerEnv.decoder!.decodeError(revertData);
if (parsed.name === "Panic") {
const panicCode = parsed.args.code.toNumber();
if (fragment.name === "Panic") {
const panicCode = revertResult.code.toNumber();
let situation = "";

@@ -57,3 +53,3 @@ switch (panicCode) {

}
return `${colorError(parsed.name)}(${formatObject({
return `${colorError(fragment.name)}(${formatObject({
code: panicCode,

@@ -65,4 +61,4 @@ situation,

const formatted = formatResult(
parsed.args,
parsed.functionFragment.inputs,
revertResult,
fragment.inputs,
{ decimals: -1, shorten: false },

@@ -72,23 +68,6 @@ dependencies

return `${colorError(parsed.name)}(${formatted})`;
return `${colorError(fragment.name)}(${formatted})`;
} catch {}
// if error not common then try to parse it as a custom error
const names = await dependencies.artifacts.getAllFullyQualifiedNames();
for (const name of names) {
const artifact = await dependencies.artifacts.readArtifact(name);
const iface = new Interface(artifact.abi);
try {
const errorDesc = iface.parseError(revertData);
return `${colorError(errorDesc.name)}(${formatResult(
errorDesc.args,
errorDesc.errorFragment.inputs,
{ decimals: -1, shorten: false },
dependencies
)})`;
} catch {}
}
// if error could not be decoded, then just show the data
return `${colorError("UnknownError")}(${formatParam(

@@ -95,0 +74,0 @@ revertData,

@@ -1,10 +0,7 @@

import { Interface, LogDescription } from "ethers/lib/utils";
import { Artifact } from "hardhat/types";
import { colorContract, colorEvent } from "../colors";
import { TracerDependencies, TracerDependenciesExtended } from "../types";
import { compareBytecode, getFromNameTags } from "../utils";
import { EventFragment, Result } from "ethers/lib/utils";
import { formatParam } from "./param";
import { formatResult } from "./result";
import { getBetterContractName } from "../utils";
import { TracerDependencies } from "../types";

@@ -16,60 +13,54 @@ export async function formatLog(

) {
// TODO make the contractName code common between formatCall and formatLog
const nameTag = currentAddress
? getFromNameTags(currentAddress, dependencies)
: undefined;
const names = await dependencies.artifacts.getAllFullyQualifiedNames();
const code =
!nameTag && currentAddress
? ((await dependencies.provider.send("eth_getCode", [
currentAddress,
"latest",
])) as string)
: undefined;
let fragment: EventFragment | undefined,
result: Result | undefined,
contractName: string | undefined;
try {
({
fragment,
result,
contractName,
} = await dependencies.tracerEnv.decoder!.decodeEvent(
log.topics,
log.data
));
let str: string | undefined;
let contractName: string | undefined = nameTag;
for (const name of names) {
const artifact = await dependencies.artifacts.readArtifact(name);
const iface = new Interface(artifact.abi);
// use just contract name
contractName = contractName.split(":")[1];
} catch {}
// try to find the contract name
if (compareBytecode(artifact.deployedBytecode, code ?? "0x") > 0.5) {
contractName = artifact.contractName;
// find a better contract name
if (currentAddress) {
const betterContractName = await getBetterContractName(
currentAddress,
dependencies
);
if (betterContractName) {
contractName = betterContractName;
} else if (contractName) {
dependencies.tracerEnv.nameTags[currentAddress] = contractName;
}
}
// try to parse the arguments
try {
const parsed = iface.parseLog(log);
const decimals = -1;
const firstPart = `${colorContract(
contractName ? contractName : "UnknownContract"
)}${
dependencies.tracerEnv.showAddresses || !contractName
? `(${currentAddress})`
: ""
}`;
if (!contractName) {
contractName = artifact.contractName;
}
const secondPart =
fragment && result
? `${colorEvent(fragment.name)}(${formatResult(
result,
fragment.inputs,
{ decimals: -1, shorten: false },
dependencies
)})`
: `${colorEvent("UnknownEvent")}(${formatParam(
log.data,
dependencies
)}, ${formatParam(log.topics, dependencies)})`;
str = `${colorEvent(parsed.name)}(${formatResult(
parsed.args,
parsed.eventFragment.inputs,
{ decimals, shorten: false },
dependencies
)})`;
} catch {}
// if we got both the contract name and arguments parsed so far, we can stop
if (contractName && str) {
return colorContract(contractName) + "." + str;
}
}
return (
`<${colorContract("UnknownContract")} ${formatParam(
currentAddress,
dependencies
)}>.` +
(str ??
`${colorEvent("UnknownEvent")}(${formatParam(
log.data,
dependencies
)}, ${formatParam(log.topics, dependencies)})`)
);
return `${firstPart}.${secondPart}`;
}

@@ -24,2 +24,3 @@ import { ethers } from "ethers";

0,
true,
td

@@ -26,0 +27,0 @@ );

@@ -0,3 +1,5 @@

import "./compile";
import "./decode";
import "./test";
import "./trace";
import "./tracecall";
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";

@@ -20,3 +18,5 @@ import { addCliParams, applyCliArgsToTracer } from "../utils";

const tracerEnv = global.tracerEnv;
const recorder = new TraceRecorder(vm, tracerEnv);
// @ts-ignore
const hreArtifacts = global.hreArtifacts;
const recorder = new TraceRecorder(vm, tracerEnv, hreArtifacts);
tracerEnv.recorder = recorder;

@@ -23,0 +23,0 @@ // @ts-ignore

@@ -12,2 +12,3 @@ import { formatCall } from "../../format/call";

gasUsed?: number;
success?: boolean;
}

@@ -28,2 +29,3 @@

item.params.gasLimit,
item.params.success ?? true, // if we don't have success, assume it was successful
dependencies

@@ -30,0 +32,0 @@ ))

@@ -11,2 +11,3 @@ import { formatCall } from "../../format/call";

gasUsed?: number;
success?: boolean;
}

@@ -27,2 +28,3 @@

item.params.gasLimit,
item.params.success ?? true, // if we don't have success, assume it was successful
dependencies

@@ -29,0 +31,0 @@ ))

@@ -18,2 +18,3 @@ import call from "./call";

import log from "./log";
import selfdestruct from "./selfdestruct";

@@ -75,2 +76,4 @@ export function parse(

return await revert.format(item, dependencies);
case "SELFDESTRUCT":
return await selfdestruct.format(item);
default:

@@ -77,0 +80,0 @@ return item.opcode + " not implemented";

@@ -18,2 +18,3 @@ import { colorConsole } from "../../colors";

gasUsed?: number;
success?: boolean;
}

@@ -43,2 +44,3 @@

item.params.gasLimit,
item.params.success ?? true, // if we don't have success, assume it was successful
dependencies

@@ -45,0 +47,0 @@ ))

@@ -22,2 +22,3 @@ import { InterpreterStep } from "@nomicfoundation/ethereumjs-evm";

import { TracerEnv } from "../types";
import { Artifacts } from "hardhat/types";

@@ -41,3 +42,3 @@ // const txs: TransactionTrace[] = [];

constructor(vm: VM, tracerEnv: TracerEnv) {
constructor(vm: VM, tracerEnv: TracerEnv, artifacts: Artifacts) {
this.vm = vm;

@@ -49,3 +50,3 @@ this.tracerEnv = tracerEnv;

if (tracerEnv.stateOverrides) {
applyStateOverrides(tracerEnv.stateOverrides, vm);
applyStateOverrides(tracerEnv.stateOverrides, vm, artifacts);
}

@@ -274,3 +275,3 @@

) {
// console.log("handleAfterMessage");
// console.log("handleAfterMessage", !evmResult?.execResult?.exceptionError);

@@ -281,4 +282,32 @@ if (!this.trace) {

if (evmResult.execResult.selfdestruct) {
const selfdestructs = Object.entries(evmResult.execResult.selfdestruct);
for (const [address, beneficiary] of selfdestructs) {
console.log("selfdestruct");
// console.log(
// "selfdestruct recorded",
// address,
// hexPrefix(beneficiary.toString("hex"))
// );
this.trace.insertItem({
opcode: "SELFDESTRUCT",
params: {
beneficiary: hexPrefix(beneficiary.toString("hex")),
},
});
}
}
// this.trace.insertItem({
// opcode: "SELFDESTRUCT",
// params: {
// beneficiary: hexPrefix("1234"),
// },
// });
this.trace.returnCurrentCall(
"0x" + evmResult.execResult.returnValue.toString("hex")
"0x" + evmResult.execResult.returnValue.toString("hex"),
!evmResult?.execResult?.exceptionError
);

@@ -285,0 +314,0 @@ this.addressStack.pop();

@@ -6,2 +6,3 @@ // export type AbstractParams = { [key: string]: any };

import { format } from "./opcodes";
import { CALL } from "./opcodes/call";

@@ -22,12 +23,3 @@ export interface Item<Params> {

export interface CallParams {
to?: string;
inputData: string;
value: string; // hex string
returnData?: string;
gasLimit: number;
gasUsed?: number;
}
export interface CallItem extends Item<CallParams> {
export interface CallItem extends Item<CALL> {
opcode: CALL_OPCODES;

@@ -96,5 +88,6 @@ children: Item<any>[];

// TODO see how to do this
returnCurrentCall(returnData: string) {
returnCurrentCall(returnData: string, success: boolean) {
if (!this.parent) throw new Error("this.parent is undefined");
this.parent.params.returnData = returnData;
this.parent.params.success = success;
this.parent = this.parent.parent as CallItem;

@@ -101,0 +94,0 @@ }

@@ -14,2 +14,3 @@ import { VM } from "@nomicfoundation/ethereumjs-vm";

defaultVerbosity?: number;
showAddresses?: boolean;
gasCost?: boolean;

@@ -26,2 +27,3 @@ opcodes?: string[];

verbosity: number;
showAddresses: boolean;
gasCost: boolean;

@@ -32,2 +34,3 @@ opcodes: Map<string, boolean>; // string[]; // TODO have a map of opcode to boolean

_internal: {
tokenDecimalsCache: Map<string, number>;
printNameTagTip:

@@ -74,3 +77,3 @@ | undefined // meaning "no need to print"

};
bytecode?: string;
bytecode?: ContractInfo;
balance?: BigNumberish;

@@ -80,1 +83,10 @@ nonce?: BigNumberish;

}
export type ContractInfo =
| string // bytecode in hex or name of the contract
| {
name: string;
libraries?: {
[libraryName: string]: ContractInfo;
};
};

@@ -10,2 +10,3 @@ import {

import {
Artifacts,
ConfigurableTaskDefinition,

@@ -16,2 +17,3 @@ HardhatRuntimeEnvironment,

import {
ContractInfo,
ProviderLike,

@@ -106,8 +108,6 @@ StateOverrides,

if (hre.tracer.recorder === undefined) {
throw new Error(
`hardhat-tracer/utils/applyCliArgsToTracer: hre.tracer.recorder is undefined`
);
// if recorder was already created, then check opcodes, else it will be checked later
if (hre.tracer.recorder !== undefined) {
checkIfOpcodesAreValid(hre.tracer.opcodes, hre.tracer.recorder.vm);
}
checkIfOpcodesAreValid(hre.tracer.opcodes, hre.tracer.recorder.vm);
}

@@ -252,7 +252,43 @@

function getBytecode(
contractInfo: ContractInfo,
artifacts: Artifacts,
addressThis: string
) {
if (typeof contractInfo === "string") {
if (ethers.utils.isHexString(contractInfo)) {
// directly bytecode was given
return contractInfo;
} else {
// name was given
contractInfo = {
name: contractInfo,
};
}
}
const artifact = artifacts.readArtifactSync(contractInfo.name);
let bytecode = artifact.deployedBytecode;
if (bytecode.startsWith("0x730000000000000000000000000000000000000000")) {
// this is a library, so we need to replace the placeholder address
bytecode = "0x" + addressThis.slice(2) + bytecode.slice(44);
}
// TODO add support for linking libraries
// artifact.deployedLinkReferences;
return bytecode;
}
export async function applyStateOverrides(
stateOverrides: StateOverrides,
vm: VM
vm: VM,
artifacts: Artifacts
) {
for (const [_address, overrides] of Object.entries(stateOverrides)) {
if (!ethers.utils.isAddress(_address)) {
throw new Error(`Invalid address ${_address} in stateOverrides`);
}
const address = Address.fromString(_address);

@@ -273,5 +309,6 @@ // for balance and nonce

if (overrides.bytecode) {
const bytecode = getBytecode(overrides.bytecode, artifacts, _address);
await vm.stateManager.putContractCode(
address,
Buffer.from(overrides.bytecode, "hex")
Buffer.from(bytecode.slice(2), "hex")
);

@@ -361,1 +398,56 @@ }

}
export async function fetchContractNameUsingArtifacts(
address: string,
dependencies: TracerDependencies
): Promise<string | undefined> {
const toBytecode = await dependencies.provider.send("eth_getCode", [address]);
const names = await dependencies.artifacts.getAllFullyQualifiedNames();
for (const name of names) {
const _artifact = await dependencies.artifacts.readArtifact(name);
// try to find the contract name
if (
compareBytecode(_artifact.deployedBytecode, toBytecode) > 0.5 ||
(address === ethers.constants.AddressZero && toBytecode.length <= 2)
) {
// if bytecode of "to" is the same as the deployed bytecode
// we can use the artifact name
return _artifact.contractName;
}
}
}
export async function getBetterContractName(
address: string,
dependencies: TracerDependencies
): Promise<string | undefined> {
// 1. See if nameTag exists already
const nameTag = getFromNameTags(address, dependencies);
if (nameTag) {
return nameTag;
}
// 2. See if there is a name() method that gives string or bytes32
dependencies.tracerEnv.enabled = false; // disable tracer to avoid tracing these calls
const contractNameFromNameMethod = await fetchContractName(
address,
dependencies.provider
);
dependencies.tracerEnv.enabled = true; // enable tracer back
if (contractNameFromNameMethod) {
dependencies.tracerEnv.nameTags[address] = contractNameFromNameMethod;
return contractNameFromNameMethod;
}
// 3. Match bytecode
const contractNameFromArtifacts = await fetchContractNameUsingArtifacts(
address,
dependencies
);
if (contractNameFromArtifacts) {
dependencies.tracerEnv.nameTags[address] = contractNameFromArtifacts;
return contractNameFromArtifacts;
}
}

@@ -81,2 +81,3 @@ import { ethers } from "ethers";

} else {
this.dependencies.tracerEnv.printNext = false;
await this.dependencies.tracerEnv.recorder?.previousTraces?.[

@@ -134,2 +135,3 @@ this.dependencies.tracerEnv.recorder?.previousTraces.length - 1

verbosity: DEFAULT_VERBOSITY,
showAddresses: false,
gasCost: false,

@@ -141,2 +143,3 @@ opcodes: new Map(),

printNameTagTip: undefined,
tokenDecimalsCache: new Map(),
},

@@ -143,0 +146,0 @@ decoder: new Decoder(artifacts),

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

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc