Skip to content

Commit

Permalink
refactor: Simplify the console-library-generator.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
Xanewok committed Sep 18, 2024
1 parent 74b1bfe commit 42a09d9
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 139 deletions.
276 changes: 138 additions & 138 deletions packages/hardhat-core/scripts/console-library-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,66 @@ import { bytesToInt } from "@nomicfoundation/ethereumjs-util";

import { keccak256 } from "../src/internal/util/keccak";

const functionPrefix = " function";
const functionBody =
") internal pure {" +
'\n _sendLogPayload(abi.encodeWithSignature("log(';
const functionSuffix = "));" + "\n }" + "\n" + "\n";
function capitalize(s: string): string {
return s.length === 0 ? "" : s.charAt(0).toUpperCase() + s.slice(1);
}

let logger =
"// ------------------------------------\n" +
"// This code was autogenerated using\n" +
"// scripts/console-library-generator.ts\n" +
"// ------------------------------------\n\n";
/**
* Generates all permutations of the given length and number of different
* elements as an iterator of 0-based indices.
*/
function* genPermutations(elemCount: number, len: number) {
// We can think of a permutation as a number of base `elemCount`, i.e.
// each 'digit' is a number between 0 and `elemCount - 1`.
// Then, to generate all permutations, we simply need to linearly iterate
// from 0 to max number of permutations (elemCount ** len) and convert
// each number to a list of digits as per the base `elemCount`, see above.
const numberOfPermutations = elemCount ** len;
// Pre-compute the base `elemCount` dividers ([1, elemCount, elemCount ** 2, ...])
const dividers = Array(elemCount)
.fill(0)
.map((_, i) => elemCount ** i);

for (let number = 0; number < numberOfPermutations; number++) {
const params = Array(len)
.fill(0)
.map((_, i) => Math.floor(number / dividers[i]) % elemCount);
// Reverse, so that we keep the natural big-endian ordering, i.e.
// [0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], ...
params.reverse();

const singleTypes = [
"int256",
"uint256",
"string memory",
"bool",
"address",
"bytes memory",
];
for (let i = 0; i < singleTypes.length; i++) {
const singleType = singleTypes[i].replace(" memory", "");
const type = singleType.charAt(0).toUpperCase() + singleType.slice(1);
logger += "export const " + type + 'Ty = "' + type + '";\n';
yield params;
}
}

const offset = singleTypes.length - 1;
for (let i = 1; i <= 32; i++) {
singleTypes[offset + i] = "bytes" + i.toString();
logger +=
"export const Bytes" + i.toString() + 'Ty = "Bytes' + i.toString() + '";\n';
type TypeName = { type: string; modifier?: "memory" };
type FnParam = TypeName & { name: string };

/** Computes the function selector for the given function with simple arguments. */
function selector({ name = "log", params = [] as TypeName[] }) {
const sig = params.map((p) => p.type).join(",");
return keccak256(Buffer.from(`${name}(${sig})`)).slice(0, 4);
}

const types = ["uint256", "string memory", "bool", "address"];
/** The types for which we generate `logUint`, `logString`, etc. */
const SINGLE_TYPES = [
{ type: "int256" },
{ type: "uint256" },
{ type: "string", modifier: "memory" },
{ type: "bool" },
{ type: "address" },
{ type: "bytes", modifier: "memory" },
...Array.from({ length: 32 }, (_, i) => ({ type: `bytes${i + 1}` })),
] as const;

/** The types for which we generate a `log` function with all possible
combinations of up to 4 arguments. */
const TYPES = [
{ type: "uint256" },
{ type: "string", modifier: "memory" },
{ type: "bool" },
{ type: "address" },
] as const;

let consoleSolFile = `// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
Expand Down Expand Up @@ -79,132 +105,106 @@ library console {
}
`;

logger +=
"\n/** Maps from a 4-byte function selector to a signature (argument types) */\n" +
"export const CONSOLE_LOG_SIGNATURES: Record<number, string[]> = {\n";
function renderLogFunction({ name = "log", params = [] as FnParam[] }): string {
let fnParams = params
.map((p) => `${p.type}${p.modifier ? ` ${p.modifier}` : ""} ${p.name}`)
.join(", ");
let sig = params.map((p) => p.type).join(",");
let passed = params.map((p) => p.name).join(", ");

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

for (let i = 0; i < singleTypes.length; i++) {
const type = singleTypes[i].replace(" memory", "");

// use logInt and logUint as function names for backwards-compatibility
const typeAliasedInt = type.replace("int256", "int");
const nameSuffix =
typeAliasedInt.charAt(0).toUpperCase() + typeAliasedInt.slice(1);

const sigInt = bytesToInt(
keccak256(Buffer.from("log" + "(" + type + ")")).slice(0, 4)
);
logger +=
" " +
sigInt +
": [" +
type.charAt(0).toUpperCase() +
type.slice(1) +
"Ty],\n";

const sigIntAliasedInt = bytesToInt(
keccak256(Buffer.from("log" + "(" + typeAliasedInt + ")")).slice(0, 4)
);
if (sigIntAliasedInt !== sigInt) {
logger +=
" " +
sigIntAliasedInt +
": [" +
type.charAt(0).toUpperCase() +
type.slice(1) +
"Ty],\n";
}
return ` function ${name}(${fnParams}) internal pure {
_sendLogPayload(abi.encodeWithSignature("log(${sig})", ${passed}));
}
consoleSolFile +=
functionPrefix +
" log" +
nameSuffix +
"(" +
singleTypes[i] +
" p0" +
functionBody +
type +
')", ' +
"p0" +
functionSuffix;
`;
}

const maxNumberOfParameters = 4;
const numberOfPermutations: Record<number, number> = {};
const dividers: Record<number, number> = {};
const paramsNames: Record<number, string[]> = {};
let logger =
"// ------------------------------------\n" +
"// This code was autogenerated using\n" +
"// scripts/console-library-generator.ts\n" +
"// ------------------------------------\n\n";

for (let i = 0; i < maxNumberOfParameters; i++) {
dividers[i] = Math.pow(maxNumberOfParameters, i);
numberOfPermutations[i] = Math.pow(maxNumberOfParameters, i + 1);
for (let i = 0; i < SINGLE_TYPES.length; i++) {
const type = capitalize(SINGLE_TYPES[i].type);

paramsNames[i] = [];
for (let j = 0; j <= i; j++) {
paramsNames[i][j] = "p" + j.toString();
}
logger += `export const ${type}Ty = "${type}";\n`;
}

for (let i = 0; i < maxNumberOfParameters; i++) {
for (let j = 0; j < numberOfPermutations[i]; j++) {
const params = [];
logger +=
"\n/** Maps from a 4-byte function selector to a signature (argument types) */\n" +
"export const CONSOLE_LOG_SIGNATURES: Record<number, string[]> = {\n";

for (let k = 0; k <= i; k++) {
params.push(types[Math.floor(j / dividers[k]) % types.length]);
}
params.reverse();
/** Maps from a 4-byte function selector to a signature (argument types) */
const CONSOLE_LOG_SIGNATURES: Map<number, string[]> = new Map();

let sigParams = [];
let sigParamsAliasedInt = [];
let constParams = [];

let input = "";
let internalParamsNames = [];
for (let k = 0; k <= i; k++) {
input += params[k] + " " + paramsNames[i][k] + ", ";
internalParamsNames.push(paramsNames[i][k]);

let param = params[k].replace(" memory", "");
let paramAliasedInt = param.replace("int256", "int");
sigParams.push(param);
sigParamsAliasedInt.push(paramAliasedInt);
constParams.push(param.charAt(0).toUpperCase() + param.slice(1) + "Ty");
}
// Add the empty log() first
const sigInt = bytesToInt(selector({ name: "log", params: [] }));
CONSOLE_LOG_SIGNATURES.set(sigInt, []);

// Generate single parameter functions that are type-suffixed for
// backwards-compatibility, e.g. logInt, logUint, logString, etc.
for (let i = 0; i < SINGLE_TYPES.length; i++) {
const param = { ...SINGLE_TYPES[i], name: "p0" } as const;

const typeAliased = param.type.replace("int256", "int");
const nameSuffix = capitalize(typeAliased);

const signature = bytesToInt(selector({ name: "log", params: [param] }));
CONSOLE_LOG_SIGNATURES.set(signature, [param.type]);

// For full backwards compatibility, also support the (invalid) selectors of
// `log(int)` and `log(uint)`. The selector encoding specifies that one has to
// use the canonical type name but it seems that we supported it in the past.
if (["uint256", "int256"].includes(param.type)) {
const signature = bytesToInt(
selector({ name: "log", params: [{ type: typeAliased }] })
);
CONSOLE_LOG_SIGNATURES.set(signature, [param.type]);
}

consoleSolFile +=
functionPrefix +
" log(" +
input.substr(0, input.length - 2) +
functionBody +
sigParams.join(",") +
')", ' +
internalParamsNames.join(", ") +
functionSuffix;

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

const sigIntAliasedInt = bytesToInt(
keccak256(
Buffer.from("log(" + sigParamsAliasedInt.join(",") + ")")
).slice(0, 4)
);
if (sigIntAliasedInt !== sigInt) {
logger +=
" " + sigIntAliasedInt + ": [" + constParams.join(", ") + "],\n";
}
consoleSolFile += renderLogFunction({
name: `log${nameSuffix}`,
params: [param],
});
}

// Now generate the function definitions for `log` for permutations of
// up to 4 parameters using the `types` (uint256, string, bool, address).
const MAX_NUMBER_OF_PARAMETERS = 4;
for (let paramCount = 0; paramCount < MAX_NUMBER_OF_PARAMETERS; paramCount++) {
for (const permutation of genPermutations(TYPES.length, paramCount + 1)) {
const params = permutation.map(
(typeIndex, i) => ({ ...TYPES[typeIndex], name: `p${i}` } as const)
);

consoleSolFile += renderLogFunction({ params });

const types = params.map((p) => p.type);
const signature = bytesToInt(selector({ name: "log", params }));
CONSOLE_LOG_SIGNATURES.set(signature, types);

// Again, for full backwards compatibility, also support the (invalid)
// selectors that contain the `int`/`uint` aliases in the selector calculation.
if (params.some((p) => ["uint256", "int256"].includes(p.type))) {
const aliased = params.map((p) => ({
...p,
type: p.type.replace("int256", "int"),
}));

const signature = bytesToInt(selector({ name: "log", params: aliased }));
CONSOLE_LOG_SIGNATURES.set(signature, types);
}
}
}

for (const [sig, types] of CONSOLE_LOG_SIGNATURES) {
const typeNames = types.map((t) => `${capitalize(t)}Ty`).join(", ");
logger += ` ${sig}: [${typeNames}],\n`;
}

consoleSolFile += "}\n";
logger = logger + "};\n";
logger += "};\n";

fs.writeFileSync(
__dirname + "/../src/internal/hardhat-network/stack-traces/logger.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export class ConsoleLogger {
return util.format(...args);
}

/** Decodes a calldata buffer into string arguments for a console log. */
private static _maybeConsoleLog(
calldata: Buffer
): ConsoleLogArgs | undefined {
Expand Down Expand Up @@ -118,7 +119,7 @@ export class ConsoleLogger {
return decodedArgs;
}

/** Decodes parameters from `data` according to `types` into their string representation. */
/** Decodes calldata 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;
Expand Down

0 comments on commit 42a09d9

Please sign in to comment.