hardhat-tracer
Version:
Hardhat Tracer plugin
258 lines • 10.8 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TraceRecorder = void 0;
const debug_1 = __importDefault(require("debug"));
const opcodes_1 = require("./opcodes");
const transaction_trace_1 = require("./transaction-trace");
const check_opcodes_1 = require("./utils/check-opcodes");
const hex_1 = require("./utils/hex");
const item_1 = require("./utils/item");
const utils_1 = require("./utils");
const debug = (0, debug_1.default)("hardhat-tracer:trace-recorder");
class TraceRecorder {
constructor(vm, tracerEnv) {
this.previousTraces = [];
this.printIndex = -1;
this.previousTracesMap = new Map();
this.vm = vm;
this.tracerEnv = tracerEnv;
(0, check_opcodes_1.checkIfOpcodesAreValid)(tracerEnv.opcodes, vm);
this.awaitedItems = [];
this.addressStack = [];
vm.events.on("beforeTx", this.handleBeforeTx.bind(this));
vm.evm.events?.on("beforeMessage", this.handleBeforeMessage.bind(this));
// vm.evm.events?.on("newContract", this.handleNewContract.bind(this));
vm.evm.events?.on("step", this.handleStep.bind(this));
vm.evm.events?.on("afterMessage", this.handleAfterMessage.bind(this));
vm.events.on("afterTx", this.handleAfterTx.bind(this));
}
storeLastTrace(txHash) {
const lastTrace = this.previousTraces[this.previousTraces.length - 1];
if (lastTrace) {
this.previousTracesMap.set(txHash.toLowerCase(), lastTrace);
}
}
getTrace(txHash) {
return this.previousTracesMap.get(txHash.toLowerCase());
}
handleBeforeTx(
// tx: any, // TypedTransaction,
resolve) {
if (!this.tracerEnv.switch.verboseEnabled)
return resolve?.();
debug("handleBeforeTx");
this.trace = new transaction_trace_1.TransactionTrace();
// this.trace.hash = hexPrefix(Buffer.from(tx.hash()).toString("hex"));
resolve?.();
}
handleBeforeMessage(message, resolve) {
if (!this.tracerEnv.switch.verboseEnabled)
return resolve?.();
debug("handleBeforeMessage");
if (!this.trace) {
throw new Error("[hardhat-tracer]: trace is undefined in handleBeforeMessage");
}
const isDelegateCall = !!message.to &&
!!message.codeAddress &&
!message.to.equals(message.codeAddress);
const isStaticCall = message.isStaticCall;
const salt = undefined; // TODO
let item;
if (isDelegateCall) {
if (message.to === undefined) {
throw new Error("[hardhat-tracer]: message.to is undefined in handleBeforeMessage");
}
item = {
opcode: "DELEGATECALL",
params: {
from: (0, hex_1.hexPrefix)(message.caller.toString()),
to: (0, hex_1.hexPrefix)((message.codeAddress ?? message.to).toString()),
inputData: (0, hex_1.hexPrefix)(Buffer.from(message.data).toString("hex")),
gasLimit: Number(message.gasLimit.toString()),
value: this.trace.parent?.params.value,
},
children: [],
};
this.addressStack.push(item.params.to);
}
else if (message.to) {
item = {
opcode: isStaticCall ? "STATICCALL" : "CALL",
params: {
from: (0, hex_1.hexPrefix)(message.caller.toString()),
to: (0, hex_1.hexPrefix)(message.to.toString()),
inputData: (0, hex_1.hexPrefix)(Buffer.from(message.data).toString("hex")),
gasLimit: Number(message.gasLimit.toString()),
value: (0, hex_1.hexPrefix)(message.value.toString(16)),
},
children: [],
};
this.addressStack.push(item.params.to);
}
else if (message.to === undefined && salt === undefined) {
item = {
opcode: "CREATE",
params: {
from: (0, hex_1.hexPrefix)(message.caller.toString()),
initCode: (0, hex_1.hexPrefix)(Buffer.from(message.data).toString("hex")),
gasLimit: Number(message.gasLimit.toString()),
value: (0, hex_1.hexPrefix)(message.value.toString(16)),
},
children: [],
};
this.addressStack.push({ value: "lazy" });
}
else if (message.to === undefined && salt !== undefined) {
item = {
opcode: "CREATE2",
params: {
from: (0, hex_1.hexPrefix)(message.caller.toString()),
initCode: (0, hex_1.hexPrefix)(Buffer.from(message.data).toString("hex")),
gasLimit: Number(message.gasLimit.toString()),
value: (0, hex_1.hexPrefix)(message.value.toString(16)),
salt: (0, hex_1.hexPrefix)(Buffer.from(salt).toString("hex")),
},
children: [],
};
this.addressStack.push({ value: "lazy" });
}
else {
item = {
opcode: "UNKNOWN_MESSAGE",
params: {},
children: [],
};
console.error("handleBeforeMessage: message type not handled", message);
}
this.trace.insertItem(item, { increaseDepth: true });
resolve?.();
}
// This is now called in handleAfterMessage
// prev: public handleNewContract(
newContract(contractAddress, resolve) {
if (!this.tracerEnv.switch.verboseEnabled)
return resolve?.();
debug("handleNewContract %s", contractAddress);
if (!this.trace || !this.trace.parent) {
console.error("handleNewContract: trace.parent not present");
}
else {
switch (this.trace.parent.opcode) {
case "CREATE":
const createItem = this.trace.parent;
createItem.params.deployedAddress = (0, hex_1.hexPrefix)(contractAddress);
break;
case "CREATE2":
const create2Item = this.trace.parent;
create2Item.params.deployedAddress = (0, hex_1.hexPrefix)(contractAddress);
break;
default:
console.log(this.trace.parent);
console.error("handleNewContract: opcode not handled");
break;
}
}
let ptr = this.addressStack[this.addressStack.length - 1];
ptr.value = contractAddress;
resolve?.();
}
handleStep(step, resolve) {
if (!this.tracerEnv.switch.verboseEnabled)
return resolve?.();
// debug("handleStep %s", step.opcode.name);
if (!this.trace) {
throw new Error("[hardhat-tracer]: trace is undefined in handleStep");
}
if (this.awaitedItems.length) {
this.awaitedItems = this.awaitedItems.filter((awaitedItems) => awaitedItems.next > 0);
for (const awaitedItem of this.awaitedItems) {
awaitedItem.next--;
if (awaitedItem.next === 0) {
const item = awaitedItem.parse(step, this.addressStack[this.addressStack.length - 1]);
this.trace.insertItem(item);
}
}
}
if (this.tracerEnv.enableAllOpcodes ||
this.tracerEnv.opcodes.get(step.opcode.name)) {
const result = (0, opcodes_1.parse)(step, this.addressStack[this.addressStack.length - 1]);
if (result) {
if ((0, item_1.isItem)(result)) {
debug("parsed step %s", step.opcode.name);
this.trace.insertItem(result);
}
else {
debug("parsed step awaited %s", step.opcode.name);
this.awaitedItems.push(result);
}
}
}
this.previousOpcode = step.opcode.name;
resolve?.();
}
handleAfterMessage(evmResult, resolve) {
if (!this.tracerEnv.switch.verboseEnabled)
return resolve?.();
debug("handleAfterMessage");
if (!this.trace) {
throw new Error("[hardhat-tracer]: trace is undefined in handleAfterMessage");
}
let { errorStr, isException } = (0, utils_1.parseExec)(evmResult.execResult);
// TODO add support - not doing for now, will add if needed
// if (isSelfDestruct) {
// const selfdestructs = Object.entries(evmResult.execResult.selfdestruct);
// for (const [address, beneficiary] of selfdestructs) {
// debug("self destruct %s", address);
// this.trace.insertItem({
// opcode: "SELFDESTRUCT",
// params: {
// beneficiary: hexPrefix(beneficiary.toString("hex")),
// },
// });
// }
// }
if (isException) {
debug("exception %s", errorStr);
this.trace.insertItem({
opcode: "EXCEPTION",
params: {
error: errorStr,
type: "EvmError",
},
});
}
if (evmResult.execResult.contractAddress) {
this.newContract(evmResult.execResult.contractAddress.toString());
}
this.trace.returnCurrentCall((0, hex_1.hexPrefix)(evmResult.execResult.output?.toString("hex") ?? "0x"), Number(evmResult?.execResult.executionGasUsed), errorStr);
this.addressStack.pop();
resolve?.();
}
handleAfterTx(
// _tx: any, // AfterTxEvent,
resolve) {
if (!this.tracerEnv.switch.verboseEnabled)
return resolve?.();
debug("handleAfterTx");
if (this.tracerEnv.enabled) {
if (!this.trace) {
throw new Error("[hardhat-tracer]: trace is undefined in handleAfterTx");
}
// store the trace for later use (printing or outputting)
debug("record the trace");
this.previousTraces.push(this.trace);
}
// clear the trace
debug("clear running trace data");
this.trace = undefined;
this.previousOpcode = undefined;
this.awaitedItems = [];
this.addressStack = [];
resolve?.();
}
}
exports.TraceRecorder = TraceRecorder;
//# sourceMappingURL=trace-recorder.js.map