Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ jobs:
cache: "npm"
- run: npm ci
- run: npx hardhat compile
- run: npx hardhat size-contracts
- run: npx hardhat contract-size list # TODO: implement diff against main/base branch
- run: npm run solhint
- run: npx hardhat test
env:
REPORT_GAS: "1"
ALCHEMY_URL: ${{ secrets.ALCHEMY_URL }}
ALCHEMY_URL_POLYGON: ${{ secrets.ALCHEMY_URL_POLYGON }}
- run: npx hardhat coverage
- run: npx hardhat test --coverage
env:
ALCHEMY_URL: ${{ secrets.ALCHEMY_URL }}
ALCHEMY_URL_POLYGON: ${{ secrets.ALCHEMY_URL_POLYGON }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ node_modules

# Package build
/build
/types
108 changes: 96 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,120 @@

In this library we include several utility functions used for tests and other tasks like deployments.

## Migration to hardhat 3

Starting on v1.0.0 these utils use hardhat 3.

These are the changes required to migrate. Some steps from the [official migration guide](https://hardhat.org/docs/migrate-from-hardhat2) are included here for completeness, but be sure to read [the official migration guide](https://hardhat.org/docs/migrate-from-hardhat2) and the [tests migration guide](https://hardhat.org/docs/migrate-from-hardhat2/guides/mocha-tests) first.

General hardhat 3 migration:

1. Make sure you're on node 22.10 or later: `nvm use 22`
2. Clear cache: `npx hardhat clean`
3. Remove all hardhat packages from your `package.json`, this includes:

- `@nomicfoundation/*`
- `@nomiclabs/*`
- `hardhat`
- `hardhat-contract-sizer`
- `hardhat-dependency-compiler`
- `solidity-coverage`
- `@ensuro/utils` v0

4. Execute package uninstall: `npm i`
5. Remove old hardhat config: `mv hardhat.config.js hardhat.config.old.js`
6. Make the project ESM, by adding `"type": "module"` to `package.json`
7. Install hardhat 3: `npm add --save-dev hardhat @nomicfoundation/hardhat-toolbox-mocha-ethers @solidstate/hardhat-contract-sizer`
8. Create an empty config:

```js
import { defineConfig } from "hardhat/config";
import hardhatToolboxMochaEthers from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
import HardhatContractSizer from "@solidstate/hardhat-contract-sizer";

export default defineConfig({
plugins: [hardhatToolboxMochaEthers, HardhatContractSizer],
solidity: {
version: "0.8.30",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
evmVersion: "prague",
},
npmFilesToBuild: [
// List all contracts you had under dependencyCompiler here
],
},
contractSizer: {
alphaSort: true,
runOnCompile: false,
},
});
```

9. Run `npx hardhat build` to check everything is working so far

Tests migration checklist:

- Convert statements like `const x = require("x")` to `import x from "x"`.
- Convert `module.exports = x` to `export default x` and `module.exports.x = 1` to `export const x = 1`.
- Initialize a connection at the test-module top-level (or wherever):

```js
const connection = await hre.network.connect();
const { networkHelpers: helpers, ethers } = connection;
```

- Pass `ethers` as the first argument to all calls to `initCurrency`, `deployProxy` and `readImplementationAddress`. It must be the one from the connection, don't import ethersjs directly.
- `to.be.reverted` matcher is now `to.revert(ethers)`. Other matchers also expect to receive the `ethers` object as first argument too.
- Pass the connection as the first argument to all calls to `initForkCurrency`
- Pass the networkHelpers as the first argument to all calls to `amScheduleAndExecute` and `amScheduleAndExecuteBatch`.
- The function `setupChain` now creates a new connection forking at the given block/url and returns it
- Custom chai matchers like `revertedWithACError` and `revertedWithAMError` now require some additional initialization in hardhat.config.ts:

```js
import { use } from "chai";
import { chaiAccessControl } from "@ensuro/utils/js/chai-plugins";

use(chaiAccessControl);
```

Other stuff to look out for:

1. The solidity build now generates several build-info file. Adapt build scripts to only take the `.json` one (exclude the `.output.json` one).
2. `npx hardhat size-contracts` is now `npx hardhat contract-size list`
3. `npx hardhat coverage` is now `npx hardhat test --coverage`

## Hardhat-Retry

TODO: this plugins needs to be implemented as a proper hardhat plugin on hardhat 3.

We include hardhat-retry to enhance the stability of tests in the projects using Hardhat. It automatically retries due to network issues like:

- Header not found. This occurs if the node fails to locate the requested data temporaly.
- -32000: execution aborted (timeout = 10s). This occurs when a network request timeout or node delays.
- Gas related errors. This occurs during retries so we set initialBaseFeePerGas to 0 so we mitigate it.

### hardhat.config.js
### hardhat.config.ts

To use hardhat-retry add the following to your Hardhat configuration file:

```js
const hretry = require("@ensuro/utils/js/hardhat-retry");

hretry.installWrapper();
```
import hardhatRetryPlugin from "@ensuro/utils/plugins/retry/index.js";

To enable hardhat-retry works correctly you must configure the hardhat network settings. Add this network config to hardha.config.js:

```js
networks: {
hardhat: {
initialBaseFeePerGas: 0,
},
},
export default defineConfig({
plugins: [hardhatRetryPlugin],
...
})
```

## Verifiable binaries

TODO: adapt this to hardhat3.

The verifiableBinaries module enables the use of compiled contracts, fetched from NPM packages.

### hardhat.config.js
Expand Down
32 changes: 32 additions & 0 deletions contracts/UpgradeableMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract UpgradeableMock is Initializable, UUPSUpgradeable, OwnableUpgradeable {
uint256 private value;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

function initialize(address initialOwner) public initializer {
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
value = 0;
}

function setValue(uint256 newValue) public {
value = newValue;
}

function getValue() public view returns (uint256) {
return value;
}

// solhint-disable-next-line no-empty-blocks
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
32 changes: 0 additions & 32 deletions hardhat.config.js

This file was deleted.

35 changes: 35 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { defineConfig } from "hardhat/config";
import hardhatToolboxMochaEthers from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
import HardhatContractSizer from "@solidstate/hardhat-contract-sizer";

import hardhatRetryPlugin from "./js/plugins/retry/index.js";

import { use } from "chai";
import { chaiAccessControl } from "./js/chai-plugins.js";

use(chaiAccessControl);

export default defineConfig({
plugins: [hardhatToolboxMochaEthers, HardhatContractSizer, hardhatRetryPlugin],
solidity: {
version: "0.8.30",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
evmVersion: "prague",
},
npmFilesToBuild: [
"@openzeppelin/contracts/governance/TimelockController.sol",
"@openzeppelin/contracts/access/manager/AccessManager.sol",
"@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol",
"@openzeppelin/contracts/token/ERC20/ERC20.sol",
"@openzeppelin/contracts/token/ERC721/ERC721.sol",
],
},
contractSizer: {
alphaSort: true,
runOnCompile: false,
},
});
18 changes: 18 additions & 0 deletions js/chai-plugins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getRole } from "./utils.js";

// Chai plugin for custom access control / manager assertions
export function chaiAccessControl(chai, utils) {
const { Assertion } = chai;

// Install chai matcher
Assertion.addMethod("revertedWithACError", function (contract, user, role) {
return new Assertion(this._obj).to.be
.revertedWithCustomError(contract, "AccessControlUnauthorizedAccount")
.withArgs(user, getRole(role));
});

// Install chai matcher for AccessManagedError
Assertion.addMethod("revertedWithAMError", function (contract, user) {
return new Assertion(this._obj).to.be.revertedWithCustomError(contract, "AccessManagedUnauthorized").withArgs(user);
});
}
15 changes: 4 additions & 11 deletions js/constants.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
const HOUR = 3600;
const WEEK = HOUR * 24 * 7;
const DAY = HOUR * 24;
export const HOUR = 3600;
export const WEEK = HOUR * 24 * 7;
export const DAY = HOUR * 24;

// From https://eips.ethereum.org/EIPS/eip-1967
const IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";

module.exports = {
WEEK,
DAY,
HOUR,
IMPLEMENTATION_SLOT,
};
export const IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
37 changes: 0 additions & 37 deletions js/hardhat-retry.js

This file was deleted.

40 changes: 40 additions & 0 deletions js/plugins/retry/hooks/network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { HardhatPluginError } from "hardhat/plugins";
import type { HookContext, NetworkHooks } from "hardhat/types/hooks";
import { ChainType, NetworkConnection } from "hardhat/types/network";

export default async (): Promise<Partial<NetworkHooks>> => {
const handlers: Partial<NetworkHooks> = {
async newConnection<ChainTypeT extends ChainType | string>(
context: HookContext,
next: (nextContext: HookContext) => Promise<NetworkConnection<ChainTypeT>>
): Promise<NetworkConnection<ChainTypeT>> {
const connection = await next(context);

console.error("NOTIMPLEMENTED: Hardhat retry plugin is not implemented yet.", connection.provider);

/*
class BackoffRetry extends ProviderWrapper {
// eslint-disable-next-line consistent-return
async request(args) {
for (let i = 0; i < MAX_RETRIES; i++) {
try {
return await this._wrappedProvider.request(args);
} catch (e) {
if (!(e instanceof ProviderError) || i >= MAX_RETRIES - 1) throw e;
if (e.code === -32000 && (e.message.includes("header not found") || e.message.includes("timeout"))) {
console.error("Retrying %s because of temp error %s: %s (%s)", args.method, e.code, e.message, e.data);
await delay(BACKOFF_DELAY_MS);
continue;
}
throw e;
}
}
}
}
*/
return connection;
},
};

return handlers;
};
21 changes: 21 additions & 0 deletions js/plugins/retry/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { task } from "hardhat/config";
import { ArgumentType } from "hardhat/types/arguments";
import type { HardhatPlugin } from "hardhat/types/plugins";

import "./type-extensions.js";

const hardhatRetryPlugin: HardhatPlugin = {
id: "hardhat-retry",
hookHandlers: {
network: () => import("./hooks/network.js"),
},
tasks: [
task("hardhat-retry-debug", "Sample task to test the plugin")
.setAction(() => {
console.log("DEBUG: hardhat-retry-debug task executed");
})
.build(),
],
};

export default hardhatRetryPlugin;
Empty file.
4 changes: 4 additions & 0 deletions js/solidity-ast-shim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createRequire } from "module";
const require = createRequire(import.meta.url);

export const { findAll } = require("solidity-ast/utils");
Loading
Loading