|
1 |
| -import fs from "fs"; |
2 |
| -import { bytesToInt } from "@nomicfoundation/ethereumjs-util"; |
| 1 | +import fs from "node:fs"; |
3 | 2 |
|
4 | 3 | import { keccak256 } from "../src/internal/util/keccak";
|
5 | 4 |
|
6 |
| -const functionPrefix = " function"; |
7 |
| -const functionBody = |
8 |
| - ") internal pure {" + |
9 |
| - '\n _sendLogPayload(abi.encodeWithSignature("log('; |
10 |
| -const functionSuffix = "));" + "\n }" + "\n" + "\n"; |
11 |
| - |
12 |
| -let logger = |
13 |
| - "// ------------------------------------\n" + |
14 |
| - "// This code was autogenerated using\n" + |
15 |
| - "// scripts/console-library-generator.ts\n" + |
16 |
| - "// ------------------------------------\n\n"; |
17 |
| - |
18 |
| -const singleTypes = [ |
19 |
| - "int256", |
20 |
| - "uint256", |
21 |
| - "string memory", |
22 |
| - "bool", |
23 |
| - "address", |
24 |
| - "bytes memory", |
25 |
| -]; |
26 |
| -for (let i = 0; i < singleTypes.length; i++) { |
27 |
| - const singleType = singleTypes[i].replace(" memory", ""); |
28 |
| - const type = singleType.charAt(0).toUpperCase() + singleType.slice(1); |
29 |
| - logger += "export const " + type + 'Ty = "' + type + '";\n'; |
| 5 | +function capitalize(s: string): string { |
| 6 | + return s.length === 0 ? "" : s.charAt(0).toUpperCase() + s.slice(1); |
30 | 7 | }
|
31 | 8 |
|
32 |
| -const offset = singleTypes.length - 1; |
33 |
| -for (let i = 1; i <= 32; i++) { |
34 |
| - singleTypes[offset + i] = "bytes" + i.toString(); |
35 |
| - logger += |
36 |
| - "export const Bytes" + i.toString() + 'Ty = "Bytes' + i.toString() + '";\n'; |
| 9 | +/** |
| 10 | + * Generates all permutations of the given length and number of different |
| 11 | + * elements as an iterator of 0-based indices. |
| 12 | + */ |
| 13 | +function* genPermutations(elemCount: number, len: number) { |
| 14 | + // We can think of a permutation as a number of base `elemCount`, i.e. |
| 15 | + // each 'digit' is a number between 0 and `elemCount - 1`. |
| 16 | + // Then, to generate all permutations, we simply need to linearly iterate |
| 17 | + // from 0 to max number of permutations (elemCount ** len) and convert |
| 18 | + // each number to a list of digits as per the base `elemCount`, see above. |
| 19 | + const numberOfPermutations = elemCount ** len; |
| 20 | + const dividers = Array(elemCount) |
| 21 | + .fill(0) |
| 22 | + .map((_, i) => elemCount ** i); |
| 23 | + |
| 24 | + for (let number = 0; number < numberOfPermutations; number++) { |
| 25 | + const params = Array(len) |
| 26 | + .fill(0) |
| 27 | + .map((_, i) => Math.floor(number / dividers[i]) % elemCount); |
| 28 | + // Reverse, so that we keep the natural big-endian ordering, i.e. |
| 29 | + // [0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], ... |
| 30 | + params.reverse(); |
| 31 | + |
| 32 | + yield params; |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +type TypeName = { type: string; modifier?: "memory" }; |
| 37 | +type FnParam = TypeName & { name: string }; |
| 38 | + |
| 39 | +/** Computes the function selector for the given function with simple arguments. */ |
| 40 | +function selector({ name = "", params = [] as TypeName[] }) { |
| 41 | + const sigParams = params.map((p) => p.type).join(","); |
| 42 | + return keccak256(Buffer.from(`${name}(${sigParams})`)).slice(0, 4); |
| 43 | +} |
| 44 | + |
| 45 | +function toHex(value: Uint8Array) { |
| 46 | + return "0x" + Buffer.from(value).toString("hex"); |
37 | 47 | }
|
38 | 48 |
|
39 |
| -const types = ["uint256", "string memory", "bool", "address"]; |
| 49 | +/** The types for which we generate `logUint`, `logString`, etc. */ |
| 50 | +const SINGLE_TYPES = [ |
| 51 | + { type: "int256" }, |
| 52 | + { type: "uint256" }, |
| 53 | + { type: "string", modifier: "memory" }, |
| 54 | + { type: "bool" }, |
| 55 | + { type: "address" }, |
| 56 | + { type: "bytes", modifier: "memory" }, |
| 57 | + ...Array.from({ length: 32 }, (_, i) => ({ type: `bytes${i + 1}` })), |
| 58 | +] as const; |
| 59 | + |
| 60 | +/** The types for which we generate a `log` function with all possible |
| 61 | + combinations of up to 4 arguments. */ |
| 62 | +const TYPES = [ |
| 63 | + { type: "uint256" }, |
| 64 | + { type: "string", modifier: "memory" }, |
| 65 | + { type: "bool" }, |
| 66 | + { type: "address" }, |
| 67 | +] as const; |
| 68 | + |
| 69 | +/** A list of `console.log*` functions that we want to generate. */ |
| 70 | +const CONSOLE_LOG_FUNCTIONS = |
| 71 | + // Basic `log()` function |
| 72 | + [{ name: "log", params: [] as FnParam[] }] |
| 73 | + // Generate single parameter functions that are type-suffixed for |
| 74 | + // backwards-compatibility, e.g. logInt, logUint, logString, etc. |
| 75 | + .concat( |
| 76 | + SINGLE_TYPES.map((single) => { |
| 77 | + const param = { ...single, name: "p0" }; |
| 78 | + const nameSuffix = capitalize(param.type.replace("int256", "int")); |
| 79 | + |
| 80 | + return { |
| 81 | + name: `log${nameSuffix}`, |
| 82 | + params: [param], |
| 83 | + }; |
| 84 | + }) |
| 85 | + ) |
| 86 | + // Also generate the function definitions for `log` for permutations of |
| 87 | + // up to 4 parameters using the `types` (uint256, string, bool, address). |
| 88 | + .concat( |
| 89 | + [...Array(4)].flatMap((_, paramCount) => { |
| 90 | + return Array.from( |
| 91 | + genPermutations(TYPES.length, paramCount + 1), |
| 92 | + (permutation) => ({ |
| 93 | + name: "log", |
| 94 | + params: permutation.map((typeIndex, i) => ({ |
| 95 | + ...TYPES[typeIndex], |
| 96 | + name: `p${i}`, |
| 97 | + })), |
| 98 | + }) |
| 99 | + ); |
| 100 | + }) |
| 101 | + ); |
| 102 | + |
| 103 | +/** Maps from a 4-byte function selector to a signature (argument types) */ |
| 104 | +const CONSOLE_LOG_SIGNATURES: Map<string, string[]> = |
| 105 | + CONSOLE_LOG_FUNCTIONS.reduce((acc, { params }) => { |
| 106 | + // We always use `log` for the selector, even if it's logUint, for example. |
| 107 | + const signature = toHex(selector({ name: "log", params })); |
| 108 | + const types = params.map((p) => p.type); |
| 109 | + acc.set(signature, types); |
| 110 | + |
| 111 | + // For backwards compatibility, we additionally support the (invalid) |
| 112 | + // selectors that contain the `int`/`uint` aliases in the selector calculation. |
| 113 | + if (params.some((p) => ["uint256", "int256"].includes(p.type))) { |
| 114 | + const aliased = params.map((p) => ({ |
| 115 | + ...p, |
| 116 | + type: p.type.replace("int256", "int"), |
| 117 | + })); |
| 118 | + |
| 119 | + const signature = toHex(selector({ name: "log", params: aliased })); |
| 120 | + acc.set(signature, types); |
| 121 | + } |
| 122 | + |
| 123 | + return acc; |
| 124 | + }, new Map()); |
40 | 125 |
|
41 |
| -let consoleSolFile = `// SPDX-License-Identifier: MIT |
| 126 | +// Finally, render and save the console.sol and logger.ts files |
| 127 | +const consoleSolFile = `\ |
| 128 | +// SPDX-License-Identifier: MIT |
42 | 129 | pragma solidity >=0.4.22 <0.9.0;
|
43 | 130 |
|
44 | 131 | library console {
|
@@ -74,140 +161,46 @@ library console {
|
74 | 161 | _castToPure(_sendLogPayloadImplementation)(payload);
|
75 | 162 | }
|
76 | 163 |
|
77 |
| - function log() internal pure { |
78 |
| - _sendLogPayload(abi.encodeWithSignature("log()")); |
| 164 | +${CONSOLE_LOG_FUNCTIONS.map(({ name, params }) => { |
| 165 | + let fnParams = params |
| 166 | + .map((p) => `${p.type}${p.modifier ? ` ${p.modifier}` : ""} ${p.name}`) |
| 167 | + .join(", "); |
| 168 | + let sig = params.map((p) => p.type).join(","); |
| 169 | + let passed = params.map((p) => p.name).join(", "); |
| 170 | + let passedArgs = passed.length > 0 ? `, ${passed}` : ""; |
| 171 | +
|
| 172 | + return `\ |
| 173 | + function ${name}(${fnParams}) internal pure { |
| 174 | + _sendLogPayload(abi.encodeWithSignature("log(${sig})"${passedArgs})); |
79 | 175 | }
|
80 | 176 | `;
|
81 |
| - |
82 |
| -logger += |
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"; |
85 |
| - |
86 |
| -// Add the empty log() first |
87 |
| -const sigInt = bytesToInt(keccak256(Buffer.from("log" + "()")).slice(0, 4)); |
88 |
| -logger += " " + sigInt + ": [],\n"; |
89 |
| - |
90 |
| -for (let i = 0; i < singleTypes.length; i++) { |
91 |
| - const type = singleTypes[i].replace(" memory", ""); |
92 |
| - |
93 |
| - // use logInt and logUint as function names for backwards-compatibility |
94 |
| - const typeAliasedInt = type.replace("int256", "int"); |
95 |
| - const nameSuffix = |
96 |
| - typeAliasedInt.charAt(0).toUpperCase() + typeAliasedInt.slice(1); |
97 |
| - |
98 |
| - const sigInt = bytesToInt( |
99 |
| - keccak256(Buffer.from("log" + "(" + type + ")")).slice(0, 4) |
100 |
| - ); |
101 |
| - logger += |
102 |
| - " " + |
103 |
| - sigInt + |
104 |
| - ": [" + |
105 |
| - type.charAt(0).toUpperCase() + |
106 |
| - type.slice(1) + |
107 |
| - "Ty],\n"; |
108 |
| - |
109 |
| - const sigIntAliasedInt = bytesToInt( |
110 |
| - keccak256(Buffer.from("log" + "(" + typeAliasedInt + ")")).slice(0, 4) |
111 |
| - ); |
112 |
| - if (sigIntAliasedInt !== sigInt) { |
113 |
| - logger += |
114 |
| - " " + |
115 |
| - sigIntAliasedInt + |
116 |
| - ": [" + |
117 |
| - type.charAt(0).toUpperCase() + |
118 |
| - type.slice(1) + |
119 |
| - "Ty],\n"; |
120 |
| - } |
121 |
| - |
122 |
| - consoleSolFile += |
123 |
| - functionPrefix + |
124 |
| - " log" + |
125 |
| - nameSuffix + |
126 |
| - "(" + |
127 |
| - singleTypes[i] + |
128 |
| - " p0" + |
129 |
| - functionBody + |
130 |
| - type + |
131 |
| - ')", ' + |
132 |
| - "p0" + |
133 |
| - functionSuffix; |
134 |
| -} |
135 |
| - |
136 |
| -const maxNumberOfParameters = 4; |
137 |
| -const numberOfPermutations: Record<number, number> = {}; |
138 |
| -const dividers: Record<number, number> = {}; |
139 |
| -const paramsNames: Record<number, string[]> = {}; |
140 |
| - |
141 |
| -for (let i = 0; i < maxNumberOfParameters; i++) { |
142 |
| - dividers[i] = Math.pow(maxNumberOfParameters, i); |
143 |
| - numberOfPermutations[i] = Math.pow(maxNumberOfParameters, i + 1); |
144 |
| - |
145 |
| - paramsNames[i] = []; |
146 |
| - for (let j = 0; j <= i; j++) { |
147 |
| - paramsNames[i][j] = "p" + j.toString(); |
148 |
| - } |
149 |
| -} |
150 |
| - |
151 |
| -for (let i = 0; i < maxNumberOfParameters; i++) { |
152 |
| - for (let j = 0; j < numberOfPermutations[i]; j++) { |
153 |
| - const params = []; |
154 |
| - |
155 |
| - for (let k = 0; k <= i; k++) { |
156 |
| - params.push(types[Math.floor(j / dividers[k]) % types.length]); |
157 |
| - } |
158 |
| - params.reverse(); |
159 |
| - |
160 |
| - let sigParams = []; |
161 |
| - let sigParamsAliasedInt = []; |
162 |
| - let constParams = []; |
163 |
| - |
164 |
| - let input = ""; |
165 |
| - let internalParamsNames = []; |
166 |
| - for (let k = 0; k <= i; k++) { |
167 |
| - input += params[k] + " " + paramsNames[i][k] + ", "; |
168 |
| - internalParamsNames.push(paramsNames[i][k]); |
169 |
| - |
170 |
| - let param = params[k].replace(" memory", ""); |
171 |
| - let paramAliasedInt = param.replace("int256", "int"); |
172 |
| - sigParams.push(param); |
173 |
| - sigParamsAliasedInt.push(paramAliasedInt); |
174 |
| - constParams.push(param.charAt(0).toUpperCase() + param.slice(1) + "Ty"); |
175 |
| - } |
176 |
| - |
177 |
| - consoleSolFile += |
178 |
| - functionPrefix + |
179 |
| - " log(" + |
180 |
| - input.substr(0, input.length - 2) + |
181 |
| - functionBody + |
182 |
| - sigParams.join(",") + |
183 |
| - ')", ' + |
184 |
| - internalParamsNames.join(", ") + |
185 |
| - functionSuffix; |
186 |
| - |
187 |
| - if (sigParams.length !== 1) { |
188 |
| - const sigInt = bytesToInt( |
189 |
| - keccak256(Buffer.from("log(" + sigParams.join(",") + ")")).slice(0, 4) |
190 |
| - ); |
191 |
| - logger += " " + sigInt + ": [" + constParams.join(", ") + "],\n"; |
192 |
| - |
193 |
| - const sigIntAliasedInt = bytesToInt( |
194 |
| - keccak256( |
195 |
| - Buffer.from("log(" + sigParamsAliasedInt.join(",") + ")") |
196 |
| - ).slice(0, 4) |
197 |
| - ); |
198 |
| - if (sigIntAliasedInt !== sigInt) { |
199 |
| - logger += |
200 |
| - " " + sigIntAliasedInt + ": [" + constParams.join(", ") + "],\n"; |
201 |
| - } |
202 |
| - } |
203 |
| - } |
| 177 | +}).join("\n")}\ |
204 | 178 | }
|
| 179 | +`; |
205 | 180 |
|
206 |
| -consoleSolFile += "}\n"; |
207 |
| -logger = logger + "};\n"; |
| 181 | +const loggerFile = `\ |
| 182 | +// ------------------------------------ |
| 183 | +// This code was autogenerated using |
| 184 | +// scripts/console-library-generator.ts |
| 185 | +// ------------------------------------ |
| 186 | +
|
| 187 | +${Array.from(SINGLE_TYPES.map((param) => capitalize(param.type))) |
| 188 | + .map((type) => `export const ${type}Ty = "${type}";`) |
| 189 | + .join("\n")} |
| 190 | +
|
| 191 | +/** Maps from a 4-byte function selector to a signature (argument types) */ |
| 192 | +export const CONSOLE_LOG_SIGNATURES: Record<number, string[]> = { |
| 193 | +${Array.from(CONSOLE_LOG_SIGNATURES) |
| 194 | + .map(([sig, types]) => { |
| 195 | + const typeNames = types.map((type) => `${capitalize(type)}Ty`).join(", "); |
| 196 | + return ` ${sig}: [${typeNames}],`; |
| 197 | + }) |
| 198 | + .join("\n")} |
| 199 | +}; |
| 200 | +`; |
208 | 201 |
|
| 202 | +fs.writeFileSync(__dirname + "/../console.sol", consoleSolFile); |
209 | 203 | fs.writeFileSync(
|
210 | 204 | __dirname + "/../src/internal/hardhat-network/stack-traces/logger.ts",
|
211 |
| - logger |
| 205 | + loggerFile |
212 | 206 | );
|
213 |
| -fs.writeFileSync(__dirname + "/../console.sol", consoleSolFile); |
0 commit comments