Skip to content

Commit aef55d2

Browse files
authored
Merge pull request #5411 from NomicFoundation/refactor/streamline-console-logger
Streamline `ConsoleLogger` logic and state
2 parents 411fef6 + cfd0884 commit aef55d2

File tree

6 files changed

+56
-125
lines changed

6 files changed

+56
-125
lines changed

packages/hardhat-core/scripts/console-library-generator.js packages/hardhat-core/scripts/console-library-generator.ts

+14-17
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
const eutil = require("@nomicfoundation/ethereumjs-util");
2-
const fs = require("fs");
1+
import fs from "fs";
2+
import { bytesToInt } from "@nomicfoundation/ethereumjs-util";
33

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

66
const functionPrefix = " function";
77
const functionBody =
@@ -12,7 +12,7 @@ const functionSuffix = "));" + "\n }" + "\n" + "\n";
1212
let logger =
1313
"// ------------------------------------\n" +
1414
"// This code was autogenerated using\n" +
15-
"// scripts/console-library-generator.js\n" +
15+
"// scripts/console-library-generator.ts\n" +
1616
"// ------------------------------------\n\n";
1717

1818
const singleTypes = [
@@ -80,14 +80,11 @@ library console {
8080
`;
8181

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

8786
// Add the empty log() first
88-
const sigInt = eutil.bufferToInt(
89-
keccak256(Buffer.from("log" + "()")).slice(0, 4)
90-
);
87+
const sigInt = bytesToInt(keccak256(Buffer.from("log" + "()")).slice(0, 4));
9188
logger += " " + sigInt + ": [],\n";
9289

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

101-
const sigInt = eutil.bufferToInt(
98+
const sigInt = bytesToInt(
10299
keccak256(Buffer.from("log" + "(" + type + ")")).slice(0, 4)
103100
);
104101
logger +=
@@ -109,7 +106,7 @@ for (let i = 0; i < singleTypes.length; i++) {
109106
type.slice(1) +
110107
"Ty],\n";
111108

112-
const sigIntAliasedInt = eutil.bufferToInt(
109+
const sigIntAliasedInt = bytesToInt(
113110
keccak256(Buffer.from("log" + "(" + typeAliasedInt + ")")).slice(0, 4)
114111
);
115112
if (sigIntAliasedInt !== sigInt) {
@@ -137,9 +134,9 @@ for (let i = 0; i < singleTypes.length; i++) {
137134
}
138135

139136
const maxNumberOfParameters = 4;
140-
const numberOfPermutations = {};
141-
const dividers = {};
142-
const paramsNames = {};
137+
const numberOfPermutations: Record<number, number> = {};
138+
const dividers: Record<number, number> = {};
139+
const paramsNames: Record<number, string[]> = {};
143140

144141
for (let i = 0; i < maxNumberOfParameters; i++) {
145142
dividers[i] = Math.pow(maxNumberOfParameters, i);
@@ -188,12 +185,12 @@ for (let i = 0; i < maxNumberOfParameters; i++) {
188185
functionSuffix;
189186

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

196-
const sigIntAliasedInt = eutil.bufferToInt(
193+
const sigIntAliasedInt = bytesToInt(
197194
keccak256(
198195
Buffer.from("log(" + sigParamsAliasedInt.join(",") + ")")
199196
).slice(0, 4)

packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,7 @@ export class EdrProviderWrapper
281281
},
282282
{
283283
enable: loggerConfig.enabled,
284-
decodeConsoleLogInputsCallback: (inputs: Buffer[]) => {
285-
const consoleLogger = new ConsoleLogger();
286-
return consoleLogger.getDecodedLogs(inputs);
287-
},
284+
decodeConsoleLogInputsCallback: ConsoleLogger.getDecodedLogs,
288285
getContractAndFunctionNameCallback: (
289286
code: Buffer,
290287
calldata?: Buffer

packages/hardhat-core/src/internal/hardhat-network/stack-traces/consoleLogger.ts

+32-94
Original file line numberDiff line numberDiff line change
@@ -42,113 +42,61 @@ import {
4242
Bytes8Ty,
4343
Bytes9Ty,
4444
BytesTy,
45-
ConsoleLogs,
4645
Int256Ty,
4746
StringTy,
4847
Uint256Ty,
48+
CONSOLE_LOG_SIGNATURES,
4949
} from "./logger";
50-
import {
51-
EvmMessageTrace,
52-
isCallTrace,
53-
isEvmStep,
54-
isPrecompileTrace,
55-
MessageTrace,
56-
} from "./message-trace";
5750

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

61-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
62-
interface ConsoleLogArray extends Array<ConsoleLogEntry> {}
63-
64-
export type ConsoleLogEntry = string | ConsoleLogArray;
65-
66-
// eslint-disable-next-line @typescript-eslint/no-redeclare
67-
export type ConsoleLogs = ConsoleLogEntry[];
53+
/** The decoded string representation of the arguments supplied to console.log */
54+
export type ConsoleLogArgs = string[];
55+
export type ConsoleLogs = ConsoleLogArgs[];
6856

6957
export class ConsoleLogger {
70-
private readonly _consoleLogs: {
71-
[key: number]: string[];
72-
} = {};
73-
74-
constructor() {
75-
this._consoleLogs = ConsoleLogs;
76-
}
77-
78-
public getLogMessages(maybeDecodedMessageTrace: MessageTrace): string[] {
79-
return this.getExecutionLogs(maybeDecodedMessageTrace).map(
80-
consoleLogToString
81-
);
82-
}
83-
84-
public getExecutionLogs(
85-
maybeDecodedMessageTrace: MessageTrace
86-
): ConsoleLogs[] {
87-
if (isPrecompileTrace(maybeDecodedMessageTrace)) {
88-
return [];
89-
}
90-
91-
const logs: ConsoleLogs[] = [];
92-
this._collectExecutionLogs(maybeDecodedMessageTrace, logs);
93-
return logs;
94-
}
95-
96-
private _collectExecutionLogs(trace: EvmMessageTrace, logs: ConsoleLogs) {
97-
for (const messageTrace of trace.steps) {
98-
if (isEvmStep(messageTrace) || isPrecompileTrace(messageTrace)) {
99-
continue;
100-
}
101-
102-
if (
103-
isCallTrace(messageTrace) &&
104-
bufferToHex(messageTrace.address) === CONSOLE_ADDRESS.toLowerCase()
105-
) {
106-
const log = this._maybeConsoleLog(Buffer.from(messageTrace.calldata));
107-
if (log !== undefined) {
108-
logs.push(log);
109-
}
110-
111-
continue;
112-
}
113-
114-
this._collectExecutionLogs(messageTrace, logs);
115-
}
116-
}
117-
11858
/**
11959
* Temporary code to print console.sol messages that come from EDR
12060
*/
121-
public getDecodedLogs(messages: Buffer[]): string[] {
61+
public static getDecodedLogs(messages: Buffer[]): string[] {
12262
const logs: string[] = [];
12363

12464
for (const message of messages) {
125-
const log = this._maybeConsoleLog(message);
65+
const log = ConsoleLogger._maybeConsoleLog(message);
12666
if (log !== undefined) {
127-
logs.push(consoleLogToString(log));
67+
logs.push(ConsoleLogger.format(log));
12868
}
12969
}
13070

13171
return logs;
13272
}
13373

134-
private _maybeConsoleLog(calldata: Buffer): ConsoleLogs | undefined {
135-
const sig = bytesToInt(calldata.slice(0, 4));
74+
/**
75+
* Returns a formatted string using the first argument as a `printf`-like
76+
* format string which can contain zero or more format specifiers.
77+
*
78+
* If there are more arguments passed than the number of specifiers, the
79+
* extra arguments are concatenated to the returned string, separated by spaces.
80+
*/
81+
public static format(args: ConsoleLogArgs = []): string {
82+
return util.format(...args);
83+
}
84+
85+
private static _maybeConsoleLog(
86+
calldata: Buffer
87+
): ConsoleLogArgs | undefined {
88+
const selector = bytesToInt(calldata.slice(0, 4));
13689
const parameters = calldata.slice(4);
13790

138-
const types = this._consoleLogs[sig];
139-
if (types === undefined) {
91+
const argTypes = CONSOLE_LOG_SIGNATURES[selector];
92+
if (argTypes === undefined) {
14093
return;
14194
}
14295

143-
const consoleLogs = this._decode(parameters, types);
96+
const decodedArgs = ConsoleLogger._decode(parameters, argTypes);
14497

145-
this._replaceNumberFormatSpecifiers(consoleLogs);
146-
147-
return consoleLogs;
148-
}
149-
150-
private _replaceNumberFormatSpecifiers(consoleLogs: ConsoleLogs) {
15198
/**
99+
* The first argument is interpreted as the format string, which may need adjusting.
152100
* Replace the occurrences of %d and %i with %s. This is necessary because if the arguments passed are numbers,
153101
* they could be too large to be formatted as a Number or an Integer, so it is safer to use a String.
154102
* %d and %i are replaced only if there is an odd number of % before the d or i.
@@ -160,15 +108,18 @@ export class ConsoleLogger {
160108
* (?<!%) negative look-behind to make this work.
161109
* The (?:) is just to avoid capturing that inner group.
162110
*/
163-
if (consoleLogs.length > 0 && typeof consoleLogs[0] === "string") {
164-
consoleLogs[0] = consoleLogs[0].replace(
111+
if (decodedArgs.length > 0) {
112+
decodedArgs[0] = decodedArgs[0].replace(
165113
/((?<!%)(?:%%)*)(%[di])/g,
166114
"$1%s"
167115
);
168116
}
117+
118+
return decodedArgs;
169119
}
170120

171-
private _decode(data: Buffer, types: string[]): ConsoleLogs {
121+
/** Decodes parameters from `data` according to `types` into their string representation. */
122+
private static _decode(data: Buffer, types: string[]): string[] {
172123
return types.map((type, i) => {
173124
const position: number = i * 32;
174125
switch (types[i]) {
@@ -282,16 +233,3 @@ export class ConsoleLogger {
282233
});
283234
}
284235
}
285-
286-
export function consoleLogToString(log: ConsoleLogs): string {
287-
if (log === undefined) {
288-
return "";
289-
}
290-
291-
// special case for console.log()
292-
if (log.length === 0) {
293-
return "";
294-
}
295-
296-
return util.format(log[0], ...log.slice(1));
297-
}

packages/hardhat-core/src/internal/hardhat-network/stack-traces/logger.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// ------------------------------------
22
// This code was autogenerated using
3-
// scripts/console-library-generator.js
3+
// scripts/console-library-generator.ts
44
// ------------------------------------
55

66
export const Int256Ty = "Int256";
@@ -42,9 +42,8 @@ export const Bytes30Ty = "Bytes30";
4242
export const Bytes31Ty = "Bytes31";
4343
export const Bytes32Ty = "Bytes32";
4444

45-
// In order to optimize map lookup
46-
// we'll store 4byte signature as int
47-
export const ConsoleLogs = {
45+
/** Maps from a 4-byte function selector to a signature (argument types) */
46+
export const CONSOLE_LOG_SIGNATURES: Record<number, string[]> = {
4847
1368866505: [],
4948
760966329: [Int256Ty],
5049
1309416733: [Int256Ty],

packages/hardhat-core/test/internal/hardhat-network/stack-traces/test.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { EdrProviderWrapper } from "../../../../src/internal/hardhat-network/pro
99
import { ReturnData } from "../../../../src/internal/hardhat-network/provider/return-data";
1010
import {
1111
ConsoleLogs,
12-
consoleLogToString,
12+
ConsoleLogger,
1313
} from "../../../../src/internal/hardhat-network/stack-traces/consoleLogger";
1414
import {
1515
printMessageTrace,
@@ -94,7 +94,7 @@ interface DeploymentTransaction {
9494
};
9595
stackTrace?: StackFrameDescription[]; // No stack trace === the tx MUST be successful
9696
imports?: string[]; // Imports needed for successful compilation
97-
consoleLogs?: ConsoleLogs[];
97+
consoleLogs?: ConsoleLogs;
9898
gas?: number;
9999
}
100100

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

@@ -454,7 +454,7 @@ function compareStackTraces(
454454
assert.lengthOf(trace, description.length);
455455
}
456456

457-
function compareConsoleLogs(logs: string[], expectedLogs?: ConsoleLogs[]) {
457+
function compareConsoleLogs(logs: string[], expectedLogs?: ConsoleLogs) {
458458
if (expectedLogs === undefined) {
459459
return;
460460
}
@@ -463,7 +463,7 @@ function compareConsoleLogs(logs: string[], expectedLogs?: ConsoleLogs[]) {
463463

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

468468
assert.equal(actual, expected);
469469
}

packages/hardhat-core/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"rootDirs": ["./test"],
66
"composite": true
77
},
8-
"include": ["./test/**/*.ts"],
8+
"include": ["./test/**/*.ts", "scripts"],
99
"exclude": [
1010
"./test/**/hardhat.config.ts",
1111
"./node_modules",

0 commit comments

Comments
 (0)