Skip to content

Update for Hardhat 3 #8

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

Draft
wants to merge 36 commits into
base: solidstate
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1bac604
update hardhat dependencies, configure package as module
ItsNickBarry Apr 18, 2025
ad3932c
fix imports, export HardhatPlugin object
ItsNickBarry Apr 18, 2025
0249c0c
Merge branch 'master' into hh3
ItsNickBarry Apr 19, 2025
9a8b352
export HardhatPlugin object
ItsNickBarry Apr 19, 2025
fe91182
update tasks to Hardhat 3 API, register tasks in HardhatPlugin
ItsNickBarry Apr 19, 2025
3cc2e77
move task action functions to actions/ directory
ItsNickBarry Apr 19, 2025
770406b
load plugin into test config
ItsNickBarry Apr 19, 2025
2ba8158
fix lib file
ItsNickBarry Apr 19, 2025
a5a2145
fix build script
ItsNickBarry Apr 19, 2025
d6bdaf6
run clean script before build script
ItsNickBarry Apr 20, 2025
a4a0edd
use patch-package to patch hardhat output selection in development
ItsNickBarry Apr 20, 2025
9619276
replace extendConfig call with config hook
ItsNickBarry Apr 20, 2025
f3f710a
exclude artifacts from tsc
ItsNickBarry Apr 20, 2025
51cc205
add missing action export
ItsNickBarry Apr 20, 2025
73f5305
compile before all tasks
ItsNickBarry Apr 20, 2025
3989b3d
support non-fully-qualifed contract names
ItsNickBarry Apr 20, 2025
7902932
remove getCollatedStorageLayout function
ItsNickBarry Apr 20, 2025
f470b85
add git ref option to inspect task
ItsNickBarry Apr 20, 2025
15053cf
update test contract for git testing
ItsNickBarry Apr 20, 2025
a51bcf4
add callAtGitRef helper and getRawStorageLayoutFromArtifact callback
ItsNickBarry Apr 20, 2025
eb2fcc8
rename getRawStorageLayout to loadRawStorageLayout
ItsNickBarry Apr 20, 2025
a4d4ce2
read storage layout from file if path to .json is passed
ItsNickBarry Apr 20, 2025
707aca5
remove obsolete storage-layout-check task
ItsNickBarry Apr 20, 2025
fce55ff
update readme
ItsNickBarry Apr 20, 2025
1150c95
do not initialize git repository, wrap git checkout errors in Hardhat…
ItsNickBarry Apr 20, 2025
2c084c6
invert if condition to reduce nesting
ItsNickBarry Apr 20, 2025
0b061cb
remove obsolete ejs and mocha utilities
ItsNickBarry Apr 21, 2025
a5a1417
Merge branch 'solidstate' into hh3
ItsNickBarry Apr 21, 2025
2594b6b
avoid git checkout for file inputs
ItsNickBarry Apr 21, 2025
d407541
fix structure of loadRawStorageLayout
ItsNickBarry Apr 21, 2025
fc45bed
allow passing hre into bound function
ItsNickBarry Apr 21, 2025
f3b8a8b
add todo note
ItsNickBarry Apr 21, 2025
6231cb4
run patch-package on postinstall
ItsNickBarry Apr 21, 2025
76c0510
add getTmpHreAtGitRef helper, avoid double recompilation after git ch…
ItsNickBarry Apr 21, 2025
4f43935
remove callback pattern
ItsNickBarry Apr 21, 2025
c8019b0
remove unused intermediate variable
ItsNickBarry Apr 21, 2025
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
2 changes: 1 addition & 1 deletion .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"*.{js,ts,ejs,sol,json,md}": ["prettier --write"]
"*.{js,ts,sol,json,md}": ["prettier --write"]
}
1 change: 0 additions & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"trailingComma": "all",
"bracketSpacing": true,
"plugins": [
"prettier-plugin-ejs",
"prettier-plugin-solidity",
"@trivago/prettier-plugin-sort-imports"
]
Expand Down
29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@ yarn add --dev @solidstate/hardhat-storage-layout-diff
Load plugin in Hardhat config:

```javascript
require('@solidstate/hardhat-storage-layout-diff');
import HardhatStorageLayoutDiff from '@solidstate/hardhat-storage-layout-diff';

const config: HardhatUserConfig = {
plugins: [
HardhatStorageLayoutDiff,
],
storageLayoutDiff: {
... // see table for configuration options
},
};
```

Add configuration under the `storageLayoutDiff` key:
Expand All @@ -39,24 +48,26 @@ npx hardhat export-storage-layout
yarn run hardhat export-storage-layout
```

Compare two contracts:
Inspect a contract's storage layout:

```bash
npx hardhat diff-storage-layout [CONTRACT_A_FULLY_QUALIFIED_NAME] [CONTRACT_B_FULLY_QUALIFIED_NAME]
npx hardhat inspect-storage-layout [CONTRACT_IDENTIFIER]
# or
yarn run hardhat diff-storage-layout [CONTRACT_A_FULLY_QUALIFIED_NAME] [CONTRACT_B_FULLY_QUALIFIED_NAME]
yarn run hardhat inspect-storage-layout [CONTRACT_IDENTIFIER]
```

Include the optional `--a-ref` and/or `--b-ref` arguments to specify the git reference where contracts `a` and `b` are defined, respectively.

Compare a contract to an exported JSON layout:
Compare two contracts:

```bash
npx hardhat storage-layout-check --source [PATH_TO_LAYOUT_JSON] --b [CONTRACT_B_FULLY_QUALIFIED_NAME]
npx hardhat diff-storage-layout [CONTRACT_A_IDENTIFIER] [CONTRACT_B_IDENTIFIER]
# or
yarn run hardhat storage-layout-check --source [PATH_TO_LAYOUT_JSON] --b [CONTRACT_B_FULLY_QUALIFIED_NAME]
yarn run hardhat diff-storage-layout [CONTRACT_A_IDENTIFIER] [CONTRACT_B_IDENTIFIER]
```

A contract identifier may be a name, a fully qualified name, or a path to a JSON file containing a storage layout.

Include the optional git ref options to look up a contract identifier at a particular git reference.

## Development

Install dependencies via Yarn:
Expand Down
2 changes: 1 addition & 1 deletion contracts/Test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ contract Test {
struct Struct {
address a;
E e;
address b;
address b_changed;
E f;
}

Expand Down
3 changes: 2 additions & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import './src/index';
import HardhatStorageLayoutDiff from './src/index.js';
import { HardhatUserConfig } from 'hardhat/config';

const config: HardhatUserConfig = {
solidity: '0.8.28',
plugins: [HardhatStorageLayoutDiff],
};

export default config;
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
],
"repository": "github:solidstate-network/hardhat-storage-layout-diff",
"author": "Nick Barry",
"type": "module",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"scripts": {
"build": "tsc --build --clean",
"build": "yarn clean && tsc --build",
"clean": "tsc --build --clean",
"postinstall": "patch-package",
"prepare": "husky",
"prettier": "prettier --write ."
},
Expand All @@ -28,18 +31,16 @@
"src"
],
"peerDependencies": {
"hardhat": "^2.0.0"
"hardhat": "3.0.0-next.4"
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/ejs": "^3.1.5",
"@types/mocha": "^10.0.10",
"@types/node": "^22.14.1",
"hardhat": "^2.23.0",
"hardhat": "3.0.0-next.4",
"husky": "^9.1.7",
"lint-staged": "^15.5.1",
"patch-package": "^8.0.0",
"prettier": "^3.5.3",
"prettier-plugin-ejs": "^1.0.3",
"prettier-plugin-solidity": "^1.4.2",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
Expand Down
28 changes: 28 additions & 0 deletions patches/hardhat+3.0.0-next.4.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
diff --git a/node_modules/hardhat/dist/src/internal/builtin-plugins/solidity/build-system/compilation-job.js b/node_modules/hardhat/dist/src/internal/builtin-plugins/solidity/build-system/compilation-job.js
index c250a14..640899a 100644
--- a/node_modules/hardhat/dist/src/internal/builtin-plugins/solidity/build-system/compilation-job.js
+++ b/node_modules/hardhat/dist/src/internal/builtin-plugins/solidity/build-system/compilation-job.js
@@ -77,6 +77,23 @@ export class CompilationJobImplementation {
};
// TODO: Deep merge the user output selection with the default one
const outputSelection = defaultOutputSelection;
+
+ const configOutputSelection = settings.outputSelection ?? {};
+
+ for (const fileKey in configOutputSelection) {
+ const configFileOutputSelection = configOutputSelection[fileKey] ?? {};
+ outputSelection[fileKey] ??= {};
+ for (const contractKey in configFileOutputSelection) {
+ const configContractOutputSelection = configFileOutputSelection[contractKey] ?? [];
+ outputSelection[fileKey][contractKey] ??= [];
+
+ const values = Array.from(new Set([...outputSelection[fileKey][contractKey], ...configContractOutputSelection]))
+ values.sort();
+
+ outputSelection[fileKey][contractKey] = values;
+ }
+ }
+
return {
language: "Solidity",
settings: {
35 changes: 35 additions & 0 deletions src/actions/diff_storage_layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
collateStorageLayout,
loadRawStorageLayout,
mergeCollatedSlots,
printMergedCollatedSlots,
} from '../lib/storage_layout_diff.js';
import { NewTaskActionFunction } from 'hardhat/types/tasks';

interface DiffStorageLayoutTaskActionArguments {
a: string;
b: string;
aRef: string;
bRef: string;
}

const action: NewTaskActionFunction<
DiffStorageLayoutTaskActionArguments
> = async (args, hre) => {
// TODO: import task name constant
await hre.tasks.getTask('compile').run();

// TODO: check default values of ref parameters
const slotsA = collateStorageLayout(
await loadRawStorageLayout(hre, args.a, args.aRef),
);
const slotsB = collateStorageLayout(
await loadRawStorageLayout(hre, args.b, args.bRef),
);

const mergedSlots = mergeCollatedSlots(slotsA, slotsB);

printMergedCollatedSlots(mergedSlots);
};

export default action;
63 changes: 63 additions & 0 deletions src/actions/export_storage_layout.ts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import pkg from '../../package.json';
import { loadRawStorageLayout } from '../lib/storage_layout_diff.js';
import { HardhatPluginError } from 'hardhat/plugins';
import { NewTaskActionFunction } from 'hardhat/types/tasks';
import fs from 'node:fs';
import path from 'node:path';

const action: NewTaskActionFunction = async (args, hre) => {
// TODO: import task name constant
await hre.tasks.getTask('compile').run();

const config = hre.config.storageLayoutDiff;

const outputDirectory = path.resolve(hre.config.paths.root, config.path);

if (!outputDirectory.startsWith(hre.config.paths.root)) {
throw new HardhatPluginError(
pkg.name,
'resolved path must be inside of project directory',
);
}

if (outputDirectory === hre.config.paths.root) {
throw new HardhatPluginError(
pkg.name,
'resolved path must not be root directory',
);
}

if (config.clear && fs.existsSync(outputDirectory)) {
fs.rmdirSync(outputDirectory, { recursive: true });
}

if (!fs.existsSync(outputDirectory)) {
fs.mkdirSync(outputDirectory, { recursive: true });
}

for (let fullName of await hre.artifacts.getAllFullyQualifiedNames()) {
if (config.only.length && !config.only.some((m) => fullName.match(m)))
continue;
if (config.except.length && config.except.some((m) => fullName.match(m)))
continue;

const storageLayout = await loadRawStorageLayout(hre, fullName);
const { storage, types } = storageLayout;

if (!storage.length) continue;

const destination =
path.resolve(outputDirectory, config.flat ? '' : '', fullName) + '.json';

if (!fs.existsSync(path.dirname(destination))) {
fs.mkdirSync(path.dirname(destination), { recursive: true });
}

fs.writeFileSync(
destination,
`${JSON.stringify({ storage, types }, null, config.spacing)}\n`,
);
}
};

export default action;
26 changes: 26 additions & 0 deletions src/actions/inspect_storage_layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
collateStorageLayout,
loadRawStorageLayout,
printCollatedSlots,
} from '../lib/storage_layout_diff.js';
import { NewTaskActionFunction } from 'hardhat/types/tasks';

interface InspectStorageLayoutTaskActionArguments {
contract: string;
ref: string;
}

const action: NewTaskActionFunction<
InspectStorageLayoutTaskActionArguments
> = async (args, hre) => {
// TODO: import task name constant
await hre.tasks.getTask('compile').run();

const slots = collateStorageLayout(
await loadRawStorageLayout(hre, args.contract, args.ref),
);

printCollatedSlots(slots);
};

export default action;
40 changes: 40 additions & 0 deletions src/hooks/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { StorageLayoutDiffConfig } from '../types.js';
import type { ConfigHooks } from 'hardhat/types/hooks';

const DEFAULT_CONFIG: StorageLayoutDiffConfig = {
path: './storage_layout',
clear: false,
flat: false,
only: [],
except: [],
spacing: 2,
};

export default async (): Promise<Partial<ConfigHooks>> => ({
resolveUserConfig: async (userConfig, resolveConfigurationVariable, next) => {
const result = {
...(await next(userConfig, resolveConfigurationVariable)),
storageLayoutDiff: {
...DEFAULT_CONFIG,
...userConfig.storageLayoutDiff,
},
};

for (const key in result.solidity.profiles) {
const profile = result.solidity.profiles[key];

for (const compiler of profile.compilers) {
const settings = compiler.settings;
settings.outputSelection ??= {};
settings.outputSelection['*'] ??= {};
settings.outputSelection['*']['*'] ??= [];

if (!settings.outputSelection['*']['*'].includes('storageLayout')) {
settings.outputSelection['*']['*'].push('storageLayout');
}
}
}

return result;
},
});
47 changes: 17 additions & 30 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,21 @@
import './tasks/diff_storage_layout';
import './tasks/export_storage_layout';
import './tasks/inspect_storage_layout';
import './tasks/storage_layout_check';
import pkg from '../package.json';
import taskDiffStorageLayout from './tasks/diff_storage_layout.js';
import taskExportStorageLayout from './tasks/export_storage_layout.js';
import taskInspectStorageLayout from './tasks/inspect_storage_layout.js';
import './type_extensions';
import type { StorageLayoutDiffConfig } from './types';
import { extendConfig } from 'hardhat/config';
import type { HardhatPlugin } from 'hardhat/types/plugins';

// TODO: types may be incomplete

const DEFAULT_CONFIG: StorageLayoutDiffConfig = {
path: './storage_layout',
clear: false,
flat: false,
only: [],
except: [],
spacing: 2,
const plugin: HardhatPlugin = {
id: pkg.name,
npmPackage: pkg.name,
tasks: [
taskDiffStorageLayout,
taskExportStorageLayout,
taskInspectStorageLayout,
],
hookHandlers: {
config: import.meta.resolve('./hooks/config.js'),
},
};

extendConfig(function (config, userConfig) {
config.storageLayoutDiff = Object.assign(
{},
DEFAULT_CONFIG,
userConfig.storageLayoutDiff,
);

for (const compiler of config.solidity.compilers) {
const outputSelection = compiler.settings.outputSelection['*']['*'];

if (!outputSelection.includes('storageLayout')) {
outputSelection.push('storageLayout');
}
}
});
export default plugin;
Loading