UNPKG

@ganache/console.log

Version:

A Solidity library and EVM hooks for using console.log from Solidity contracts

182 lines 7.42 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getSignatures = exports.hardhatTypeAliases = exports.combinatorTypes = void 0; const utils_1 = require("@ganache/utils"); exports.combinatorTypes = [ "address", "bool", "string memory", "uint256" ]; const primitiveTypes = [...exports.combinatorTypes, "bytes memory", "int256"]; /** * Hardhat abi encodes uint instead of uint256. This saves a couple of bytes, * but is incorrect. */ exports.hardhatTypeAliases = new Map([ ["uint256", "uint"], ["int256", "int"] ]); const typeToHandlerMap = new Map([ ["address", "address"], ["bool", "bool"], ["bytes memory", "bytes"], ["int", "int256"], ["int256", "int256"], ["string memory", "string"], ["uint", "uint256"], ["uint256", "uint256"], // generate bytes1 .. bytes32 ...Array.from({ length: 32 }, (_, i) => [ `bytes${i + 1}`, `fixedBytes(${i + 1})` ]) ]); const COMMENT = ` /** * Prints to \`stdout\` with newline. Multiple arguments can be passed, with the * first used as the primary message and all additional used as substitution * values similar to [\`printf(3)\`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to \`util.format()\`). * * \`\`\`solidity * uint256 count = 5; * console.log('count: %d', count); * // Prints: count: 5, to stdout * console.log('count:', count); * // Prints: count: 5, to stdout * \`\`\` * * See \`util.format()\` for more information. */`; /** * A cache used to ensure we do not accidentally generate multiple Solidity * function signatures that result in the same "4-byte". * * It is technically possible that `keccak("log(< some params >)").slice(0, 4)` and * `keccak("log(< other params >)").slice(0, 4)` could be identical. In the * very unlikely event that this happens we'll need to rename the signature so * it no longer results in a "4-byte" collision. * * Note: this cache is only being used to detect "4-byte" signature collisions. */ const signatureCache = new Map(); /** * Generates the solidity and javascript function signatures for a given set of * params. * @param params * @param solidityFunctionName * @returns */ function getSignature(params, solidityFunctionName = "log") { const abiParams = params.map(type => type.replace(" memory", "")); const abiSignatureString = `log(${abiParams.join(",")})`; // the solidity "4-bytes" signature: const signature = (0, utils_1.keccak)(Buffer.from(abiSignatureString)).subarray(0, 4); // we store the signature as an int on the JS side: const signatureInt = parseInt(signature.toString("hex"), 16); /* istanbul ignore if */ if (signatureCache.has(signatureInt)) { // we have things like `logUint256(uint256)`, log(uint256), etc, that are // *supposed* to generate the exact same abi signature, so the 4-bytes will // match. We only want to throw if the signatures themselves differ while // the 4-bytes match. if (signatureCache.get(signatureInt) !== abiSignatureString) { // if we've already generated this signature before throw! throw new Error(`Signature collision detected between log(${abiSignatureString}) and ???(${signatureCache.get(signatureInt)})`); } } signatureCache.set(signatureInt, abiSignatureString); const names = params.length === 1 ? ["value"] : params.map((_, i) => `value${i + 1}`); const fullParamsWithNames = params.map((arg, i) => arg + " " + names[i]); const encodeArgs = [`"${abiSignatureString}"`, ...names]; const printComment = params.length > 1 && abiParams[0] === "string"; const solidity = `${printComment ? COMMENT : ""} function ${solidityFunctionName}(${fullParamsWithNames.join(", ")}) internal view { _sendLogPayload(abi.encodeWithSignature(${encodeArgs.join(", ")})); }`; const javascriptHandlers = params.map(arg => typeToHandlerMap.get(arg)); const javascript = ` // ${abiSignatureString} [${signatureInt}, [${javascriptHandlers.join(", ")}]]`; return { solidity, javascript, params, name: solidityFunctionName }; } function getNamedLogFunctionName(type) { const logName = `log${type[0].toUpperCase() + type.replace(" memory", "").slice(1)}`; return logName; } /** * Generates signature code as `logString(string memory)`or `logAddress(address)` * instead of just `log(string memory)` or `log(address)`. * * Might yield multiple signatures, like `"logUint256"` and `"logUint"` for * `solidityType` `"uint256"`. * * @param solidityType */ function* getNamedSignature(solidityType) { const logName = getNamedLogFunctionName(solidityType); yield getSignature([solidityType], logName); if (exports.hardhatTypeAliases.get(solidityType)) { const alias = exports.hardhatTypeAliases.get(solidityType); const aliasLogName = getNamedLogFunctionName(alias); const soliditySignature = getSignature([solidityType], aliasLogName); delete soliditySignature.javascript; const javascriptSignature = getSignature([alias], aliasLogName); delete javascriptSignature.solidity; yield soliditySignature; yield javascriptSignature; } } /** * Combines the array of types into every permutation of types from length 1 to * array.length. * @param array */ function* permute(array) { for (let i = 0; i < array.length; i++) { const length = Math.pow(array.length, i + 1); for (let j = 0; j < length; j++) { const parameters = []; for (let k = i; k >= 0; k--) { const denominator = Math.pow(array.length, k); const index = Math.floor(j / denominator) % array.length; const type = array[index]; parameters.push(type); } yield getSignature(parameters); // generate javascript signature handlers for hardhat's uint/int // signatures if (parameters.some(p => exports.hardhatTypeAliases.has(p))) { const aliasParams = parameters.map(p => exports.hardhatTypeAliases.has(p) ? exports.hardhatTypeAliases.get(p) : p); const aliasSignature = getSignature(aliasParams); // we don't want to include alias solidity signatures because they would // just automatically be compiled to the uint256 and int256 versions // anyway; the signatures are the same. delete aliasSignature.solidity; yield aliasSignature; } } } } function* getSignatures() { const emptyLog = getSignature([]); yield emptyLog; // logString(string value), logBytes(bytes value), etc. for (const signatures of primitiveTypes.map(getNamedSignature)) { for (const signature of signatures) yield signature; } // logBytes1(bytes1 value1) ... logBytes32(bytes1 value1) for (let n = 1; n <= 32; n++) { yield getSignature([`bytes${n}`], `logBytes${n}`); } // all possible permutations of combinatorTypes: for (const signature of permute(exports.combinatorTypes)) yield signature; } exports.getSignatures = getSignatures; //# sourceMappingURL=helpers.js.map