Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(store): add foundation for v2 data modelling #352

Merged
merged 82 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from 71 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
08b6a06
feat(solecs): wip prototype for new data model core library
alvrs Jan 17, 2023
50009ff
feat(solecs): wip data model - add getData and split
alvrs Jan 18, 2023
6beb967
feat(solecs): wip data model - add StoreSwitch lib
alvrs Jan 18, 2023
f6000c4
feat(solecs): wip data model - add Vec2Table and tests
alvrs Jan 18, 2023
f958774
feat(solecs): wip data model - improve gas for slice/get
alvrs Jan 18, 2023
432ec65
feat(solecs): wip data model - add SystemTable and tests
alvrs Jan 18, 2023
ebc161d
feat(solecs): wip data model - add World and end-to-end tests
alvrs Jan 19, 2023
4c249d8
refactor(solecs): wip data model - rename schemas and tables
alvrs Jan 20, 2023
c358c83
refactor(solecs): wip data model - convert bytes without copying
alvrs Jan 20, 2023
669562b
feat(solecs): wip data model - add more efficient packing methods
alvrs Jan 20, 2023
fe24aeb
refactor(solecs): wip data model - change function names and schema r…
alvrs Jan 24, 2023
b4ed722
feat(solecs): wip data model - add logic to set partial word
alvrs Jan 25, 2023
0d8ea21
feat(solecs): wip data model - add low level buffer library
alvrs Jan 25, 2023
59675fa
feat(solecs): wip data model - add low level storage library
alvrs Jan 25, 2023
e769b4f
refactor(solecs): wip data model - use Storage lib in StoreCore
alvrs Jan 25, 2023
72c0439
refactor(solecs): wip data model - change schema encoding and naming
alvrs Jan 25, 2023
65411d8
feat(solecs): wip data model - add logic and tests to set and get dyn…
alvrs Jan 26, 2023
ad6b132
feat(solecs): wip data model - add logic and tests to read and write …
alvrs Jan 26, 2023
2cf2a82
feat(solecs): wip data model - add logic and test to set and get indi…
alvrs Jan 27, 2023
7d8b557
test(solecs): wip data model - add fuzzy test for Storage lib
alvrs Jan 27, 2023
018014b
test(solecs): wip data model - add fuzzy tests for Buffer and Bytes
alvrs Jan 27, 2023
a3b6501
feat(solecs): wip data model - add validity checks for registering sc…
alvrs Jan 27, 2023
4e6f452
feat(solecs): wip data model - move schema and packed counter to own …
alvrs Jan 29, 2023
101ba5a
refactor(solecs): wip data model - add different events for setting r…
alvrs Jan 29, 2023
c3ceb01
refactor(solecs): wip data model - make encoded dynamic lengths part …
alvrs Jan 29, 2023
ed068cc
test(solecs): wip data model - add mixed table with dynamic data schema
alvrs Jan 29, 2023
b5dfeeb
feat(cli): add gas report command
alvrs Jan 30, 2023
1511cbb
test(solecs): wip data model - refactor buffer and bytes tests to use…
alvrs Jan 30, 2023
a5f4af9
test(solecs): wip data model - add test for gas cost of custom encodi…
alvrs Jan 31, 2023
8d36c45
feat(solecs): wip data model - add CallbackArray schema and tests, re…
alvrs Jan 31, 2023
ebabe02
feat(solecs): wip data model - add indexer functionality
alvrs Jan 31, 2023
735921a
feat(solecs): wip data model - add registerOnUpdateHook to interface
alvrs Jan 31, 2023
2cb36b7
feat(store): move store core source into own package outside of solecs
alvrs Jan 31, 2023
c6a6fa3
build(store): add -rf to dist command to override existing files
alvrs Jan 31, 2023
e9f97c6
test(store): generate gasreport
alvrs Feb 1, 2023
104f751
build(store): fix CI, add gas report action
alvrs Feb 1, 2023
77f07ab
build(store): update gas report action name
alvrs Feb 1, 2023
f4278ef
refactor(store): use setRecord instead of setStaticData
alvrs Feb 1, 2023
b1baf08
build(store): add mud cli as dev dependency
alvrs Feb 1, 2023
dbde0dc
build(store): update forge-std and ds-test dependencies
alvrs Feb 1, 2023
9db9ea0
chore(store): use forge-std Test, remove ds-test dependency, fix gas-…
alvrs Feb 1, 2023
625bc4d
build(store): bring back ds-test bc forge-std actually needs it
alvrs Feb 1, 2023
608d00a
build: use local mud cli in gasreport action
alvrs Feb 1, 2023
4d74dde
build: gas report action - install cli dependencies before building
alvrs Feb 1, 2023
0146455
build: gas report action - move cli linking action after general buil…
alvrs Feb 1, 2023
40c512d
build: gas report action - force gh to use local cli
alvrs Feb 1, 2023
f75a52c
fix(store): remove sneaked in temp test file
alvrs Feb 1, 2023
3cba328
build(store): fix push for gas report action
alvrs Feb 1, 2023
f49bf75
feat(store): add onDelete hook, rename IOnUpdateHooks to IStoreHooks,…
alvrs Feb 1, 2023
373f68c
build(store): add explicit commit author for gas report action
alvrs Feb 1, 2023
8096ef0
refactor(store): prefix store events with Mud
alvrs Feb 1, 2023
92ae909
build: fix gasreport detached head
alvrs Feb 1, 2023
42a844a
refactor(store): rename IStoreHooks to IStoreHook
alvrs Feb 1, 2023
319ccc7
refactor(store): rename Schema_ to SchemaLib, PackedCounter_ to Packe…
alvrs Feb 1, 2023
55952b3
build(store): different way to solve gas report action detached head …
alvrs Feb 1, 2023
fc61b4c
refactor(store): more consistency for error naming
alvrs Feb 1, 2023
f4504f5
build: fix gas report action
alvrs Feb 1, 2023
3218469
refactor: rename gas report action
alvrs Feb 1, 2023
df8889c
refactor: hopefully actually fix gas report action
alvrs Feb 1, 2023
fba6ffb
refactor(store): move StoreCore internal functions to StoreCoreInternal
alvrs Feb 1, 2023
09725bf
refactor(store): rename write to store and read to load in Storage an…
alvrs Feb 1, 2023
d11ca82
build: another attempt at fixing the gas report action
alvrs Feb 1, 2023
94486b6
test: update gas report
alvrs Feb 1, 2023
6053be6
refactor(store): move test out of src
alvrs Feb 1, 2023
1fd8d6a
test: update gas report
alvrs Feb 1, 2023
e8f1727
refactor(store): use named params for Storage and Memory lib calls, r…
alvrs Feb 2, 2023
eb04849
test: update gas report
alvrs Feb 2, 2023
7e25d96
refactor(store): use abi.encodePacked for fixed length types instead …
alvrs Feb 2, 2023
73e8f2f
test: update gas report
alvrs Feb 2, 2023
28ed69c
refactor(core): change argument name of registerHook
alvrs Feb 2, 2023
0c22b76
fix(cli): support gas-report with lines including quotes
alvrs Feb 2, 2023
c650b1c
chore: remove unnecessary conversion methods for primitive types
alvrs Feb 7, 2023
40f33c2
chore(store): remove unused code
alvrs Feb 7, 2023
211e72a
refactor: use uint256 in PackedCounter accumulator
alvrs Feb 7, 2023
8e97d6e
chore: change setting of schema length bytes
alvrs Feb 7, 2023
daee780
fix: don't reset dynamic data when deleting storage value but only le…
alvrs Feb 7, 2023
bf72e06
chore: update comments
alvrs Feb 7, 2023
972f9f5
chore: move _getStaticData into the correct section
alvrs Feb 7, 2023
8debd1c
chore: remove misleading comment
alvrs Feb 7, 2023
2c5e6f3
chore: remove comment
alvrs Feb 7, 2023
e144272
chore: remove comment
alvrs Feb 7, 2023
9293a51
test: update gas report
alvrs Feb 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/gasreport.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# .github/workflows/gasreport.yml
name: Gas report

on:
pull_request:
paths:
- packages/store/**

jobs:
gas-report:
runs-on: ubuntu-22.04
name: Generate gas report
steps:
- uses: actions/setup-node@v3
with:
node-version: 16

- name: git-checkout
uses: actions/checkout@v3

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Install dependencies
run: yarn install --network-concurrency 1

- name: Install local CLI
run: cd packages/cli && yarn build && npm link && cd ../..

- name: Run gas report
run: yarn workspace @latticexyz/store run gasreport

- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "test: update gas report"
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"packages": [
"packages/utils",
"packages/solecs",
"packages/store",
"packages/cli",
"packages/recs",
"packages/react",
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"openurl": "^1.1.1",
"path": "^0.12.7",
"solmate": "https://github.com/Rari-Capital/solmate.git#9cf1428245074e39090dceacb0c28b1f684f584c",
"table": "^6.8.1",
"typechain": "^8.1.1",
"uuid": "^8.3.2",
"yargs": "^17.5.1"
Expand Down
219 changes: 219 additions & 0 deletions packages/cli/src/commands/gas-report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import type { Arguments, CommandBuilder } from "yargs";
import { readFileSync, writeFileSync, rmSync } from "fs";
import { execa } from "execa";
import chalk from "chalk";
import { table, getBorderCharacters } from "table";

/**
* Print the gas report to the console, save it to a file and compare it to a previous gas report if provided.
* Requires forge to be installed, and gas test files including `// !gasreport` comments, like this:
*
* ```solidity
* contract GasTest is DSTestPlus {
* function testBuffer() public pure {
* // !gasreport allocate a buffer
* Buffer buffer = Buffer_.allocate(32);
*
* bytes32 value = keccak256("some data");
*
* // !gasreport append 32 bytes to a buffer
* buffer.append(value);
* }
* }
* ```
*/

type Options = {
path: string[];
save?: string;
compare?: string;
};

type GasReportEntry = {
source: string;
name: string;
functionCall: string;
gasUsed: number;
prevGasUsed?: number;
};

type GasReport = GasReportEntry[];

export const command = "gas-report";
export const desc = "Create a gas report";

export const builder: CommandBuilder<Options, Options> = (yargs) =>
yargs.options({
path: { type: "array", 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" },
});

export const handler = async (args: Arguments<Options>): Promise<void> => {
const { path, save } = args;
let { compare } = args;
let gasReport: GasReport = [];

// Iterate through all files provided in the path
for (const file of path) {
gasReport = gasReport.concat(await runGasReport(file));
}

// If this gas report should be compared to an existing one, load the existing one
const compareGasReport: GasReport = [];
if (compare) {
try {
const compareFileContents = readFileSync(compare, "utf8");
// Create a regex to extract the name, function call and gas used
const compareGasReportRegex = new RegExp(/\((.*)\) \| (.*) \[(.*)\]: (.*)/g);
// Loop through the matches and add the resuls to the compareGasReport
let compareGasReportMatch;
while ((compareGasReportMatch = compareGasReportRegex.exec(compareFileContents)) !== null) {
const source = compareGasReportMatch[1];
const name = compareGasReportMatch[2];
const functionCall = compareGasReportMatch[3];
const gasUsed = compareGasReportMatch[4];

compareGasReport.push({ source, name, functionCall, gasUsed: parseInt(gasUsed) });
}
} catch {
console.log(chalk.red(`Gas report to compare not found: ${compare}`));
compare = undefined;
}
}

// 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);
return { ...entry, prevGasUsed: prevEntry?.gasUsed };
});

// Print gas report
printGasReport(gasReport, compare);

// Save gas report to file if requested
if (save) saveGasReport(gasReport, save);

process.exit(0);
};

async function runGasReport(path: string): Promise<GasReport> {
if (!path.endsWith(".t.sol")) {
console.log("Skipping gas report for", chalk.bold(path), "(not a test file)");
return [];
}
console.log("Running gas report for", chalk.bold(path));
const gasReport: GasReport = [];

// Parse the given test file, and add gas reporting wherever requested by a `// !gasreport` comment
const fileContents = readFileSync(path, "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;
let i = 0;
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: ${name} [${functionCall.replaceAll('"', '\\"')}]:", _gasreport);`
);

i++;
}

// 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 = path.replace(/\.t\.sol$/, "MudGasReport.t.sol");
writeFileSync(tempFileName, newFile);

// Run the generated file using forge
const child = execa("forge", ["test", "--match-path", tempFileName, "-vvv"], {
stdio: ["inherit", "pipe", "inherit"],
});

// Extrect the logs from the child process
let logs = "";
try {
logs = (await child).stdout;
rmSync(tempFileName);
} catch (e: any) {
console.log(e.stdout ?? e);
console.log(chalk.red("\n-----------\nError while running the gas report (see above)"));
rmSync(tempFileName);
process.exit();
}

// Extract the gas reports from the logs

// 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 name = gasReportMatch[1];
const functionCall = gasReportMatch[2].replace(";", "");
const gasUsed = gasReportMatch[3];

gasReport.push({ source: path, name, functionCall, gasUsed: parseInt(gasUsed) });
}

return gasReport;
}

function printGasReport(gasReport: GasReport, compare?: string) {
if (compare) console.log(chalk.bold(`Gas report compared to ${compare}`));

const headers = [
chalk.bold("Source"),
chalk.bold("Name"),
chalk.bold("Function call"),
chalk.bold("Gas used"),
...(compare ? [chalk.bold("Prev gas used"), chalk.bold("Difference")] : []),
];

const values = gasReport.map((entry) => {
const diff = entry.prevGasUsed ? entry.gasUsed - entry.prevGasUsed : 0;
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];
});

const rows = [headers, ...values];

console.log(table(rows, { border: getBorderCharacters("norc") }));
}

function saveGasReport(gasReport: GasReport, path: string) {
console.log(chalk.bold(`Saving gas report to ${path}`));
const serializedGasReport = gasReport
.map((entry) => `(${entry.source}) | ${entry.name} [${entry.functionCall}]: ${entry.gasUsed}`)
.join("\n");

writeFileSync(path, serializedGasReport);
}
2 changes: 1 addition & 1 deletion packages/cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */

/* Language and Environment */
"target": "es2015" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
Expand Down
10 changes: 10 additions & 0 deletions packages/store/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
cache
abi
out
types/ethers-contracts
docs
_docs
DOCS.md
artifacts
yarn-error.log
API
7 changes: 7 additions & 0 deletions packages/store/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*

!abi/**
!src/**
!types/**
!package.json
!README.md
1 change: 1 addition & 0 deletions packages/store/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
17
7 changes: 7 additions & 0 deletions packages/store/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"printWidth": 120,
"semi": true,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true
}
8 changes: 8 additions & 0 deletions packages/store/.solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "solhint:recommended",
"rules": {
"compiler-version": ["error", ">=0.8.0"],
"avoid-low-level-calls": "off",
"func-visibility": ["warn", { "ignoreConstructors": true }]
}
}
4 changes: 4 additions & 0 deletions packages/store/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
1 change: 1 addition & 0 deletions packages/store/CHANGELOG.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
label: Changelog
1 change: 1 addition & 0 deletions packages/store/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Store
9 changes: 9 additions & 0 deletions packages/store/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[profile.default]
ffi = false
fuzz_runs = 256
optimizer = true
optimizer_runs = 1000000
verbosity = 1
libs = ["../../node_modules"]
src = "src"
out = "out"
Loading