UNPKG

hardhat

Version:

Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.

251 lines (228 loc) 8.4 kB
import util from "node:util"; import { bytesToHexString } from "@nomicfoundation/hardhat-utils/bytes"; import { bytesToBigInt, bytesToNumber, } from "@nomicfoundation/hardhat-utils/number"; import { AddressTy, BoolTy, Bytes10Ty, Bytes11Ty, Bytes12Ty, Bytes13Ty, Bytes14Ty, Bytes15Ty, Bytes16Ty, Bytes17Ty, Bytes18Ty, Bytes19Ty, Bytes1Ty, Bytes20Ty, Bytes21Ty, Bytes22Ty, Bytes23Ty, Bytes24Ty, Bytes25Ty, Bytes26Ty, Bytes27Ty, Bytes28Ty, Bytes29Ty, Bytes2Ty, Bytes30Ty, Bytes31Ty, Bytes32Ty, Bytes3Ty, Bytes4Ty, Bytes5Ty, Bytes6Ty, Bytes7Ty, Bytes8Ty, Bytes9Ty, BytesTy, Int256Ty, StringTy, Uint256Ty, CONSOLE_LOG_SIGNATURES, } from "./console-log-signatures.js"; /** * Interprets a `Uint8Array` as a signed integer and returns a `BigInt`. Assumes 256-bit numbers. * @param {Uint8Array} num Signed integer value * @returns {bigint} */ const fromSigned = (num: Uint8Array): bigint => { return BigInt.asIntN(256, bytesToBigInt(num)); }; const REGISTER_SIZE = 32; /** The decoded string representation of the arguments supplied to console.log */ export type ConsoleLogArgs = string[]; export type ConsoleLogs = ConsoleLogArgs[]; export class ConsoleLogger { /** * Temporary code to print console.sol messages that come from EDR */ public static getDecodedLogs(messages: Buffer[]): string[] { const logs: string[] = []; for (const message of messages) { const log = ConsoleLogger.#maybeConsoleLog(message); if (log !== undefined) { logs.push(ConsoleLogger.format(log)); } } return logs; } /** * 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); } /** Decodes a calldata buffer into string arguments for a console log. */ static #maybeConsoleLog(calldata: Buffer): ConsoleLogArgs | undefined { const selector = bytesToNumber(calldata.subarray(0, 4)); const parameters = calldata.subarray(4); const argTypes = CONSOLE_LOG_SIGNATURES[selector]; if (argTypes === undefined) { return; } const decodedArgs = ConsoleLogger.#decode(parameters, argTypes); /** * 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. * If there is an even number of % then it is assumed that the % is escaped and should not be replaced. * The regex matches a '%d' or an '%i' that has an even number of * '%' behind it (including 0). This group of pairs of '%' is captured * and preserved, while the '%[di]' is replaced with '%s'. * Naively doing (%%)* is not enough; we also have to use the * (?<!%) negative look-behind to make this work. * The (?:) is just to avoid capturing that inner group. */ if (decodedArgs.length > 0) { decodedArgs[0] = decodedArgs[0].replace( /((?<!%)(?:%%)*)(%[di])/g, "$1%s", ); } return decodedArgs; } /** Decodes calldata parameters from `data` according to `types` into their string representation. */ static #decode(data: Buffer, types: string[]): string[] { return types.map((type, i) => { const position: number = i * 32; switch (types[i]) { case Uint256Ty: return bytesToBigInt( data.subarray(position, position + REGISTER_SIZE), ).toString(10); case Int256Ty: return fromSigned( data.subarray(position, position + REGISTER_SIZE), ).toString(); case BoolTy: if (data[position + 31] !== 0) { return "true"; } return "false"; case StringTy: const sStart = bytesToNumber( data.subarray(position, position + REGISTER_SIZE), ); const sLen = bytesToNumber( data.subarray(sStart, sStart + REGISTER_SIZE), ); return data .subarray(sStart + REGISTER_SIZE, sStart + REGISTER_SIZE + sLen) .toString(); case AddressTy: return bytesToHexString( data.subarray(position + 12, position + REGISTER_SIZE), ); case BytesTy: const bStart = bytesToNumber( data.subarray(position, position + REGISTER_SIZE), ); const bLen = bytesToNumber( data.subarray(bStart, bStart + REGISTER_SIZE), ); return bytesToHexString( data.subarray( bStart + REGISTER_SIZE, bStart + REGISTER_SIZE + bLen, ), ); case Bytes1Ty: return bytesToHexString(data.subarray(position, position + 1)); case Bytes2Ty: return bytesToHexString(data.subarray(position, position + 2)); case Bytes3Ty: return bytesToHexString(data.subarray(position, position + 3)); case Bytes4Ty: return bytesToHexString(data.subarray(position, position + 4)); case Bytes5Ty: return bytesToHexString(data.subarray(position, position + 5)); case Bytes6Ty: return bytesToHexString(data.subarray(position, position + 6)); case Bytes7Ty: return bytesToHexString(data.subarray(position, position + 7)); case Bytes8Ty: return bytesToHexString(data.subarray(position, position + 8)); case Bytes9Ty: return bytesToHexString(data.subarray(position, position + 9)); case Bytes10Ty: return bytesToHexString(data.subarray(position, position + 10)); case Bytes11Ty: return bytesToHexString(data.subarray(position, position + 11)); case Bytes12Ty: return bytesToHexString(data.subarray(position, position + 12)); case Bytes13Ty: return bytesToHexString(data.subarray(position, position + 13)); case Bytes14Ty: return bytesToHexString(data.subarray(position, position + 14)); case Bytes15Ty: return bytesToHexString(data.subarray(position, position + 15)); case Bytes16Ty: return bytesToHexString(data.subarray(position, position + 16)); case Bytes17Ty: return bytesToHexString(data.subarray(position, position + 17)); case Bytes18Ty: return bytesToHexString(data.subarray(position, position + 18)); case Bytes19Ty: return bytesToHexString(data.subarray(position, position + 19)); case Bytes20Ty: return bytesToHexString(data.subarray(position, position + 20)); case Bytes21Ty: return bytesToHexString(data.subarray(position, position + 21)); case Bytes22Ty: return bytesToHexString(data.subarray(position, position + 22)); case Bytes23Ty: return bytesToHexString(data.subarray(position, position + 23)); case Bytes24Ty: return bytesToHexString(data.subarray(position, position + 24)); case Bytes25Ty: return bytesToHexString(data.subarray(position, position + 25)); case Bytes26Ty: return bytesToHexString(data.subarray(position, position + 26)); case Bytes27Ty: return bytesToHexString(data.subarray(position, position + 27)); case Bytes28Ty: return bytesToHexString(data.subarray(position, position + 28)); case Bytes29Ty: return bytesToHexString(data.subarray(position, position + 29)); case Bytes30Ty: return bytesToHexString(data.subarray(position, position + 30)); case Bytes31Ty: return bytesToHexString(data.subarray(position, position + 31)); case Bytes32Ty: return bytesToHexString(data.subarray(position, position + 32)); default: return ""; } }); } }