Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Streamline ConsoleLogger logic and state #5411

Merged
merged 4 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -289,10 +289,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 @@ -95,7 +95,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 @@ -110,7 +110,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 @@ -455,7 +455,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 @@ -464,7 +464,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
Loading