diff --git a/packages/cli/package.json b/packages/cli/package.json index 6da0bbd994..6c94e5b3c4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -51,6 +51,7 @@ "nice-grpc-web": "^2.0.1", "openurl": "^1.1.1", "path": "^0.12.7", + "strip-ansi": "^7.1.0", "table": "^6.8.1", "throttle-debounce": "^5.0.0", "typechain": "^8.1.1", diff --git a/packages/cli/src/commands/gas-report.ts b/packages/cli/src/commands/gas-report.ts index 378dffc123..737b546a0c 100644 --- a/packages/cli/src/commands/gas-report.ts +++ b/packages/cli/src/commands/gas-report.ts @@ -1,8 +1,9 @@ import type { CommandModule } from "yargs"; -import { readFileSync, writeFileSync, rmSync } from "fs"; +import { readFileSync, writeFileSync } from "fs"; import { execa } from "execa"; import chalk from "chalk"; import { table, getBorderCharacters } from "table"; +import stripAnsi from "strip-ansi"; /** * Print the gas report to the console, save it to a file and compare it to a previous gas report if provided. @@ -30,17 +31,15 @@ type Options = { }; type GasReportEntry = { - source: string; + file: string; + test: string; name: string; - functionCall: string; gasUsed: number; prevGasUsed?: number; }; type GasReport = GasReportEntry[]; -const tempFileSuffix = "MudGasReport"; - const commandModule: CommandModule = { command: "gas-report", @@ -48,30 +47,19 @@ const commandModule: CommandModule = { builder(yargs) { return yargs.options({ - path: { type: "array", string: true, default: ["Gas.t.sol"], desc: "File containing the gas tests" }, save: { type: "string", desc: "Save the gas report to a file" }, compare: { type: "string", desc: "Compare to an existing gas report" }, }); }, - async handler({ path: files, save, compare }) { - const validFiles = files.filter((file) => file.endsWith(".t.sol")); - const tempFiles = await Promise.all(validFiles.map((file) => createGasReport(file))); - - process.once("SIGINT", () => { - console.log("caught sigint, deleting temp files"); - tempFiles.forEach((file) => rmSync(file)); - }); - + async handler({ save, compare }) { let gasReport: GasReport; try { gasReport = await runGasReport(); - } catch { + } catch (error) { + console.error(error); setTimeout(() => process.exit()); return; - } finally { - // Delete the temporary files - tempFiles.forEach((file) => rmSync(file)); } // If this gas report should be compared to an existing one, load the existing one @@ -80,9 +68,7 @@ const commandModule: CommandModule = { const compareGasReport: GasReport = JSON.parse(readFileSync(compare, "utf8")); // Merge the previous gas report with the new one gasReport = gasReport.map((entry) => { - const prevEntry = compareGasReport.find( - (e) => e.name === entry.name && e.functionCall === entry.functionCall - ); + const prevEntry = compareGasReport.find((e) => e.file === entry.file && e.name === entry.name); return { ...entry, prevGasUsed: prevEntry?.gasUsed }; }); } catch { @@ -103,66 +89,18 @@ const commandModule: CommandModule = { export default commandModule; -async function createGasReport(filename: string): Promise { - console.log("Creating gas report for", chalk.bold(filename)); - - // Parse the given test file, and add gas reporting wherever requested by a `// !gasreport` comment - const fileContents = readFileSync(filename, "utf8"); - let newFile = fileContents; - - // Use a regex to find first line of each function - const functionRegex = new RegExp(/function (.*){/g); - // Insert a line to declare the _gasreport variable at the start of each function - let functionMatch; - while ((functionMatch = functionRegex.exec(fileContents)) !== null) { - const functionSignature = functionMatch[0]; - newFile = newFile.replace(functionSignature, `${functionSignature}\nuint256 _gasreport;`); - } - - // A gasreport comment has a name (written after the comment) and a function call (written on the next line) - // Create a regex to extract both the name and the function call - const regex = new RegExp(/\/\/ !gasreport (.*)\n(.*)/g); - - // Apply the regex and loop through the matches, - // and create a new file with the gasreport comments replaced by the gas report - let match; - while ((match = regex.exec(fileContents)) !== null) { - const name = match[1]; - const functionCall = match[2].trim(); - - newFile = newFile.replace( - match[0], - ` -_gasreport = gasleft(); -${functionCall} -_gasreport = _gasreport - gasleft(); -console.log("GAS REPORT(${filename}): ${name} [${functionCall.replaceAll('"', '\\"')}]:", _gasreport);` - ); - } - - // Remove all occurrences of `pure` with `view` - newFile = newFile.replace(/pure/g, "view"); - - // Write the new file to disk (temporarily) - // Create the temp file by replacing the previous file name with MudGasReport - const tempFileName = filename.replace(/\.t\.sol$/, `${tempFileSuffix}.t.sol`); - writeFileSync(tempFileName, newFile); - - return tempFileName; -} - async function runGasReport(): Promise { console.log("Running gas report"); const gasReport: GasReport = []; // Extract the logs from the child process - let logs = ""; + let stdout: string; try { // Run the generated file using forge - const child = execa("forge", ["test", "--match-path", `*${tempFileSuffix}*`, "-vvv"], { + const child = execa("forge", ["test", "-vvv"], { stdio: ["inherit", "pipe", "inherit"], }); - logs = (await child).stdout; + stdout = (await child).stdout; } catch (error: any) { console.log(error.stdout ?? error); console.log(chalk.red("\n-----------\nError while running the gas report (see above)")); @@ -170,21 +108,53 @@ async function runGasReport(): Promise { } // Extract the gas reports from the logs + const lines = stdout.split("\n").map(stripAnsi); + const gasReportPattern = /^\s*GAS REPORT: (\d+) (.*)$/; + const testFunctionPattern = /^\[(?:PASS|FAIL).*\] (\w+)\(\)/; + const testFilePattern = /^Running \d+ tests? for (.*):(.*)$/; + + function nearestLine(pattern: RegExp, startIndex: number = lines.length - 1): number { + for (let i = startIndex; i >= 0; i--) { + const line = lines[i]; + if (pattern.test(line)) { + return i; + } + } + return -1; + } + + for (let i = 0; i < lines.length; i++) { + const matches = lines[i].match(gasReportPattern); + if (!matches) continue; + + const gasUsed = parseInt(matches[1]); + const name = matches[2]; + + const testFunctionLineIndex = nearestLine(testFunctionPattern, i); + if (testFunctionLineIndex === -1) { + throw new Error("Could not find nearest test function, did `forge test` output change?"); + } + const testFileLineIndex = nearestLine(testFilePattern, testFunctionLineIndex); + if (testFileLineIndex === -1) { + throw new Error("Could not find nearest test filename, did `forge test` output change?"); + } + + const functionMatches = lines[testFunctionLineIndex].match(testFunctionPattern); + if (!functionMatches) { + throw new Error("Could not parse test function name, did `forge test` output change?"); + } + const fileMatches = lines[testFileLineIndex].match(testFilePattern); + if (!fileMatches) { + throw new Error("Could not parse test filename, did `forge test` output change?"); + } + + const test = functionMatches[1]; + const file = fileMatches[1]; - // Create a regex to find all lines starting with `GAS REPORT:` and extract the name, function call and gas used - const gasReportRegex = new RegExp(/GAS REPORT\((.*)\): (.*) \[(.*)\]: (.*)/g); - - // Loop through the matches and print the gas report - let gasReportMatch; - while ((gasReportMatch = gasReportRegex.exec(logs)) !== null) { - const source = gasReportMatch[1]; - const name = gasReportMatch[2]; - const functionCall = gasReportMatch[3].replace(";", ""); - const gasUsed = parseInt(gasReportMatch[4]); - gasReport.push({ source, name, functionCall, gasUsed }); + gasReport.push({ file, test, name, gasUsed }); } - gasReport.sort((a, b) => a.source.localeCompare(b.source)); + gasReport.sort((a, b) => a.file.localeCompare(b.file)); return gasReport; } @@ -193,9 +163,9 @@ function printGasReport(gasReport: GasReport, compare?: string) { if (compare) console.log(chalk.bold(`Gas report compared to ${compare}`)); const headers = [ - chalk.bold("Source"), + chalk.bold("File"), + chalk.bold("Test"), chalk.bold("Name"), - chalk.bold("Function call"), chalk.bold("Gas used"), ...(compare ? [chalk.bold("Prev gas used"), chalk.bold("Difference")] : []), ]; @@ -205,7 +175,7 @@ function printGasReport(gasReport: GasReport, compare?: string) { const diffEntry = diff > 0 ? chalk.red(`+${diff}`) : diff < 0 ? chalk.green(`${diff}`) : diff; const compareColumns = compare ? [entry.prevGasUsed ?? "n/a", diffEntry] : []; const gasUsedEntry = diff > 0 ? chalk.red(entry.gasUsed) : diff < 0 ? chalk.green(entry.gasUsed) : entry.gasUsed; - return [entry.source, entry.name, entry.functionCall, gasUsedEntry, ...compareColumns]; + return [entry.file, entry.test, entry.name, gasUsedEntry, ...compareColumns]; }); const rows = [headers, ...values]; diff --git a/packages/store/gas-report.json b/packages/store/gas-report.json index 7ca8be11cd..8e77fb77b1 100644 --- a/packages/store/gas-report.json +++ b/packages/store/gas-report.json @@ -1,746 +1,38 @@ [ { - "source": "test/Bytes.t.sol", - "name": "compare equal bytes", - "functionCall": "bool equals = Bytes.equals(a, b)", - "gasUsed": 202 - }, - { - "source": "test/Bytes.t.sol", - "name": "compare unequal bytes", - "functionCall": "bool equals = Bytes.equals(a, b)", - "gasUsed": 202 - }, - { - "source": "test/Bytes.t.sol", - "name": "set bytes1 in bytes32", - "functionCall": "Bytes.setBytes1(input, 8, 0xff)", - "gasUsed": 7 - }, - { - "source": "test/Bytes.t.sol", - "name": "set bytes2 in bytes32", - "functionCall": "Bytes.setBytes2(input, 8, 0xffff)", - "gasUsed": 7 - }, - { - "source": "test/Bytes.t.sol", - "name": "set bytes4 in bytes32", - "functionCall": "Bytes.setBytes4(input, 8, 0xffffffff)", - "gasUsed": 7 - }, - { - "source": "test/Bytes.t.sol", - "name": "set bytes4 in bytes memory", - "functionCall": "bytes memory result = Bytes.setBytes4(input, 0, overwrite)", - "gasUsed": 43 - }, - { - "source": "test/Bytes.t.sol", - "name": "slice bytes3 with offset 1", - "functionCall": "bytes3 b = Bytes.slice3(a, 1)", - "gasUsed": 77 - }, - { - "source": "test/Bytes.t.sol", - "name": "slice bytes32 with offset 10", - "functionCall": "bytes32 output = Bytes.slice32(input, 10)", - "gasUsed": 74 - }, - { - "source": "test/Bytes.t.sol", - "name": "create bytes32 from bytes memory with offset 0", - "functionCall": "bytes32 output = Bytes.toBytes32(input, 0)", - "gasUsed": 22 - }, - { - "source": "test/Bytes.t.sol", - "name": "create bytes32 from bytes memory with offset 16", - "functionCall": "bytes32 output = Bytes.toBytes32(input, 16)", - "gasUsed": 22 - }, - { - "source": "test/Gas.t.sol", + "file": "test/Gas.t.sol", + "test": "testCompareAbiEncodeVsCustom", "name": "abi encode", - "functionCall": "bytes memory abiEncoded = abi.encode(mixed)", - "gasUsed": 969 + "gasUsed": 956 }, { - "source": "test/Gas.t.sol", + "file": "test/Gas.t.sol", + "test": "testCompareAbiEncodeVsCustom", "name": "abi decode", - "functionCall": "Mixed memory abiDecoded = abi.decode(abiEncoded, (Mixed))", - "gasUsed": 1755 + "gasUsed": 1745 }, { - "source": "test/Gas.t.sol", + "file": "test/Gas.t.sol", + "test": "testCompareAbiEncodeVsCustom", "name": "custom encode", - "functionCall": "bytes memory customEncoded = customEncode(mixed)", - "gasUsed": 1826 + "gasUsed": 1811 }, { - "source": "test/Gas.t.sol", + "file": "test/Gas.t.sol", + "test": "testCompareAbiEncodeVsCustom", "name": "custom decode", - "functionCall": "Mixed memory customDecoded = customDecode(customEncoded)", - "gasUsed": 2446 + "gasUsed": 2123 }, { - "source": "test/Gas.t.sol", + "file": "test/Gas.t.sol", + "test": "testCompareAbiEncodeVsCustom", "name": "pass abi encoded bytes to external contract", - "functionCall": "someContract.doSomethingWithBytes(abiEncoded)", - "gasUsed": 6554 + "gasUsed": 6549 }, { - "source": "test/Gas.t.sol", + "file": "test/Gas.t.sol", + "test": "testCompareAbiEncodeVsCustom", "name": "pass custom encoded bytes to external contract", - "functionCall": "someContract.doSomethingWithBytes(customEncoded)", - "gasUsed": 1381 - }, - { - "source": "test/KeyEncoding.t.sol", - "name": "register KeyEncoding schema", - "functionCall": "KeyEncoding.registerSchema()", - "gasUsed": 64695 - }, - { - "source": "test/Mixed.t.sol", - "name": "store Mixed struct in storage (native solidity)", - "functionCall": "testMixed = mixed", - "gasUsed": 92050 - }, - { - "source": "test/Mixed.t.sol", - "name": "register Mixed schema", - "functionCall": "Mixed.registerSchema()", - "gasUsed": 61228 - }, - { - "source": "test/Mixed.t.sol", - "name": "set record in Mixed", - "functionCall": "Mixed.set({ key: key, u32: 1, u128: 2, a32: a32, s: s })", - "gasUsed": 112165 - }, - { - "source": "test/Mixed.t.sol", - "name": "get record from Mixed", - "functionCall": "MixedData memory mixed = Mixed.get(key)", - "gasUsed": 13460 - }, - { - "source": "test/PackedCounter.t.sol", - "name": "get value at index of PackedCounter", - "functionCall": "packedCounter.atIndex(3)", - "gasUsed": 261 - }, - { - "source": "test/PackedCounter.t.sol", - "name": "set value at index of PackedCounter", - "functionCall": "packedCounter = packedCounter.setAtIndex(2, 5)", - "gasUsed": 799 - }, - { - "source": "test/PackedCounter.t.sol", - "name": "pack uint40 array into PackedCounter", - "functionCall": "PackedCounter packedCounter = PackedCounterLib.pack(counters)", - "gasUsed": 2152 - }, - { - "source": "test/PackedCounter.t.sol", - "name": "get total of PackedCounter", - "functionCall": "packedCounter.total()", - "gasUsed": 33 - }, - { - "source": "test/Schema.t.sol", - "name": "get schema type at index", - "functionCall": "SchemaType schemaType1 = schema.atIndex(0)", - "gasUsed": 191 - }, - { - "source": "test/Schema.t.sol", - "name": "get number of dynamic fields from schema", - "functionCall": "uint256 num = schema.numDynamicFields()", - "gasUsed": 80 - }, - { - "source": "test/Schema.t.sol", - "name": "get number of static fields from schema", - "functionCall": "uint256 num = schema.numStaticFields()", - "gasUsed": 91 - }, - { - "source": "test/Schema.t.sol", - "name": "get static data length from schema", - "functionCall": "uint256 length = schema.staticDataLength()", - "gasUsed": 39 - }, - { - "source": "test/Schema.t.sol", - "name": "check if schema is empty (non-empty schema)", - "functionCall": "bool empty = encodedSchema.isEmpty()", - "gasUsed": 13 - }, - { - "source": "test/Schema.t.sol", - "name": "check if schema is empty (empty schema)", - "functionCall": "bool empty = encodedSchema.isEmpty()", - "gasUsed": 13 - }, - { - "source": "test/Schema.t.sol", - "name": "validate schema", - "functionCall": "encodedSchema.validate({ allowEmpty: false })", - "gasUsed": 18272 - }, - { - "source": "test/Slice.t.sol", - "name": "make Slice from bytes", - "functionCall": "Slice slice = SliceLib.fromBytes(data)", - "gasUsed": 40 - }, - { - "source": "test/Slice.t.sol", - "name": "get Slice length", - "functionCall": "slice.length()", - "gasUsed": 7 - }, - { - "source": "test/Slice.t.sol", - "name": "get Slice pointer", - "functionCall": "slice.pointer()", - "gasUsed": 33 - }, - { - "source": "test/Slice.t.sol", - "name": "subslice bytes (no copy) [1:4]", - "functionCall": "Slice slice1 = SliceLib.getSubslice(data, 1, 1 + 3)", - "gasUsed": 317 - }, - { - "source": "test/Slice.t.sol", - "name": "subslice bytes (no copy) [4:37]", - "functionCall": "Slice slice2 = SliceLib.getSubslice(data, 4, 4 + 33)", - "gasUsed": 317 - }, - { - "source": "test/Slice.t.sol", - "name": "Slice (0 bytes) to bytes memory", - "functionCall": "bytes memory sliceData0 = slice.toBytes()", - "gasUsed": 610 - }, - { - "source": "test/Slice.t.sol", - "name": "Slice (2 bytes) to bytes memory", - "functionCall": "bytes memory sliceData2 = slice.toBytes()", - "gasUsed": 644 - }, - { - "source": "test/Slice.t.sol", - "name": "Slice (32 bytes) to bytes memory", - "functionCall": "bytes memory sliceData32 = slice.toBytes()", - "gasUsed": 644 - }, - { - "source": "test/Slice.t.sol", - "name": "Slice (34 bytes) to bytes memory", - "functionCall": "bytes memory sliceData34 = slice.toBytes()", - "gasUsed": 722 - }, - { - "source": "test/Slice.t.sol", - "name": "Slice (1024 bytes) to bytes memory", - "functionCall": "bytes memory sliceData1024 = slice.toBytes()", - "gasUsed": 4979 - }, - { - "source": "test/Slice.t.sol", - "name": "Slice (1024x1024 bytes) to bytes memory", - "functionCall": "bytes memory sliceData1024x1024 = slice.toBytes()", - "gasUsed": 6606528 - }, - { - "source": "test/Slice.t.sol", - "name": "Slice to bytes32", - "functionCall": "bytes32 output = slice.toBytes32()", - "gasUsed": 87 - }, - { - "source": "test/Storage.t.sol", - "name": "store 1 storage slot", - "functionCall": "Storage.store({ storagePointer: storagePointer, data: originalDataFirstSlot })", - "gasUsed": 22509 - }, - { - "source": "test/Storage.t.sol", - "name": "store 34 bytes over 3 storage slots (with offset and safeTrail))", - "functionCall": "Storage.store({ storagePointer: storagePointer, offset: 31, data: data1 })", - "gasUsed": 23164 - }, - { - "source": "test/Storage.t.sol", - "name": "load 34 bytes over 3 storage slots (with offset and safeTrail))", - "functionCall": "bytes memory data = Storage.load({ storagePointer: storagePointer, length: 34, offset: 31 })", - "gasUsed": 1108 - }, - { - "source": "test/StoreCoreDynamic.t.sol", - "name": "get field slice (cold, 1 slot)", - "functionCall": "bytes memory secondFieldSlice = StoreCore.getFieldSlice(_table, _key, 1, schema, 0, 4)", - "gasUsed": 8161 - }, - { - "source": "test/StoreCoreDynamic.t.sol", - "name": "get field slice (warm, 1 slot)", - "functionCall": "secondFieldSlice = StoreCore.getFieldSlice(_table, _key, 1, schema, 4, 8)", - "gasUsed": 2188 - }, - { - "source": "test/StoreCoreDynamic.t.sol", - "name": "get field slice (semi-cold, 1 slot)", - "functionCall": "bytes memory thirdFieldSlice = StoreCore.getFieldSlice(_table, _key, 2, schema, 4, 32)", - "gasUsed": 4193 - }, - { - "source": "test/StoreCoreDynamic.t.sol", - "name": "get field slice (warm, 2 slots)", - "functionCall": "thirdFieldSlice = StoreCore.getFieldSlice(_table, _key, 2, schema, 8, 40)", - "gasUsed": 4475 - }, - { - "source": "test/StoreCoreDynamic.t.sol", - "name": "get field length (cold, 1 slot)", - "functionCall": "uint256 length = StoreCore.getFieldLength(_table, _key, 1, schema)", - "gasUsed": 7968 - }, - { - "source": "test/StoreCoreDynamic.t.sol", - "name": "get field length (warm, 1 slot)", - "functionCall": "length = StoreCore.getFieldLength(_table, _key, 1, schema)", - "gasUsed": 1964 - }, - { - "source": "test/StoreCoreDynamic.t.sol", - "name": "get field length (warm due to , 2 slots)", - "functionCall": "uint256 length = StoreCore.getFieldLength(_table, _key, 2, schema)", - "gasUsed": 7968 - }, - { - "source": "test/StoreCoreDynamic.t.sol", - "name": "get field length (warm, 2 slots)", - "functionCall": "length = StoreCore.getFieldLength(_table, _key, 2, schema)", - "gasUsed": 1964 - }, - { - "source": "test/StoreCoreDynamic.t.sol", - "name": "pop from field (cold, 1 slot, 1 uint32 item)", - "functionCall": "StoreCore.popFromField(_table, _key, 1, byteLengthToPop)", - "gasUsed": 29270 - }, - { - "source": "test/StoreCoreDynamic.t.sol", - "name": "pop from field (warm, 1 slot, 1 uint32 item)", - "functionCall": "StoreCore.popFromField(_table, _key, 1, byteLengthToPop)", - "gasUsed": 19348 - }, - { - "source": "test/StoreCoreDynamic.t.sol", - "name": "pop from field (cold, 2 slots, 10 uint32 items)", - "functionCall": "StoreCore.popFromField(_table, _key, 2, byteLengthToPop)", - "gasUsed": 31149 - }, - { - "source": "test/StoreCoreDynamic.t.sol", - "name": "pop from field (warm, 2 slots, 10 uint32 items)", - "functionCall": "StoreCore.popFromField(_table, _key, 2, byteLengthToPop)", - "gasUsed": 19231 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "access non-existing record", - "functionCall": "StoreCore.getRecord(table, key)", - "gasUsed": 7310 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "access static field of non-existing record", - "functionCall": "StoreCore.getField(table, key, 0)", - "gasUsed": 2982 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "access dynamic field of non-existing record", - "functionCall": "StoreCore.getField(table, key, 1)", - "gasUsed": 3593 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "access length of dynamic field of non-existing record", - "functionCall": "StoreCore.getFieldLength(table, key, 1, schema)", - "gasUsed": 1331 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "access slice of dynamic field of non-existing record", - "functionCall": "StoreCore.getFieldSlice(table, key, 1, schema, 0, 0)", - "gasUsed": 1331 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "delete record (complex data, 3 slots)", - "functionCall": "StoreCore.deleteRecord(table, key)", - "gasUsed": 11001 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "Check for existence of table (existent)", - "functionCall": "StoreCore.hasTable(table)", - "gasUsed": 1111 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "check for existence of table (non-existent)", - "functionCall": "StoreCore.hasTable(table2)", - "gasUsed": 3138 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "register subscriber", - "functionCall": "StoreCore.registerStoreHook(table, subscriber)", - "gasUsed": 65566 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "set record on table with subscriber", - "functionCall": "StoreCore.setRecord(table, key, data)", - "gasUsed": 73972 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "set static field on table with subscriber", - "functionCall": "StoreCore.setField(table, key, 0, data)", - "gasUsed": 31960 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "delete record on table with subscriber", - "functionCall": "StoreCore.deleteRecord(table, key)", - "gasUsed": 24553 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "register subscriber", - "functionCall": "StoreCore.registerStoreHook(table, subscriber)", - "gasUsed": 65566 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "set (dynamic) record on table with subscriber", - "functionCall": "StoreCore.setRecord(table, key, data)", - "gasUsed": 167409 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "set (dynamic) field on table with subscriber", - "functionCall": "StoreCore.setField(table, key, 1, arrayDataBytes)", - "gasUsed": 34927 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "delete (dynamic) record on table with subscriber", - "functionCall": "StoreCore.deleteRecord(table, key)", - "gasUsed": 26059 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "push to field (1 slot, 1 uint32 item)", - "functionCall": "StoreCore.pushToField(table, key, 1, secondDataToPush)", - "gasUsed": 17072 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "push to field (2 slots, 10 uint32 items)", - "functionCall": "StoreCore.pushToField(table, key, 2, thirdDataToPush)", - "gasUsed": 39780 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "StoreCore: register schema", - "functionCall": "StoreCore.registerSchema(table, schema, keySchema)", - "gasUsed": 54877 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "StoreCore: get schema (warm)", - "functionCall": "StoreCore.getSchema(table)", - "gasUsed": 1136 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "StoreCore: get key schema (warm)", - "functionCall": "StoreCore.getKeySchema(table)", - "gasUsed": 2330 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "set complex record with dynamic data (4 slots)", - "functionCall": "StoreCore.setRecord(table, key, data)", - "gasUsed": 107714 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "get complex record with dynamic data (4 slots)", - "functionCall": "StoreCore.getRecord(table, key)", - "gasUsed": 6443 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "compare: Set complex record with dynamic data using native solidity", - "functionCall": "testStruct = _testStruct", - "gasUsed": 116839 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "compare: Set complex record with dynamic data using abi.encode", - "functionCall": "testMapping[1234] = abi.encode(_testStruct)", - "gasUsed": 267377 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "set dynamic length of dynamic index 0", - "functionCall": "StoreCoreInternal._setDynamicDataLengthAtIndex(table, key, 0, 10)", - "gasUsed": 23600 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "set dynamic length of dynamic index 1", - "functionCall": "StoreCoreInternal._setDynamicDataLengthAtIndex(table, key, 1, 99)", - "gasUsed": 1719 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "reduce dynamic length of dynamic index 0", - "functionCall": "StoreCoreInternal._setDynamicDataLengthAtIndex(table, key, 0, 5)", - "gasUsed": 1707 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "set static field (1 slot)", - "functionCall": "StoreCore.setField(table, key, 0, firstDataPacked)", - "gasUsed": 37980 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "get static field (1 slot)", - "functionCall": "StoreCore.getField(table, key, 0)", - "gasUsed": 2980 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "set static field (overlap 2 slot)", - "functionCall": "StoreCore.setField(table, key, 1, secondDataPacked)", - "gasUsed": 35009 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "get static field (overlap 2 slot)", - "functionCall": "StoreCore.getField(table, key, 1)", - "gasUsed": 3877 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "set dynamic field (1 slot, first dynamic field)", - "functionCall": "StoreCore.setField(table, key, 2, thirdDataBytes)", - "gasUsed": 57377 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "get dynamic field (1 slot, first dynamic field)", - "functionCall": "StoreCore.getField(table, key, 2)", - "gasUsed": 3812 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "set dynamic field (1 slot, second dynamic field)", - "functionCall": "StoreCore.setField(table, key, 3, fourthDataBytes)", - "gasUsed": 35503 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "get dynamic field (1 slot, second dynamic field)", - "functionCall": "StoreCore.getField(table, key, 3)", - "gasUsed": 3825 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "set static record (1 slot)", - "functionCall": "StoreCore.setRecord(table, key, data)", - "gasUsed": 37369 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "get static record (1 slot)", - "functionCall": "StoreCore.getRecord(table, key, schema)", - "gasUsed": 1329 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "set static record (2 slots)", - "functionCall": "StoreCore.setRecord(table, key, data)", - "gasUsed": 59940 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "get static record (2 slots)", - "functionCall": "StoreCore.getRecord(table, key, schema)", - "gasUsed": 1578 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "StoreCore: set table metadata", - "functionCall": "StoreCore.setMetadata(table, tableName, fieldNames)", - "gasUsed": 251850 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "update in field (1 slot, 1 uint32 item)", - "functionCall": "StoreCore.updateInField(table, key, 1, 4 * 1, secondDataForUpdate)", - "gasUsed": 16604 - }, - { - "source": "test/StoreCoreGas.t.sol", - "name": "push to field (2 slots, 6 uint64 items)", - "functionCall": "StoreCore.updateInField(table, key, 2, 8 * 1, thirdDataForUpdate)", - "gasUsed": 17699 - }, - { - "source": "test/StoreMetadata.t.sol", - "name": "set record in StoreMetadataTable", - "functionCall": "StoreMetadata.set({ tableId: tableId, tableName: tableName, abiEncodedFieldNames: abi.encode(fieldNames) })", - "gasUsed": 250196 - }, - { - "source": "test/StoreMetadata.t.sol", - "name": "get record from StoreMetadataTable", - "functionCall": "StoreMetadataData memory metadata = StoreMetadata.get(tableId)", - "gasUsed": 12117 - }, - { - "source": "test/StoreSwitch.t.sol", - "name": "check if delegatecall", - "functionCall": "isDelegate = StoreSwitch.isDelegateCall()", - "gasUsed": 694 - }, - { - "source": "test/StoreSwitch.t.sol", - "name": "check if delegatecall", - "functionCall": "isDelegate = StoreSwitch.isDelegateCall()", - "gasUsed": 627 - }, - { - "source": "test/tables/Callbacks.t.sol", - "name": "set field in Callbacks", - "functionCall": "Callbacks.set(key, callbacks)", - "gasUsed": 63195 - }, - { - "source": "test/tables/Callbacks.t.sol", - "name": "get field from Callbacks (warm)", - "functionCall": "bytes24[] memory returnedCallbacks = Callbacks.get(key)", - "gasUsed": 5791 - }, - { - "source": "test/tables/Callbacks.t.sol", - "name": "push field to Callbacks", - "functionCall": "Callbacks.push(key, callbacks[0])", - "gasUsed": 40884 - }, - { - "source": "test/tables/Hooks.t.sol", - "name": "set field in Hooks", - "functionCall": "Hooks.set(key, addresses)", - "gasUsed": 63364 - }, - { - "source": "test/tables/Hooks.t.sol", - "name": "get field from Hooks (warm)", - "functionCall": "address[] memory returnedAddresses = Hooks.get(key)", - "gasUsed": 5940 - }, - { - "source": "test/tables/Hooks.t.sol", - "name": "push field to Hooks", - "functionCall": "Hooks.push(key, addresses[0])", - "gasUsed": 40876 - }, - { - "source": "test/tightcoder/DecodeSlice.t.sol", - "name": "decode packed uint32[]", - "functionCall": "uint32[] memory arr = SliceLib.fromBytes(input).decodeArray_uint32()", - "gasUsed": 794 - }, - { - "source": "test/tightcoder/DecodeSlice.t.sol", - "name": "decode packed bytes32[]", - "functionCall": "bytes32[] memory output = SliceLib.fromBytes(input).decodeArray_bytes32()", - "gasUsed": 623 - }, - { - "source": "test/tightcoder/EncodeArray.t.sol", - "name": "encode packed bytes[]", - "functionCall": "bytes memory output = EncodeArray.encode(input)", - "gasUsed": 1365 - }, - { - "source": "test/tightcoder/EncodeArray.t.sol", - "name": "encode packed uint16[]", - "functionCall": "bytes memory output = EncodeArray.encode(input)", - "gasUsed": 1155 - }, - { - "source": "test/tightcoder/EncodeArray.t.sol", - "name": "encode packed uint32[]", - "functionCall": "bytes memory output = EncodeArray.encode(input)", - "gasUsed": 1061 - }, - { - "source": "test/tightcoder/EncodeArray.t.sol", - "name": "encode packed uint8[]", - "functionCall": "bytes memory output = EncodeArray.encode(input)", - "gasUsed": 1050 - }, - { - "source": "test/tightcoder/TightCoder.t.sol", - "name": "decode packed uint32[]", - "functionCall": "uint32[] memory output = SliceLib.fromBytes(packed).decodeArray_uint32()", - "gasUsed": 797 - }, - { - "source": "test/tightcoder/TightCoder.t.sol", - "name": "encode packed bytes24[]", - "functionCall": "bytes memory packed = EncodeArray.encode(input)", - "gasUsed": 898 - }, - { - "source": "test/tightcoder/TightCoder.t.sol", - "name": "decode packed uint32[]", - "functionCall": "bytes24[] memory output = SliceLib.fromBytes(packed).decodeArray_bytes24()", - "gasUsed": 634 - }, - { - "source": "test/Vector2.t.sol", - "name": "register Vector2 schema", - "functionCall": "Vector2.registerSchema()", - "gasUsed": 58004 - }, - { - "source": "test/Vector2.t.sol", - "name": "set Vector2 record", - "functionCall": "Vector2.set({ key: key, x: 1, y: 2 })", - "gasUsed": 38643 - }, - { - "source": "test/Vector2.t.sol", - "name": "get Vector2 record", - "functionCall": "Vector2Data memory vector = Vector2.get(key)", - "gasUsed": 5094 + "gasUsed": 1375 } ] diff --git a/packages/store/package.json b/packages/store/package.json index a05e328c5b..bab6b295ee 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -46,7 +46,7 @@ "clean:typechain": "rimraf types", "dev": "tsup --watch", "docs": "rimraf API && hardhat docgen", - "gas-report": "../cli/dist/mud.js gas-report --path test/* --path test/**/* --save gas-report.json", + "gas-report": "../cli/dist/mud.js gas-report --save gas-report.json", "generate-tightcoder": "tsx ./scripts/generate-tightcoder.ts && prettier --write '**/tightcoder/*.sol'", "lint": "solhint --config ./.solhint.json 'src/**/*.sol'", "test": "vitest typecheck --run && vitest --run --passWithNoTests && forge test" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b57a697d59..c2f4721cd6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -131,6 +131,9 @@ importers: path: specifier: ^0.12.7 version: 0.12.7 + strip-ansi: + specifier: ^7.1.0 + version: 7.1.0 table: specifier: ^6.8.1 version: 6.8.1 @@ -4324,6 +4327,11 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: false + /ansi-styles@2.2.1: resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} engines: {node: '>=0.10.0'} @@ -13331,6 +13339,13 @@ packages: dependencies: ansi-regex: 5.0.1 + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: false + /strip-bom-stream@1.0.0: resolution: {integrity: sha512-7jfJB9YpI2Z0aH3wu10ZqitvYJaE0s5IzFuWE+0pbb4Q/armTloEUShymkDO47YSLnjAW52mlXT//hs9wXNNJQ==} engines: {node: '>=0.10.0'}