Skip to content

Commit

Permalink
Merge pull request #5411 from NomicFoundation/refactor/streamline-con…
Browse files Browse the repository at this point in the history
…sole-logger

Streamline `ConsoleLogger` logic and state
  • Loading branch information
Xanewok authored Jul 2, 2024
2 parents 411fef6 + cfd0884 commit aef55d2
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 125 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const eutil = require("@nomicfoundation/ethereumjs-util");
const fs = require("fs");
import fs from "fs";
import { bytesToInt } from "@nomicfoundation/ethereumjs-util";

const { keccak256 } = require("../internal/util/keccak");
import { keccak256 } from "../src/internal/util/keccak";

const functionPrefix = " function";
const functionBody =
Expand All @@ -12,7 +12,7 @@ const functionSuffix = "));" + "\n }" + "\n" + "\n";
let logger =
"// ------------------------------------\n" +
"// This code was autogenerated using\n" +
"// scripts/console-library-generator.js\n" +
"// scripts/console-library-generator.ts\n" +
"// ------------------------------------\n\n";

const singleTypes = [
Expand Down Expand Up @@ -80,14 +80,11 @@ library console {
`;

logger +=
"\n// In order to optimize map lookup\n" +
"// we'll store 4byte signature as int\n" +
"export const ConsoleLogs = {\n";
"\n/** Maps from a 4-byte function selector to a signature (argument types) */\n" +
"export const CONSOLE_LOG_SIGNATURES: Record<number, string[]> = {\n";

// Add the empty log() first
const sigInt = eutil.bufferToInt(
keccak256(Buffer.from("log" + "()")).slice(0, 4)
);
const sigInt = bytesToInt(keccak256(Buffer.from("log" + "()")).slice(0, 4));
logger += " " + sigInt + ": [],\n";

for (let i = 0; i < singleTypes.length; i++) {
Expand All @@ -98,7 +95,7 @@ for (let i = 0; i < singleTypes.length; i++) {
const nameSuffix =
typeAliasedInt.charAt(0).toUpperCase() + typeAliasedInt.slice(1);

const sigInt = eutil.bufferToInt(
const sigInt = bytesToInt(
keccak256(Buffer.from("log" + "(" + type + ")")).slice(0, 4)
);
logger +=
Expand All @@ -109,7 +106,7 @@ for (let i = 0; i < singleTypes.length; i++) {
type.slice(1) +
"Ty],\n";

const sigIntAliasedInt = eutil.bufferToInt(
const sigIntAliasedInt = bytesToInt(
keccak256(Buffer.from("log" + "(" + typeAliasedInt + ")")).slice(0, 4)
);
if (sigIntAliasedInt !== sigInt) {
Expand Down Expand Up @@ -137,9 +134,9 @@ for (let i = 0; i < singleTypes.length; i++) {
}

const maxNumberOfParameters = 4;
const numberOfPermutations = {};
const dividers = {};
const paramsNames = {};
const numberOfPermutations: Record<number, number> = {};
const dividers: Record<number, number> = {};
const paramsNames: Record<number, string[]> = {};

for (let i = 0; i < maxNumberOfParameters; i++) {
dividers[i] = Math.pow(maxNumberOfParameters, i);
Expand Down Expand Up @@ -188,12 +185,12 @@ for (let i = 0; i < maxNumberOfParameters; i++) {
functionSuffix;

if (sigParams.length !== 1) {
const sigInt = eutil.bufferToInt(
const sigInt = bytesToInt(
keccak256(Buffer.from("log(" + sigParams.join(",") + ")")).slice(0, 4)
);
logger += " " + sigInt + ": [" + constParams.join(", ") + "],\n";

const sigIntAliasedInt = eutil.bufferToInt(
const sigIntAliasedInt = bytesToInt(
keccak256(
Buffer.from("log(" + sigParamsAliasedInt.join(",") + ")")
).slice(0, 4)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,7 @@ export class EdrProviderWrapper
},
{
enable: loggerConfig.enabled,
decodeConsoleLogInputsCallback: (inputs: Buffer[]) => {
const consoleLogger = new ConsoleLogger();
return consoleLogger.getDecodedLogs(inputs);
},
decodeConsoleLogInputsCallback: ConsoleLogger.getDecodedLogs,
getContractAndFunctionNameCallback: (
code: Buffer,
calldata?: Buffer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,113 +42,61 @@ import {
Bytes8Ty,
Bytes9Ty,
BytesTy,
ConsoleLogs,
Int256Ty,
StringTy,
Uint256Ty,
CONSOLE_LOG_SIGNATURES,
} from "./logger";
import {
EvmMessageTrace,
isCallTrace,
isEvmStep,
isPrecompileTrace,
MessageTrace,
} from "./message-trace";

const CONSOLE_ADDRESS = "0x000000000000000000636F6e736F6c652e6c6f67"; // toHex("console.log")
const REGISTER_SIZE = 32;

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ConsoleLogArray extends Array<ConsoleLogEntry> {}

export type ConsoleLogEntry = string | ConsoleLogArray;

// eslint-disable-next-line @typescript-eslint/no-redeclare
export type ConsoleLogs = ConsoleLogEntry[];
/** The decoded string representation of the arguments supplied to console.log */
export type ConsoleLogArgs = string[];
export type ConsoleLogs = ConsoleLogArgs[];

export class ConsoleLogger {
private readonly _consoleLogs: {
[key: number]: string[];
} = {};

constructor() {
this._consoleLogs = ConsoleLogs;
}

public getLogMessages(maybeDecodedMessageTrace: MessageTrace): string[] {
return this.getExecutionLogs(maybeDecodedMessageTrace).map(
consoleLogToString
);
}

public getExecutionLogs(
maybeDecodedMessageTrace: MessageTrace
): ConsoleLogs[] {
if (isPrecompileTrace(maybeDecodedMessageTrace)) {
return [];
}

const logs: ConsoleLogs[] = [];
this._collectExecutionLogs(maybeDecodedMessageTrace, logs);
return logs;
}

private _collectExecutionLogs(trace: EvmMessageTrace, logs: ConsoleLogs) {
for (const messageTrace of trace.steps) {
if (isEvmStep(messageTrace) || isPrecompileTrace(messageTrace)) {
continue;
}

if (
isCallTrace(messageTrace) &&
bufferToHex(messageTrace.address) === CONSOLE_ADDRESS.toLowerCase()
) {
const log = this._maybeConsoleLog(Buffer.from(messageTrace.calldata));
if (log !== undefined) {
logs.push(log);
}

continue;
}

this._collectExecutionLogs(messageTrace, logs);
}
}

/**
* Temporary code to print console.sol messages that come from EDR
*/
public getDecodedLogs(messages: Buffer[]): string[] {
public static getDecodedLogs(messages: Buffer[]): string[] {
const logs: string[] = [];

for (const message of messages) {
const log = this._maybeConsoleLog(message);
const log = ConsoleLogger._maybeConsoleLog(message);
if (log !== undefined) {
logs.push(consoleLogToString(log));
logs.push(ConsoleLogger.format(log));
}
}

return logs;
}

private _maybeConsoleLog(calldata: Buffer): ConsoleLogs | undefined {
const sig = bytesToInt(calldata.slice(0, 4));
/**
* Returns a formatted string using the first argument as a `printf`-like
* format string which can contain zero or more format specifiers.
*
* If there are more arguments passed than the number of specifiers, the
* extra arguments are concatenated to the returned string, separated by spaces.
*/
public static format(args: ConsoleLogArgs = []): string {
return util.format(...args);
}

private static _maybeConsoleLog(
calldata: Buffer
): ConsoleLogArgs | undefined {
const selector = bytesToInt(calldata.slice(0, 4));
const parameters = calldata.slice(4);

const types = this._consoleLogs[sig];
if (types === undefined) {
const argTypes = CONSOLE_LOG_SIGNATURES[selector];
if (argTypes === undefined) {
return;
}

const consoleLogs = this._decode(parameters, types);
const decodedArgs = ConsoleLogger._decode(parameters, argTypes);

this._replaceNumberFormatSpecifiers(consoleLogs);

return consoleLogs;
}

private _replaceNumberFormatSpecifiers(consoleLogs: ConsoleLogs) {
/**
* The first argument is interpreted as the format string, which may need adjusting.
* Replace the occurrences of %d and %i with %s. This is necessary because if the arguments passed are numbers,
* they could be too large to be formatted as a Number or an Integer, so it is safer to use a String.
* %d and %i are replaced only if there is an odd number of % before the d or i.
Expand All @@ -160,15 +108,18 @@ export class ConsoleLogger {
* (?<!%) negative look-behind to make this work.
* The (?:) is just to avoid capturing that inner group.
*/
if (consoleLogs.length > 0 && typeof consoleLogs[0] === "string") {
consoleLogs[0] = consoleLogs[0].replace(
if (decodedArgs.length > 0) {
decodedArgs[0] = decodedArgs[0].replace(
/((?<!%)(?:%%)*)(%[di])/g,
"$1%s"
);
}

return decodedArgs;
}

private _decode(data: Buffer, types: string[]): ConsoleLogs {
/** Decodes parameters from `data` according to `types` into their string representation. */
private static _decode(data: Buffer, types: string[]): string[] {
return types.map((type, i) => {
const position: number = i * 32;
switch (types[i]) {
Expand Down Expand Up @@ -282,16 +233,3 @@ export class ConsoleLogger {
});
}
}

export function consoleLogToString(log: ConsoleLogs): string {
if (log === undefined) {
return "";
}

// special case for console.log()
if (log.length === 0) {
return "";
}

return util.format(log[0], ...log.slice(1));
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// ------------------------------------
// This code was autogenerated using
// scripts/console-library-generator.js
// scripts/console-library-generator.ts
// ------------------------------------

export const Int256Ty = "Int256";
Expand Down Expand Up @@ -42,9 +42,8 @@ export const Bytes30Ty = "Bytes30";
export const Bytes31Ty = "Bytes31";
export const Bytes32Ty = "Bytes32";

// In order to optimize map lookup
// we'll store 4byte signature as int
export const ConsoleLogs = {
/** Maps from a 4-byte function selector to a signature (argument types) */
export const CONSOLE_LOG_SIGNATURES: Record<number, string[]> = {
1368866505: [],
760966329: [Int256Ty],
1309416733: [Int256Ty],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { EdrProviderWrapper } from "../../../../src/internal/hardhat-network/pro
import { ReturnData } from "../../../../src/internal/hardhat-network/provider/return-data";
import {
ConsoleLogs,
consoleLogToString,
ConsoleLogger,
} from "../../../../src/internal/hardhat-network/stack-traces/consoleLogger";
import {
printMessageTrace,
Expand Down Expand Up @@ -94,7 +94,7 @@ interface DeploymentTransaction {
};
stackTrace?: StackFrameDescription[]; // No stack trace === the tx MUST be successful
imports?: string[]; // Imports needed for successful compilation
consoleLogs?: ConsoleLogs[];
consoleLogs?: ConsoleLogs;
gas?: number;
}

Expand All @@ -109,7 +109,7 @@ interface CallTransaction {
// The second one is with function and parms
function?: string; // Default: no data
params?: Array<string | number>; // Default: no param
consoleLogs?: ConsoleLogs[];
consoleLogs?: ConsoleLogs;
gas?: number;
}

Expand Down Expand Up @@ -454,7 +454,7 @@ function compareStackTraces(
assert.lengthOf(trace, description.length);
}

function compareConsoleLogs(logs: string[], expectedLogs?: ConsoleLogs[]) {
function compareConsoleLogs(logs: string[], expectedLogs?: ConsoleLogs) {
if (expectedLogs === undefined) {
return;
}
Expand All @@ -463,7 +463,7 @@ function compareConsoleLogs(logs: string[], expectedLogs?: ConsoleLogs[]) {

for (let i = 0; i < logs.length; i++) {
const actual = logs[i];
const expected = consoleLogToString(expectedLogs[i]);
const expected = ConsoleLogger.format(expectedLogs[i]);

assert.equal(actual, expected);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/hardhat-core/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"rootDirs": ["./test"],
"composite": true
},
"include": ["./test/**/*.ts"],
"include": ["./test/**/*.ts", "scripts"],
"exclude": [
"./test/**/hardhat.config.ts",
"./node_modules",
Expand Down

0 comments on commit aef55d2

Please sign in to comment.