diff --git a/.changeset/lazy-hornets-clap.md b/.changeset/lazy-hornets-clap.md deleted file mode 100644 index 61606d5fb9..0000000000 --- a/.changeset/lazy-hornets-clap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@nomicfoundation/hardhat-chai-matchers": patch ---- - -Fixed a problem when `.withArgs` was used with arrays with different length diff --git a/.changeset/neat-impalas-attend.md b/.changeset/neat-impalas-attend.md new file mode 100644 index 0000000000..4c55079548 --- /dev/null +++ b/.changeset/neat-impalas-attend.md @@ -0,0 +1,5 @@ +--- +"@nomiclabs/hardhat-web3": major +--- + +Updates web3 to latest 4.x version. diff --git a/.changeset/polite-mugs-cry.m b/.changeset/polite-mugs-cry.m index b03cb1f534..05b38a6eed 100644 --- a/.changeset/polite-mugs-cry.m +++ b/.changeset/polite-mugs-cry.m @@ -1,5 +1,5 @@ --- -"@nomiclabs/hardhat-etherscan": patch +"@nomiclabs/hardhat-verify": patch --- -Fix URLs for the Aurora networks (thanks @zZoMROT!) +Fix URLs for the Aurora networks (thanks @zZoMROT and @ZumZoom!) diff --git a/.github/workflows/hardhat-chai-matchers-ci.yml b/.github/workflows/hardhat-chai-matchers-ci.yml index 35e720e80a..34deb7b24d 100644 --- a/.github/workflows/hardhat-chai-matchers-ci.yml +++ b/.github/workflows/hardhat-chai-matchers-ci.yml @@ -67,7 +67,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-core-ci.yml b/.github/workflows/hardhat-core-ci.yml index 75a5c3ef26..b7699630c3 100644 --- a/.github/workflows/hardhat-core-ci.yml +++ b/.github/workflows/hardhat-core-ci.yml @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-ethers-ci.yml b/.github/workflows/hardhat-ethers-ci.yml index 457743ec24..f7dadcbd13 100644 --- a/.github/workflows/hardhat-ethers-ci.yml +++ b/.github/workflows/hardhat-ethers-ci.yml @@ -65,7 +65,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-foundry-ci.yml b/.github/workflows/hardhat-foundry-ci.yml index f52e14fc2b..1809eda2ac 100644 --- a/.github/workflows/hardhat-foundry-ci.yml +++ b/.github/workflows/hardhat-foundry-ci.yml @@ -67,7 +67,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-network-helpers-ci.yml b/.github/workflows/hardhat-network-helpers-ci.yml index c2bc454b93..6c5922aa4e 100644 --- a/.github/workflows/hardhat-network-helpers-ci.yml +++ b/.github/workflows/hardhat-network-helpers-ci.yml @@ -67,7 +67,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-network-tracing-all-solc-versions.yml b/.github/workflows/hardhat-network-tracing-all-solc-versions.yml index 0704096987..b2b2257b3d 100644 --- a/.github/workflows/hardhat-network-tracing-all-solc-versions.yml +++ b/.github/workflows/hardhat-network-tracing-all-solc-versions.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-network-tracing-ci.yml b/.github/workflows/hardhat-network-tracing-ci.yml index c5c8f07963..d8286209a2 100644 --- a/.github/workflows/hardhat-network-tracing-ci.yml +++ b/.github/workflows/hardhat-network-tracing-ci.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-shorthand-ci.yml b/.github/workflows/hardhat-shorthand-ci.yml index f07472ab0a..4584da6fa2 100644 --- a/.github/workflows/hardhat-shorthand-ci.yml +++ b/.github/workflows/hardhat-shorthand-ci.yml @@ -65,7 +65,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-solhint-ci.yml b/.github/workflows/hardhat-solhint-ci.yml index b548aae14e..bd4da9dc37 100644 --- a/.github/workflows/hardhat-solhint-ci.yml +++ b/.github/workflows/hardhat-solhint-ci.yml @@ -65,7 +65,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-solpp-ci.yml b/.github/workflows/hardhat-solpp-ci.yml index ac5f545700..d838e5f964 100644 --- a/.github/workflows/hardhat-solpp-ci.yml +++ b/.github/workflows/hardhat-solpp-ci.yml @@ -49,7 +49,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-toolbox-ci.yml b/.github/workflows/hardhat-toolbox-ci.yml index d10cddefc0..d87d249cd6 100644 --- a/.github/workflows/hardhat-toolbox-ci.yml +++ b/.github/workflows/hardhat-toolbox-ci.yml @@ -9,7 +9,7 @@ on: - "packages/hardhat-chai-matchers/**" - "packages/hardhat-network-helpers/**" - "packages/hardhat-ethers/**" - - "packages/hardhat-etherscan/**" + - "packages/hardhat-verify/**" - "packages/hardhat-common/**" - "config/**" pull_request: @@ -21,7 +21,7 @@ on: - "packages/hardhat-chai-matchers/**" - "packages/hardhat-network-helpers/**" - "packages/hardhat-ethers/**" - - "packages/hardhat-etherscan/**" + - "packages/hardhat-verify/**" - "packages/hardhat-common/**" - "config/**" @@ -73,7 +73,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-truffle4-ci.yml b/.github/workflows/hardhat-truffle4-ci.yml index 46b7d5d003..871a3040ec 100644 --- a/.github/workflows/hardhat-truffle4-ci.yml +++ b/.github/workflows/hardhat-truffle4-ci.yml @@ -67,7 +67,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-truffle5-ci.yml b/.github/workflows/hardhat-truffle5-ci.yml index 70e873cc04..b013591b4d 100644 --- a/.github/workflows/hardhat-truffle5-ci.yml +++ b/.github/workflows/hardhat-truffle5-ci.yml @@ -67,7 +67,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-verify-ci.yml b/.github/workflows/hardhat-verify-ci.yml index a16b39cdf1..34f0cc51c7 100644 --- a/.github/workflows/hardhat-verify-ci.yml +++ b/.github/workflows/hardhat-verify-ci.yml @@ -65,7 +65,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-vyper-ci.yml b/.github/workflows/hardhat-vyper-ci.yml index 528e027713..736f51031e 100644 --- a/.github/workflows/hardhat-vyper-ci.yml +++ b/.github/workflows/hardhat-vyper-ci.yml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-web3-ci.yml b/.github/workflows/hardhat-web3-ci.yml index 33a8ead269..f01d13f3ac 100644 --- a/.github/workflows/hardhat-web3-ci.yml +++ b/.github/workflows/hardhat-web3-ci.yml @@ -65,7 +65,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/hardhat-web3-legacy-ci.yml b/.github/workflows/hardhat-web3-legacy-ci.yml index 221110aa34..79af94c5be 100644 --- a/.github/workflows/hardhat-web3-legacy-ci.yml +++ b/.github/workflows/hardhat-web3-legacy-ci.yml @@ -65,7 +65,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [14, 16, 18.15] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index eead734896..f7850899c6 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -2,7 +2,9 @@ name: Pre-release tests on: push: - branches: [pre-release-testing-branch] + branches: + - pre-release-testing-branch + - changeset-release/main workflow_dispatch: concurrency: diff --git a/.github/workflows/test-recent-mainnet-block.yml b/.github/workflows/test-recent-mainnet-block.yml index 2d22215aea..209d2f18a2 100644 --- a/.github/workflows/test-recent-mainnet-block.yml +++ b/.github/workflows/test-recent-mainnet-block.yml @@ -24,7 +24,7 @@ jobs: run: yarn build - name: Run test env: - INFURA_URL: ${{ secrets.INFURA_URL }} + ALCHEMY_URL: ${{ secrets.ALCHEMY_URL }} run: yarn ts-node scripts/test-recent-mainnet-block.ts - name: Notify failures if: failure() diff --git a/config/eslint/eslintrc.js b/config/eslint/eslintrc.js index 0d563e5981..f33ff32f2c 100644 --- a/config/eslint/eslintrc.js +++ b/config/eslint/eslintrc.js @@ -191,7 +191,7 @@ module.exports = { "no-cond-assign": "error", "no-debugger": "error", "no-duplicate-case": "error", - "no-duplicate-imports": "error", + "@typescript-eslint/no-duplicate-imports": "error", "no-eval": "error", "no-extra-bind": "error", "no-new-func": "error", diff --git a/docs/src/content/hardhat-chai-matchers/docs/overview.md b/docs/src/content/hardhat-chai-matchers/docs/overview.md index a61f57b0c8..131ecd0500 100644 --- a/docs/src/content/hardhat-chai-matchers/docs/overview.md +++ b/docs/src/content/hardhat-chai-matchers/docs/overview.md @@ -90,8 +90,8 @@ This package provides the predicates `anyValue` and `anyUint`, but you can easil ```js -function isEven(x: BigNumber): boolean { - return x.mod(2).isZero(); +function isEven(x: bigint): boolean { + return x % 2n === 0n; } await expect(contract.emitUint(2)) @@ -175,7 +175,7 @@ This package enhances the standard numerical equality matchers (`equal`, `above` expect(await token.balanceOf(someAddress)).to.equal(1); ``` -These matchers support not just [ethers' `BigNumber`](https://docs.ethers.io/v5/single-page/#/v5/api/utils/bignumber/) and the native JavaScript `Number`, but also [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt), [bn.js](https://github.com/indutny/bn.js/), and [bignumber.js](https://github.com/MikeMcl/bignumber.js/). +These matchers support not just the native JavaScript `Number`, but also [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt), [bn.js](https://github.com/indutny/bn.js/), and [bignumber.js](https://github.com/MikeMcl/bignumber.js/). ### Balance Changes diff --git a/docs/src/content/hardhat-chai-matchers/docs/reference.md b/docs/src/content/hardhat-chai-matchers/docs/reference.md index 42f99a9c64..ee1dd7d2d9 100644 --- a/docs/src/content/hardhat-chai-matchers/docs/reference.md +++ b/docs/src/content/hardhat-chai-matchers/docs/reference.md @@ -10,13 +10,12 @@ When `@nomicfoundation/hardhat-chai-matchers` is used, equality comparisons of n expect(await token.totalSupply()).to.equal(1_000_000); ``` -will work. These assertions don't normally work because the value returned by `totalSupply()` is an [ethers BigNumber](https://docs.ethers.io/v5/single-page/#/v5/api/utils/bignumber/), and an instance of a `BigNumber` will always be different than a plain number. +will work. These assertions don't normally work because the value returned by `totalSupply()` is a [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt), and a bigint value will always be different than a plain number. The supported types are: - Plain javascript numbers - [BigInts](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) -- [Ethers BigNumbers](https://docs.ethers.io/v5/single-page/#/v5/api/utils/bignumber/) - [`bn.js`](https://github.com/indutny/bn.js/) instances - [`bignumber.js`](https://github.com/MikeMcl/bignumber.js/) instances @@ -226,8 +225,8 @@ await expect(factory.create(9999)) Predicates are just functions that return true if the value is correct, and return false if it isn't, so you can create your own predicates: ```ts -function isEven(x: BigNumber): boolean { - return x.mod(2).isZero(); +function isEven(x: bigint): boolean { + return x % 2n === 0n; } await expect(token.transfer(100)).to.emit(token, "Transfer").withArgs(isEven); diff --git a/docs/src/content/hardhat-network/docs/guides/forking-other-networks.md b/docs/src/content/hardhat-network/docs/guides/forking-other-networks.md index e987e66e87..af7402898f 100644 --- a/docs/src/content/hardhat-network/docs/guides/forking-other-networks.md +++ b/docs/src/content/hardhat-network/docs/guides/forking-other-networks.md @@ -167,7 +167,7 @@ await impersonatedSigner.sendTransaction(...); Alternatively, you can use the [`impersonateAccount`]() helper and then obtain the signer for that address: ```js -const helpers = require("@nomicfoundation/hardhat-network-helpers"); +const helpers = require("@nomicfoundation/hardhat-toolbox/network-helpers"); const address = "0x1234567890123456789012345678901234567890"; await helpers.impersonateAccount(address); @@ -183,7 +183,7 @@ Once you've got a local instance of the mainnet chain state, setting that state You can reset the network with the [`reset`]() network helper: ```js -const helpers = require("@nomicfoundation/hardhat-network-helpers"); +const helpers = require("@nomicfoundation/hardhat-toolbox/network-helpers"); await helpers.reset(url, blockNumber); ``` diff --git a/docs/src/content/hardhat-runner/docs/advanced/create-task.md b/docs/src/content/hardhat-runner/docs/advanced/create-task.md index 33c7972df1..f2b571d995 100644 --- a/docs/src/content/hardhat-runner/docs/advanced/create-task.md +++ b/docs/src/content/hardhat-runner/docs/advanced/create-task.md @@ -48,7 +48,7 @@ Let’s go through the process of creating one to interact with a smart contract Tasks in Hardhat are asynchronous JavaScript functions that get access to the [Hardhat Runtime Environment](../advanced/hardhat-runtime-environment.md), which exposes its configuration and parameters, as well as programmatic access to other tasks and any plugin objects that may have been injected. -For our example, we will use the [`@nomicfoundation/hardhat-toolbox`](/hardhat-runner/plugins/nomicfoundation-hardhat-toolbox), which includes the [ethers.js](https://docs.ethers.io/v5/) library to interact with our contracts. +For our example, we will use the [`@nomicfoundation/hardhat-toolbox`](/hardhat-runner/plugins/nomicfoundation-hardhat-toolbox), which includes the [ethers.js](https://docs.ethers.org/v6/) library to interact with our contracts. ::::tabsgroup{options="npm 7+,npm 6,yarn"} @@ -63,7 +63,7 @@ npm install --save-dev @nomicfoundation/hardhat-toolbox :::tab{value="npm 6"} ``` -npm install --save-dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomiclabs/hardhat-ethers @nomiclabs/hardhat-etherscan chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v5 @ethersproject/abi @ethersproject/providers +npm install --save-dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomicfoundation/hardhat-ethers @nomicfoundation/hardhat-verify chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v6 ``` ::: @@ -71,7 +71,7 @@ npm install --save-dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat :::tab{value="yarn"} ``` -yarn add --dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomiclabs/hardhat-ethers @nomiclabs/hardhat-etherscan chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v5 @ethersproject/abi @ethersproject/providers +yarn add --dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomicfoundation/hardhat-ethers @nomicfoundation/hardhat-verify chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v6 ``` ::: diff --git a/docs/src/content/hardhat-runner/docs/advanced/hardhat-runtime-environment.md b/docs/src/content/hardhat-runner/docs/advanced/hardhat-runtime-environment.md index 6f9d550920..9902915702 100644 --- a/docs/src/content/hardhat-runner/docs/advanced/hardhat-runtime-environment.md +++ b/docs/src/content/hardhat-runner/docs/advanced/hardhat-runtime-environment.md @@ -51,7 +51,7 @@ This way, tests written for Hardhat are just normal Mocha tests. This enables yo The HRE only provides the core functionality that users and plugin developers need to start building on top of Hardhat. Using it to interface directly with Ethereum in your project can be somewhat harder than expected. -Everything gets easier when you use higher-level libraries, like [Ethers.js](https://docs.ethers.io/) or [ethereum-waffle](https://www.npmjs.com/package/ethereum-waffle), but these libraries need some initialization to work, and that could get repetitive. +Everything gets easier when you use higher-level libraries, like [Ethers.js](https://docs.ethers.org/v6/) or [ethereum-waffle](https://www.npmjs.com/package/ethereum-waffle), but these libraries need some initialization to work, and that could get repetitive. Hardhat lets you hook into the HRE construction, and extend it with new functionality. This way, you only have to initialize everything once, and your new features or libraries will be available everywhere the HRE is used. diff --git a/docs/src/content/hardhat-runner/docs/getting-started/index.md b/docs/src/content/hardhat-runner/docs/getting-started/index.md index b1bc062deb..a17a00ab47 100644 --- a/docs/src/content/hardhat-runner/docs/getting-started/index.md +++ b/docs/src/content/hardhat-runner/docs/getting-started/index.md @@ -150,7 +150,7 @@ If you created a TypeScript project, this task will also generate TypeScript bin ### Testing your contracts -Your project comes with tests that use [Mocha](https://mochajs.org), [Chai](https://www.chaijs.com), and [Ethers.js](https://docs.ethers.io/v5). +Your project comes with tests that use [Mocha](https://mochajs.org), [Chai](https://www.chaijs.com), and [Ethers.js](https://docs.ethers.org/v6/). If you take a look in the `test/` folder, you'll see a test file: @@ -178,7 +178,7 @@ You can run your tests with `npx hardhat test`: ``` $ npx hardhat test -Generating typings for: 2 artifacts in dir: typechain-types for target: ethers-v5 +Generating typings for: 2 artifacts in dir: typechain-types for target: ethers-v6 Successfully generated 6 typings! Compiled 2 Solidity files successfully diff --git a/docs/src/content/hardhat-runner/docs/guides/hardhat-console.md b/docs/src/content/hardhat-runner/docs/guides/hardhat-console.md index 7bf0def7a4..451a30a51e 100644 --- a/docs/src/content/hardhat-runner/docs/guides/hardhat-console.md +++ b/docs/src/content/hardhat-runner/docs/guides/hardhat-console.md @@ -25,7 +25,7 @@ For example, you'll have access in the global scope to the `config` object: > ``` -And if you followed the [Getting started guide](../getting-started) or installed `@nomiclabs/hardhat-ethers`, the `ethers` object: +And if you followed the [Getting started guide](../getting-started) or installed `@nomicfoundation/hardhat-ethers`, the `ethers` object: ``` > ethers diff --git a/docs/src/content/hardhat-runner/docs/guides/migrating-from-hardhat-waffle.md b/docs/src/content/hardhat-runner/docs/guides/migrating-from-hardhat-waffle.md index 5d2a12c130..b4edec853c 100644 --- a/docs/src/content/hardhat-runner/docs/guides/migrating-from-hardhat-waffle.md +++ b/docs/src/content/hardhat-runner/docs/guides/migrating-from-hardhat-waffle.md @@ -55,7 +55,7 @@ Follow these steps to migrate your project to Hardhat Toolbox. :::tab{value="npm 6"} ``` - npm install --save-dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomiclabs/hardhat-ethers @nomiclabs/hardhat-etherscan chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v5 @ethersproject/abi @ethersproject/providers + npm install --save-dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomicfoundation/hardhat-ethers @nomicfoundation/hardhat-verify chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v6 ``` ::: @@ -63,7 +63,7 @@ Follow these steps to migrate your project to Hardhat Toolbox. :::tab{value="yarn"} ``` - yarn add --dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomiclabs/hardhat-ethers @nomiclabs/hardhat-etherscan chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v5 @ethersproject/abi @ethersproject/providers + yarn add --dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers@1 @nomiclabs/hardhat-ethers @nomiclabs/hardhat-etherscan chai ethers@5 hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v6 ``` ::: @@ -96,8 +96,8 @@ Follow these steps to migrate your project to Hardhat Toolbox. Adding the Toolbox will make many other imports redundant, so you can remove any of these if you want: - - `@nomiclabs/hardhat-ethers` - - `@nomiclabs/hardhat-etherscan` + - `@nomicfoundation/hardhat-ethers` + - `@nomicfoundation/hardhat-verify` - `hardhat-gas-reporter` - `solidity-coverage` - `@typechain/hardhat` diff --git a/docs/src/content/hardhat-runner/docs/guides/project-setup.md b/docs/src/content/hardhat-runner/docs/guides/project-setup.md index 0751641054..4ae4c45da1 100644 --- a/docs/src/content/hardhat-runner/docs/guides/project-setup.md +++ b/docs/src/content/hardhat-runner/docs/guides/project-setup.md @@ -128,7 +128,7 @@ When it comes to testing your contracts, the sample project comes with some usef - The built-in [Hardhat Network](/hardhat-network/docs) as the development network to test on, along with the [Hardhat Network Helpers](/hardhat-network-helpers) library to manipulate this network. - [Mocha](https://mochajs.org/) as the test runner, [Chai](https://chaijs.com/) as the assertion library, and the [Hardhat Chai Matchers](/hardhat-chai-matchers) to extend Chai with contracts-related functionality. -- The [`ethers.js`](https://docs.ethers.io/v5/) library to interact with the network and with contracts. +- The [`ethers.js`](https://docs.ethers.org/v6/) library to interact with the network and with contracts. As well as other useful plugins. You can learn more about this in the [Testing contracts guide](./test-contracts.md). diff --git a/docs/src/content/hardhat-runner/docs/guides/test-contracts.md b/docs/src/content/hardhat-runner/docs/guides/test-contracts.md index d7e4e93552..f967343d76 100644 --- a/docs/src/content/hardhat-runner/docs/guides/test-contracts.md +++ b/docs/src/content/hardhat-runner/docs/guides/test-contracts.md @@ -2,7 +2,7 @@ After [compiling your contracts](./compile-contracts.md), the next step is to write some tests to verify that they work as intended. -This guide explains our recommended approach for testing contracts in Hardhat. It relies on [ethers](https://docs.ethers.io/v5/) to connect to [Hardhat Network](/hardhat-network) and on [Mocha](https://mochajs.org/) and [Chai](https://www.chaijs.com/) for the tests. It also uses our custom [Chai matchers](/hardhat-chai-matchers) and our [Hardhat Network Helpers](/hardhat-network-helpers) to make it easier to write clean test code. These packages are part of the Hardhat Toolbox plugin; if you followed the previous guides, you should already have them installed. +This guide explains our recommended approach for testing contracts in Hardhat. It relies on [ethers](https://docs.ethers.org/v6/) to connect to [Hardhat Network](/hardhat-network) and on [Mocha](https://mochajs.org/) and [Chai](https://www.chaijs.com/) for the tests. It also uses our custom [Chai matchers](/hardhat-chai-matchers) and our [Hardhat Network Helpers](/hardhat-network-helpers) to make it easier to write clean test code. These packages are part of the Hardhat Toolbox plugin; if you followed the previous guides, you should already have them installed. While this is our recommended test setup, Hardhat is flexible: you can customize the approach or take a completely different path with other tools. @@ -27,7 +27,7 @@ For our first test we’ll deploy the `Lock` contract and assert that the unlock ```tsx import { expect } from "chai"; import hre from "hardhat"; -import { time } from "@nomicfoundation/hardhat-network-helpers"; +import { time } from "@nomicfoundation/hardhat-toolbox/network-helpers"; describe("Lock", function () { it("Should set the right unlockTime", async function () { @@ -37,8 +37,9 @@ describe("Lock", function () { // deploy a lock contract where funds can be withdrawn // one year in the future - const Lock = await hre.ethers.getContractFactory("Lock"); - const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); + const lock = await ethers.deployContract("Lock", [unlockTime], { + value: lockedAmount, + }); // assert that the value is correct expect(await lock.unlockTime()).to.equal(unlockTime); @@ -53,7 +54,7 @@ describe("Lock", function () { ```js const { expect } = require("chai"); const hre = require("hardhat"); -const { time } = require("@nomicfoundation/hardhat-network-helpers"); +const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers"); describe("Lock", function () { it("Should set the right unlockTime", async function () { @@ -63,8 +64,9 @@ describe("Lock", function () { // deploy a lock contract where funds can be withdrawn // one year in the future - const Lock = await hre.ethers.getContractFactory("Lock"); - const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); + const lock = await ethers.deployContract("Lock", [unlockTime], { + value: lockedAmount, + }); // assert that the value is correct expect(await lock.unlockTime()).to.equal(unlockTime); @@ -78,7 +80,7 @@ describe("Lock", function () { First we import the things we are going to use: the [`expect`](https://www.chaijs.com/api/bdd/) function from `chai` to write our assertions, the [Hardhat Runtime Environment](../advanced/hardhat-runtime-environment.md) (`hre`), and the [network helpers](/hardhat-network-helpers) to interact with the Hardhat Network. After that we use the `describe` and `it` functions, which are global Mocha functions used to describe and group your tests. (You can read more about Mocha [here](https://mochajs.org/#getting-started).) -The test itself is what’s inside the callback argument to the `it` function. First we set the values for the amount we want to lock (in [wei](https://ethereum.org/en/glossary/#wei)) and the unlock time. For the latter we use [`time.latest`](), a network helper that returns the timestamp of the last mined block. Then we deploy the contract itself: first we get a [`ContractFactory`](https://docs.ethers.io/v5/single-page/#/v5/api/contract/contract-factory/) for the `Lock` contract and then we deploy it, passing the unlock time as its constructor argument. We also pass an object with the transaction parameters. This is optional, but we'll use it to send some ETH by setting its `value` field. +The test itself is what’s inside the callback argument to the `it` function. First we set the values for the amount we want to lock (in [wei](https://ethereum.org/en/glossary/#wei)) and the unlock time. For the latter we use [`time.latest`](), a network helper that returns the timestamp of the last mined block. Then we deploy the contract itself: we call `ethers.deployContract` with the name of the contract we want to deploy and an array of constructor arguments that has the unlock time. We also pass an object with the transaction parameters. This is optional, but we'll use it to send some ETH by setting its `value` field. Finally, we check that the value returned by the `unlockTime()` [getter](https://docs.soliditylang.org/en/v0.8.13/contracts.html#getter-functions) in the contract matches the value that we used when we deployed it. Since all the functions on a contract are async, we have to use the `await` keyword to get its value; otherwise, we would be comparing a promise with a number and this would always fail. @@ -171,8 +173,9 @@ describe("Lock", function () { const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; unlockTime = (await helpers.time.latest()) + ONE_YEAR_IN_SECS; - const Lock = await ethers.getContractFactory("Lock"); - lock = await Lock.deploy(unlockTime, { value: lockedAmount }); + lock = await ethers.deployContract("Lock", [unlockTime], { + value: lockedAmount, + }); }); it("some test", async function () { @@ -195,8 +198,9 @@ describe("Lock", function () { const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; unlockTime = (await helpers.time.latest()) + ONE_YEAR_IN_SECS; - const Lock = await ethers.getContractFactory("Lock"); - lock = await Lock.deploy(unlockTime, { value: lockedAmount }); + lock = await ethers.deployContract("Lock", [unlockTime], { + value: lockedAmount, + }); }); it("some test", async function () { @@ -216,17 +220,25 @@ However, there are two problems with this approach: The `loadFixture` helper in the Hardhat Network Helpers fixes both of these problems. This helper receives a _fixture_, a function that sets up the chain to some desired state. The first time `loadFixture` is called, the fixture is executed. But the second time, instead of executing the fixture again, `loadFixture` will reset the state of the network to the point where it was right after the fixture was executed. This is faster, and it undoes any state changes done by the previous test. -This is how our two first tests look like when a fixture is used: +This is how our tests look like when a fixture is used: ```tsx +const { expect } = require("chai"); +const hre = require("hardhat"); +const { + loadFixture, + time, +} = require("@nomicfoundation/hardhat-toolbox/network-helpers"); + describe("Lock", function () { async function deployOneYearLockFixture() { const lockedAmount = 1_000_000_000; const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS; - const Lock = await ethers.getContractFactory("Lock"); - const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); + const lock = await ethers.deployContract("Lock", [unlockTime], { + value: lockedAmount, + }); return { lock, unlockTime, lockedAmount }; } @@ -243,6 +255,29 @@ describe("Lock", function () { await expect(lock.withdraw()).to.be.revertedWith("You can't withdraw yet"); }); + + it("Should transfer the funds to the owner", async function () { + const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture); + + await time.increaseTo(unlockTime); + + // this will throw if the transaction reverts + await lock.withdraw(); + }); + + it("Should revert with the right error if called from another account", async function () { + const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture); + + const [owner, otherAccount] = await ethers.getSigners(); + + // we increase the time of the chain to pass the first check + await time.increaseTo(unlockTime); + + // We use lock.connect() to send a transaction from another account + await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith( + "You aren't the owner" + ); + }); }); ``` diff --git a/docs/src/content/hardhat-runner/docs/other-guides/truffle-testing.md b/docs/src/content/hardhat-runner/docs/other-guides/truffle-testing.md index e291fd71f6..ec72f8ba5e 100644 --- a/docs/src/content/hardhat-runner/docs/other-guides/truffle-testing.md +++ b/docs/src/content/hardhat-runner/docs/other-guides/truffle-testing.md @@ -26,7 +26,7 @@ Now run `npx hardhat` inside your project folder and select `Create an empty har Let's now install the `Truffle` and `Web3.js` plugins, as well as `web3.js` itself. ``` -npm install --save-dev @nomiclabs/hardhat-truffle5 @nomiclabs/hardhat-web3 web3 +npm install --save-dev @nomiclabs/hardhat-truffle5 @nomiclabs/hardhat-web3 'web3@^1.0.0-beta.36' ``` Enable the Truffle 5 plugin on your Hardhat config file by requiring it: diff --git a/docs/src/content/hardhat-runner/docs/other-guides/waffle-testing.md b/docs/src/content/hardhat-runner/docs/other-guides/waffle-testing.md index 1c8521bd04..b89a4c76c9 100644 --- a/docs/src/content/hardhat-runner/docs/other-guides/waffle-testing.md +++ b/docs/src/content/hardhat-runner/docs/other-guides/waffle-testing.md @@ -8,7 +8,7 @@ Read [this guide](/hardhat-runner/docs/guides/test-contracts.md) to learn about Writing smart contract tests in Hardhat is done using JavaScript or TypeScript. -In this guide, we'll show you how to use [Ethers.js](https://docs.ethers.io/), a JavaScript library to interact with Ethereum, and [Waffle](https://getwaffle.io/) a simple smart contract testing library built on top of it. +In this guide, we'll show you how to use [Ethers.js](https://docs.ethers.org/v6/), a JavaScript library to interact with Ethereum, and [Waffle](https://getwaffle.io/) a simple smart contract testing library built on top of it. Let's see how to use it starting from an empty Hardhat project. @@ -59,7 +59,7 @@ npm install --save-dev chai @nomiclabs/hardhat-waffle :::tab{value="npm 6"} ``` -npm install --save-dev chai @nomiclabs/hardhat-waffle ethereum-waffle @nomiclabs/hardhat-ethers ethers +npm install --save-dev chai @nomiclabs/hardhat-waffle ethereum-waffle @nomiclabs/hardhat-ethers ethers@5 ``` ::: @@ -67,7 +67,7 @@ npm install --save-dev chai @nomiclabs/hardhat-waffle ethereum-waffle @nomiclabs :::tab{value="yarn"} ``` -yarn add --dev chai @nomiclabs/hardhat-waffle ethereum-waffle @nomiclabs/hardhat-ethers ethers +yarn add --dev chai @nomiclabs/hardhat-waffle ethereum-waffle @nomiclabs/hardhat-ethers ethers@5 ``` ::: @@ -229,7 +229,7 @@ A `Signer` in Ethers.js is an object that represents an Ethereum account. It's u :::tip -To learn more about `Signer`, you can look at the [Signers documentation](https://docs.ethers.io/v5/api/signer/#Wallet). +To learn more about `Signer`, you can look at the [Signers documentation](https://docs.ethers.org/v6/api/providers/#Signer). ::: diff --git a/docs/src/content/hardhat-runner/plugins/_dirinfo.yaml b/docs/src/content/hardhat-runner/plugins/_dirinfo.yaml index 1728c8daff..05dfea2621 100644 --- a/docs/src/content/hardhat-runner/plugins/_dirinfo.yaml +++ b/docs/src/content/hardhat-runner/plugins/_dirinfo.yaml @@ -3,7 +3,7 @@ section-title: Plugins order: - "@nomicfoundation/hardhat-toolbox" - "@nomicfoundation/hardhat-chai-matchers" - - "@nomiclabs/hardhat-ethers" + - "@nomicfoundation/hardhat-ethers" - "@nomicfoundation/hardhat-verify" - "@nomicfoundation/hardhat-foundry" - "@nomiclabs/hardhat-vyper" diff --git a/docs/src/content/hardhat-runner/plugins/plugins.ts b/docs/src/content/hardhat-runner/plugins/plugins.ts index 435cf481ea..370d9bc5cc 100644 --- a/docs/src/content/hardhat-runner/plugins/plugins.ts +++ b/docs/src/content/hardhat-runner/plugins/plugins.ts @@ -773,6 +773,15 @@ const communityPlugins: IPlugin[] = [ "Enable project-specific features inside Truffle Dashboard, including advanced calldata decoding and more", tags: ["truffle-dashboard", "transaction", "signing", "decoding"], }, + { + name: "hardhat-w3f", + author: "Gelato Network", + npmPackage: "@gelatonetwork/web3-functions-sdk", + authorUrl: "https://github.com/gelatodigital/web3-functions-sdk", + description: + "The hardhat-w3f plugin allows builders to build & run Web3 Functions connecting smart off-chain data with smart contracts ", + tags: ["Gelato", "w3f", "offchain", "functions"], + }, ]; const officialPlugins: IPlugin[] = [ @@ -791,7 +800,7 @@ const officialPlugins: IPlugin[] = [ tags: ["Chai", "Testing"], }, { - name: "@nomiclabs/hardhat-ethers", + name: "@nomicfoundation/hardhat-ethers", author: "Nomic Foundation", authorUrl: "https://twitter.com/NomicFoundation", description: "Injects ethers.js into the Hardhat Runtime Environment", diff --git a/docs/src/content/tutorial/creating-a-new-hardhat-project.md b/docs/src/content/tutorial/creating-a-new-hardhat-project.md index 8f6d0d39a5..b269436fe1 100644 --- a/docs/src/content/tutorial/creating-a-new-hardhat-project.md +++ b/docs/src/content/tutorial/creating-a-new-hardhat-project.md @@ -142,7 +142,7 @@ npm install --save-dev @nomicfoundation/hardhat-toolbox :::tab{value="npm 6"} ``` -npm install --save-dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomiclabs/hardhat-ethers @nomiclabs/hardhat-etherscan chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v5 @ethersproject/abi @ethersproject/providers +npm install --save-dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomicfoundation/hardhat-ethers @nomicfoundation/hardhat-verify chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v6 ``` ::: @@ -150,7 +150,7 @@ npm install --save-dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat :::tab{value=yarn} ``` -yarn add --dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomiclabs/hardhat-ethers @nomiclabs/hardhat-etherscan chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v5 @ethersproject/abi @ethersproject/providers +yarn add --dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomicfoundation/hardhat-ethers @nomicfoundation/hardhat-verify chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v6 ``` ::: diff --git a/docs/src/content/tutorial/deploying-to-a-live-network.md b/docs/src/content/tutorial/deploying-to-a-live-network.md index 088f044b51..50a350b80c 100644 --- a/docs/src/content/tutorial/deploying-to-a-live-network.md +++ b/docs/src/content/tutorial/deploying-to-a-live-network.md @@ -6,7 +6,7 @@ The "mainnet" Ethereum network deals with real money, but there are separate "te At the software level, deploying to a testnet is the same as deploying to mainnet. The only difference is which network you connect to. Let's look into what the code to deploy your contracts using ethers.js would look like. -The main concepts used are `Signer`, `ContractFactory` and `Contract` which we explained back in the [testing](testing-contracts.md) section. There's nothing new that needs to be done when compared to testing, given that when you're testing your contracts you're _actually_ making a deployment to your development network. This makes the code very similar, or even the same. +The main concepts used are `Signer` and `Contract` which we explained back in the [testing](testing-contracts.md) section. There's nothing new that needs to be done when compared to testing, given that when you're testing your contracts you're _actually_ making a deployment to your development network. This makes the code very similar, or even the same. Let's create a new directory `scripts` inside the project root's directory, and paste the following into a `deploy.js` file in that directory: @@ -16,12 +16,9 @@ async function main() { console.log("Deploying contracts with the account:", deployer.address); - console.log("Account balance:", (await deployer.getBalance()).toString()); + const token = await ethers.deployContract("Token"); - const Token = await ethers.getContractFactory("Token"); - const token = await Token.deploy(); - - console.log("Token address:", token.address); + console.log("Token address:", await token.getAddress()); } main() @@ -43,7 +40,6 @@ With our current configuration, running it without the `--network` parameter wou ``` $ npx hardhat run scripts/deploy.js Deploying contracts with the account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Account balance: 10000000000000000000000 Token address: 0x5FbDB2315678afecb367f032d93F642f64180aa3 ``` @@ -121,6 +117,7 @@ To deploy on Sepolia you need to send some Sepolia ether to the address that's g - [Alchemy Sepolia Faucet](https://sepoliafaucet.com/) - [Coinbase Sepolia Faucet](https://coinbase.com/faucets/ethereum-sepolia-faucet) (only works if you are using the Coinbase Wallet) +- [Infura Sepolia Faucet](https://www.infura.io/faucet/sepolia) You'll have to change your wallet's network to Sepolia before transacting. diff --git a/docs/src/content/tutorial/final-thoughts.md b/docs/src/content/tutorial/final-thoughts.md index 419b85a365..bda100dde5 100644 --- a/docs/src/content/tutorial/final-thoughts.md +++ b/docs/src/content/tutorial/final-thoughts.md @@ -8,7 +8,7 @@ Here are some links you might find useful throughout your journey: - [Hardhat's documentation site](/docs/) - [Hardhat Toolbox's documentation](/hardhat-runner/plugins/nomicfoundation-hardhat-toolbox) - [Hardhat Support Discord server](/discord) -- [Ethers.js Documentation](https://docs.ethers.io/) +- [Ethers.js Documentation](https://docs.ethers.org/v6/) - [Mocha Documentation](https://mochajs.org/) - [Chai Documentation](https://www.chaijs.com/) - [Alchemy's smart contract tutorial](https://docs.alchemy.com/docs/hello-world-smart-contract) to also learn how to use Metamask and Solidity as well as an RPC endpoint like the one that Alchemy provides. diff --git a/docs/src/content/tutorial/testing-contracts.md b/docs/src/content/tutorial/testing-contracts.md index 4d87502e75..16ecd2a999 100644 --- a/docs/src/content/tutorial/testing-contracts.md +++ b/docs/src/content/tutorial/testing-contracts.md @@ -4,7 +4,7 @@ Writing automated tests when building smart contracts is of crucial importance, To test our contract, we are going to use Hardhat Network, a local Ethereum network designed for development. It comes built-in with Hardhat, and it's used as the default network. You don't need to setup anything to use it. -In our tests we're going to use [ethers.js](https://docs.ethers.io/v5/) to interact with the Ethereum contract we built in the previous section, and we'll use [Mocha](https://mochajs.org/) as our test runner. +In our tests we're going to use [ethers.js](https://docs.ethers.org/v6/) to interact with the Ethereum contract we built in the previous section, and we'll use [Mocha](https://mochajs.org/) as our test runner. ## Writing tests @@ -19,9 +19,7 @@ describe("Token contract", function () { it("Deployment should assign the total supply of tokens to the owner", async function () { const [owner] = await ethers.getSigners(); - const Token = await ethers.getContractFactory("Token"); - - const hardhatToken = await Token.deploy(); + const hardhatToken = await ethers.deployContract("Token"); const ownerBalance = await hardhatToken.balanceOf(owner.address); expect(await hardhatToken.totalSupply()).to.equal(ownerBalance); @@ -57,21 +55,15 @@ const { ethers } = require("hardhat"); :::tip -To learn more about `Signer`, you can look at the [Signers documentation](https://docs.ethers.io/v5/api/signer/). +To learn more about `Signer`, you can look at the [Signers documentation](https://docs.ethers.org/v6/api/providers/#Signer). ::: ```js -const Token = await ethers.getContractFactory("Token"); +const hardhatToken = await ethers.deployContract("Token"); ``` -A `ContractFactory` in ethers.js is an abstraction used to deploy new smart contracts, so `Token` here is a factory for instances of our token contract. - -```js -const hardhatToken = await Token.deploy(); -``` - -Calling `deploy()` on a `ContractFactory` will start the deployment, and return a `Promise` that resolves to a `Contract`. This is the object that has a method for each of your smart contract functions. +Calling `ethers.deployContract("Token")` will start the deployment of our token contract, and return a `Promise` that resolves to a `Contract`. This is the object that has a method for each of your smart contract functions. ```js const ownerBalance = await hardhatToken.balanceOf(owner.address); @@ -79,7 +71,7 @@ const ownerBalance = await hardhatToken.balanceOf(owner.address); Once the contract is deployed, we can call our contract methods on `hardhatToken`. Here we get the balance of the owner account by calling the contract's `balanceOf()` method. -Recall that the account that deploys the token gets its entire supply. By default, `ContractFactory` and `Contract` instances are connected to the first signer. This means that the account in the `owner` variable executed the deployment, and `balanceOf()` should return the entire supply amount. +Recall that the account that deploys the token gets its entire supply. By default, `Contract` instances are connected to the first signer. This means that the account in the `owner` variable executed the deployment, and `balanceOf()` should return the entire supply amount. ```js expect(await hardhatToken.totalSupply()).to.equal(ownerBalance); @@ -93,7 +85,7 @@ To do this we're using [Chai](https://www.chaijs.com/) which is a popular JavaSc If you need to test your code by sending a transaction from an account (or `Signer` in ethers.js terminology) other than the default one, you can use the `connect()` method on your ethers.js `Contract` object to connect it to a different account, like this: -```js{18} +```js{16} const { expect } = require("chai"); describe("Token contract", function () { @@ -102,9 +94,7 @@ describe("Token contract", function () { it("Should transfer tokens between accounts", async function() { const [owner, addr1, addr2] = await ethers.getSigners(); - const Token = await ethers.getContractFactory("Token"); - - const hardhatToken = await Token.deploy(); + const hardhatToken = await ethers.deployContract("Token"); // Transfer 50 tokens from owner to addr1 await hardhatToken.transfer(addr1.address, 50); @@ -124,20 +114,19 @@ The two tests that we wrote begin with their setup, which in this case means dep You can avoid code duplication and improve the performance of your test suite by using **fixtures**. A fixture is a setup function that is run only the first time it's invoked. On subsequent invocations, instead of re-running it, Hardhat will reset the state of the network to what it was at the point after the fixture was initially executed. ```js -const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers"); +const { + loadFixture, +} = require("@nomicfoundation/hardhat-toolbox/network-helpers"); const { expect } = require("chai"); describe("Token contract", function () { async function deployTokenFixture() { - const Token = await ethers.getContractFactory("Token"); const [owner, addr1, addr2] = await ethers.getSigners(); - const hardhatToken = await Token.deploy(); - - await hardhatToken.deployed(); + const hardhatToken = await ethers.deployContract("Token"); // Fixtures can return anything you consider useful for your tests - return { Token, hardhatToken, owner, addr1, addr2 }; + return { hardhatToken, owner, addr1, addr2 }; } it("Should assign the total supply of tokens to the owner", async function () { @@ -184,7 +173,9 @@ const { expect } = require("chai"); // We use `loadFixture` to share common setups (or fixtures) between tests. // Using this simplifies your tests and makes them run faster, by taking // advantage of Hardhat Network's snapshot functionality. -const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers"); +const { + loadFixture, +} = require("@nomicfoundation/hardhat-toolbox/network-helpers"); // `describe` is a Mocha function that allows you to organize your tests. // Having your tests organized makes debugging them easier. All Mocha @@ -198,19 +189,18 @@ describe("Token contract", function () { // loadFixture to run this setup once, snapshot that state, and reset Hardhat // Network to that snapshot in every test. async function deployTokenFixture() { - // Get the ContractFactory and Signers here. - const Token = await ethers.getContractFactory("Token"); + // Get the Signers here. const [owner, addr1, addr2] = await ethers.getSigners(); - // To deploy our contract, we just have to call Token.deploy() and await - // its deployed() method, which happens once its transaction has been + // To deploy our contract, we just have to call ethers.deployContract and await + // its waitForDeployment() method, which happens once its transaction has been // mined. - const hardhatToken = await Token.deploy(); + const hardhatToken = await ethers.deployContract("Token"); - await hardhatToken.deployed(); + await hardhatToken.waitForDeployment(); // Fixtures can return anything you consider useful for your tests - return { Token, hardhatToken, owner, addr1, addr2 }; + return { hardhatToken, owner, addr1, addr2 }; } // You can nest describe calls to create subsections. diff --git a/packages/common/empty-hardhat-project/hardhat.config.js b/packages/common/empty-hardhat-project/hardhat.config.js new file mode 100644 index 0000000000..aa3527a6b0 --- /dev/null +++ b/packages/common/empty-hardhat-project/hardhat.config.js @@ -0,0 +1,3 @@ +module.exports = { + solidity: "0.8.19", +}; diff --git a/packages/common/run-with-hardhat.js b/packages/common/run-with-hardhat.js new file mode 100644 index 0000000000..dcdf3b4b6f --- /dev/null +++ b/packages/common/run-with-hardhat.js @@ -0,0 +1,79 @@ +const { fork } = require("child_process"); +const path = require("path"); +let hardhatNodeProcess; + +/** + * Ensure hardhat node is running, for tests that require it. + */ +before(async () => { + console.log("\n### Starting hardhat node instance ###"); + + const pathToCli = path.resolve( + __dirname, + "..", + "hardhat-core", + "internal", + "cli", + "cli" + ); + const pathToEmptyProject = path.resolve(__dirname, "empty-hardhat-project"); + + hardhatNodeProcess = fork(pathToCli, ["node"], { + cwd: pathToEmptyProject, + env: { HARDHAT_EXPERIMENTAL_ALLOW_NON_LOCAL_INSTALLATION: "true" }, + stdio: "pipe", + }); + + return new Promise((resolve, reject) => { + let stdout = ""; + let stderr = ""; + + hardhatNodeProcess.stdout.on("data", (data) => { + stdout += data.toString(); + if ( + data + .toString() + .includes("Started HTTP and WebSocket JSON-RPC server at") + ) { + resolve(); + } + }); + + hardhatNodeProcess.stderr.on("data", (data) => { + stderr += data.toString(); + }); + + const buildErrorMessage = () => { + return `There was a problem running hardhat node. + +stdout: +${stdout} + +stderr: +${stderr}`; + }; + + hardhatNodeProcess.on("error", () => { + reject(new Error(buildErrorMessage())); + }); + + hardhatNodeProcess.on("exit", (statusCode) => { + if (statusCode === 0) { + return; + } + + reject(new Error(buildErrorMessage())); + }); + }); +}); + +/** + * Cleanup the process running hardhat node + */ +after(async () => { + if (hardhatNodeProcess === undefined) { + return; + } + hardhatNodeProcess.kill(); + console.log("\n### Stopped hardhat node instance ###"); +}); diff --git a/packages/hardhat-chai-matchers/.eslintrc.js b/packages/hardhat-chai-matchers/.eslintrc.js index 44ed8ed6d5..7f3a838a47 100644 --- a/packages/hardhat-chai-matchers/.eslintrc.js +++ b/packages/hardhat-chai-matchers/.eslintrc.js @@ -4,4 +4,7 @@ module.exports = { project: `${__dirname}/src/tsconfig.json`, sourceType: "module", }, + rules: { + "@typescript-eslint/no-non-null-assertion": "error" + } }; diff --git a/packages/hardhat-chai-matchers/CHANGELOG.md b/packages/hardhat-chai-matchers/CHANGELOG.md index 5da3f4d0ba..eed11a2871 100644 --- a/packages/hardhat-chai-matchers/CHANGELOG.md +++ b/packages/hardhat-chai-matchers/CHANGELOG.md @@ -1,5 +1,21 @@ # @nomicfoundation/hardhat-chai-matchers +## 2.0.1 + +### Patch Changes + +- 70c2ccf12: Removed an unnecessary dependency + +## 2.0.0 + +### Major Changes + +- 523235b83: Added support for ethers v6 + +### Patch Changes + +- 06c4797a7: Fixed a problem when `.withArgs` was used with arrays with different length + ## 1.0.6 ### Patch Changes diff --git a/packages/hardhat-chai-matchers/LICENSE b/packages/hardhat-chai-matchers/LICENSE index 3b8858c555..3b7e8c7eab 100644 --- a/packages/hardhat-chai-matchers/LICENSE +++ b/packages/hardhat-chai-matchers/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-chai-matchers/README.md b/packages/hardhat-chai-matchers/README.md index 2cbf130d8d..59e6d1a0d8 100644 --- a/packages/hardhat-chai-matchers/README.md +++ b/packages/hardhat-chai-matchers/README.md @@ -17,13 +17,13 @@ npm install --save-dev @nomicfoundation/hardhat-chai-matchers If you are using an older version of npm, you'll also need to install all the packages used by the plugin. ```bash -npm install --save-dev @nomicfoundation/hardhat-chai-matchers chai @nomiclabs/hardhat-ethers ethers +npm install --save-dev @nomicfoundation/hardhat-chai-matchers chai @nomicfoundation/hardhat-ethers ethers ``` That's also the case if you are using yarn: ```bash -yarn add --dev @nomicfoundation/hardhat-chai-matchers chai @nomiclabs/hardhat-ethers ethers +yarn add --dev @nomicfoundation/hardhat-chai-matchers chai @nomicfoundation/hardhat-ethers ethers ``` ### Usage diff --git a/packages/hardhat-chai-matchers/package.json b/packages/hardhat-chai-matchers/package.json index a3732936d3..e0679c0456 100644 --- a/packages/hardhat-chai-matchers/package.json +++ b/packages/hardhat-chai-matchers/package.json @@ -1,6 +1,6 @@ { "name": "@nomicfoundation/hardhat-chai-matchers", - "version": "1.0.6", + "version": "2.0.1", "description": "Hardhat utils for testing", "homepage": "https://github.com/nomicfoundation/hardhat/tree/main/packages/hardhat-chai-matchers", "repository": "github:nomicfoundation/hardhat", @@ -19,7 +19,7 @@ "lint:fix": "yarn prettier --write && yarn eslint --fix", "eslint": "eslint 'src/**/*.ts' 'test/**/*.ts'", "prettier": "prettier \"**/*.{js,md,json}\"", - "test": "mocha --recursive \"test/**/*.ts\" --exit --reporter dot", + "test": "mocha --recursive \"test/**/*.ts\" --exit", "test:ci": "yarn test && node scripts/check-subpath-exports.js", "build": "tsc --build .", "prepublishOnly": "yarn build", @@ -37,7 +37,7 @@ "README.md" ], "devDependencies": { - "@nomiclabs/hardhat-ethers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", "@types/bn.js": "^5.1.0", "@types/chai": "^4.2.0", "@types/mocha": ">=9.1.0", @@ -52,7 +52,7 @@ "eslint-plugin-import": "2.24.1", "eslint-plugin-no-only-tests": "3.0.0", "eslint-plugin-prettier": "3.4.0", - "ethers": "^5.0.0", + "ethers": "^6.1.0", "get-port": "^5.1.1", "hardhat": "^2.9.4", "mocha": "^10.0.0", @@ -62,13 +62,12 @@ "typescript": "~4.7.4" }, "peerDependencies": { - "@nomiclabs/hardhat-ethers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", "chai": "^4.2.0", - "ethers": "^5.0.0", + "ethers": "^6.1.0", "hardhat": "^2.9.4" }, "dependencies": { - "@ethersproject/abi": "^5.1.2", "@types/chai-as-promised": "^7.1.3", "chai-as-promised": "^7.1.1", "deep-eql": "^4.0.1", diff --git a/packages/hardhat-chai-matchers/src/index.ts b/packages/hardhat-chai-matchers/src/index.ts index 658ffa8acd..d5fa986711 100644 --- a/packages/hardhat-chai-matchers/src/index.ts +++ b/packages/hardhat-chai-matchers/src/index.ts @@ -1,4 +1,4 @@ -import "@nomiclabs/hardhat-ethers"; +import "@nomicfoundation/hardhat-ethers"; import "./types"; diff --git a/packages/hardhat-chai-matchers/src/internal/changeEtherBalance.ts b/packages/hardhat-chai-matchers/src/internal/changeEtherBalance.ts index 50def56b7f..e0383d29ec 100644 --- a/packages/hardhat-chai-matchers/src/internal/changeEtherBalance.ts +++ b/packages/hardhat-chai-matchers/src/internal/changeEtherBalance.ts @@ -1,33 +1,40 @@ -import type { BigNumberish, providers } from "ethers"; +import type { + Addressable, + BigNumberish, + TransactionResponse, + default as EthersT, +} from "ethers"; import { buildAssert } from "../utils"; import { ensure } from "./calledOnContract/utils"; -import { Account, getAddressOf } from "./misc/account"; +import { getAddressOf } from "./misc/account"; import { BalanceChangeOptions } from "./misc/balance"; +import { assertIsNotNull } from "./utils"; export function supportChangeEtherBalance(Assertion: Chai.AssertionStatic) { Assertion.addMethod( "changeEtherBalance", function ( this: any, - account: Account | string, + account: Addressable | string, balanceChange: BigNumberish, options?: BalanceChangeOptions ) { - const { BigNumber } = require("ethers"); - + const { toBigInt } = require("ethers") as typeof EthersT; // capture negated flag before async code executes; see buildAssert's jsdoc const negated = this.__flags.negate; const subject = this._obj; const checkBalanceChange = ([actualChange, address]: [ - typeof BigNumber, + bigint, string ]) => { const assert = buildAssert(negated, checkBalanceChange); + const expectedChange = toBigInt(balanceChange); + assert( - actualChange.eq(BigNumber.from(balanceChange)), + actualChange === expectedChange, `Expected the ether balance of "${address}" to change by ${balanceChange.toString()} wei, but it changed by ${actualChange.toString()} wei`, `Expected the ether balance of "${address}" NOT to change by ${balanceChange.toString()} wei, but it did` ); @@ -47,19 +54,16 @@ export function supportChangeEtherBalance(Assertion: Chai.AssertionStatic) { export async function getBalanceChange( transaction: - | providers.TransactionResponse - | Promise - | (() => - | Promise - | providers.TransactionResponse), - account: Account | string, + | TransactionResponse + | Promise + | (() => Promise | TransactionResponse), + account: Addressable | string, options?: BalanceChangeOptions -) { - const { BigNumber } = await import("ethers"); +): Promise { const hre = await import("hardhat"); const provider = hre.network.provider; - let txResponse: providers.TransactionResponse; + let txResponse: TransactionResponse; if (typeof transaction === "function") { txResponse = await transaction(); @@ -68,6 +72,7 @@ export async function getBalanceChange( } const txReceipt = await txResponse.wait(); + assertIsNotNull(txReceipt, "txReceipt"); const txBlockNumber = txReceipt.blockNumber; const block = await provider.send("eth_getBlockByHash", [ @@ -83,23 +88,26 @@ export async function getBalanceChange( const address = await getAddressOf(account); - const balanceAfter = await provider.send("eth_getBalance", [ + const balanceAfterHex = await provider.send("eth_getBalance", [ address, `0x${txBlockNumber.toString(16)}`, ]); - const balanceBefore = await provider.send("eth_getBalance", [ + const balanceBeforeHex = await provider.send("eth_getBalance", [ address, `0x${(txBlockNumber - 1).toString(16)}`, ]); + const balanceAfter = BigInt(balanceAfterHex); + const balanceBefore = BigInt(balanceBeforeHex); + if (options?.includeFee !== true && address === txResponse.from) { - const gasPrice = txReceipt.effectiveGasPrice ?? txResponse.gasPrice; + const gasPrice = txReceipt.gasPrice; const gasUsed = txReceipt.gasUsed; - const txFee = gasPrice.mul(gasUsed); + const txFee = gasPrice * gasUsed; - return BigNumber.from(balanceAfter).add(txFee).sub(balanceBefore); + return balanceAfter + txFee - balanceBefore; } else { - return BigNumber.from(balanceAfter).sub(balanceBefore); + return balanceAfter - balanceBefore; } } diff --git a/packages/hardhat-chai-matchers/src/internal/changeEtherBalances.ts b/packages/hardhat-chai-matchers/src/internal/changeEtherBalances.ts index e2be028cfd..2c972591f5 100644 --- a/packages/hardhat-chai-matchers/src/internal/changeEtherBalances.ts +++ b/packages/hardhat-chai-matchers/src/internal/changeEtherBalances.ts @@ -1,25 +1,26 @@ -import type { BigNumber, BigNumberish, providers } from "ethers"; +import type EthersT from "ethers"; +import type { Addressable, BigNumberish, TransactionResponse } from "ethers"; import ordinal from "ordinal"; import { buildAssert } from "../utils"; -import { getAddressOf, Account } from "./misc/account"; +import { getAddressOf } from "./misc/account"; import { BalanceChangeOptions, getAddresses, getBalances, } from "./misc/balance"; +import { assertIsNotNull } from "./utils"; export function supportChangeEtherBalances(Assertion: Chai.AssertionStatic) { Assertion.addMethod( "changeEtherBalances", function ( this: any, - accounts: Array, + accounts: Array, balanceChanges: BigNumberish[], options?: BalanceChangeOptions ) { - const { BigNumber } = require("ethers"); - + const { toBigInt } = require("ethers") as typeof EthersT; // capture negated flag before async code executes; see buildAssert's jsdoc const negated = this.__flags.negate; @@ -29,19 +30,19 @@ export function supportChangeEtherBalances(Assertion: Chai.AssertionStatic) { } const checkBalanceChanges = ([actualChanges, accountAddresses]: [ - Array, + bigint[], string[] ]) => { const assert = buildAssert(negated, checkBalanceChanges); assert( - actualChanges.every((change, ind) => - change.eq(BigNumber.from(balanceChanges[ind])) + actualChanges.every( + (change, ind) => change === toBigInt(balanceChanges[ind]) ), () => { const lines: string[] = []; - actualChanges.forEach((change: BigNumber, i) => { - if (!change.eq(BigNumber.from(balanceChanges[i]))) { + actualChanges.forEach((change: bigint, i) => { + if (change !== toBigInt(balanceChanges[i])) { lines.push( `Expected the ether balance of ${ accountAddresses[i] @@ -57,8 +58,8 @@ export function supportChangeEtherBalances(Assertion: Chai.AssertionStatic) { }, () => { const lines: string[] = []; - actualChanges.forEach((change: BigNumber, i) => { - if (change.eq(BigNumber.from(balanceChanges[i]))) { + actualChanges.forEach((change: bigint, i) => { + if (change === toBigInt(balanceChanges[i])) { lines.push( `Expected the ether balance of ${ accountAddresses[i] @@ -88,15 +89,14 @@ export function supportChangeEtherBalances(Assertion: Chai.AssertionStatic) { } export async function getBalanceChanges( - transaction: - | providers.TransactionResponse - | Promise, - accounts: Array, + transaction: TransactionResponse | Promise, + accounts: Array, options?: BalanceChangeOptions -) { +): Promise { const txResponse = await transaction; const txReceipt = await txResponse.wait(); + assertIsNotNull(txReceipt, "txReceipt"); const txBlockNumber = txReceipt.blockNumber; const balancesAfter = await getBalances(accounts, txBlockNumber); @@ -104,16 +104,16 @@ export async function getBalanceChanges( const txFees = await getTxFees(accounts, txResponse, options); - return balancesAfter.map((balance, ind) => - balance.add(txFees[ind]).sub(balancesBefore[ind]) + return balancesAfter.map( + (balance, ind) => balance + txFees[ind] - balancesBefore[ind] ); } async function getTxFees( - accounts: Array, - txResponse: providers.TransactionResponse, + accounts: Array, + txResponse: TransactionResponse, options?: BalanceChangeOptions -) { +): Promise { return Promise.all( accounts.map(async (account) => { if ( @@ -121,14 +121,15 @@ async function getTxFees( (await getAddressOf(account)) === txResponse.from ) { const txReceipt = await txResponse.wait(); - const gasPrice = txReceipt.effectiveGasPrice ?? txResponse.gasPrice; + assertIsNotNull(txReceipt, "txReceipt"); + const gasPrice = txReceipt.gasPrice ?? txResponse.gasPrice; const gasUsed = txReceipt.gasUsed; - const txFee = gasPrice.mul(gasUsed); + const txFee = gasPrice * gasUsed; return txFee; } - return 0; + return 0n; }) ); } diff --git a/packages/hardhat-chai-matchers/src/internal/changeTokenBalance.ts b/packages/hardhat-chai-matchers/src/internal/changeTokenBalance.ts index 7def041206..17a006ffba 100644 --- a/packages/hardhat-chai-matchers/src/internal/changeTokenBalance.ts +++ b/packages/hardhat-chai-matchers/src/internal/changeTokenBalance.ts @@ -1,14 +1,29 @@ import type EthersT from "ethers"; +import type { + Addressable, + BaseContract, + BaseContractMethod, + BigNumberish, + ContractTransactionResponse, +} from "ethers"; import { buildAssert } from "../utils"; import { ensure } from "./calledOnContract/utils"; -import { Account, getAddressOf } from "./misc/account"; - -type TransactionResponse = EthersT.providers.TransactionResponse; - -interface Token extends EthersT.Contract { - balanceOf(address: string, overrides?: any): Promise; -} +import { getAddressOf } from "./misc/account"; +import { assertIsNotNull } from "./utils"; + +type TransactionResponse = EthersT.TransactionResponse; + +export type Token = BaseContract & { + balanceOf: BaseContractMethod<[string], bigint, bigint>; + name: BaseContractMethod<[], string, string>; + transfer: BaseContractMethod< + [string, BigNumberish], + boolean, + ContractTransactionResponse + >; + symbol: BaseContractMethod<[], string, string>; +}; export function supportChangeTokenBalance(Assertion: Chai.AssertionStatic) { Assertion.addMethod( @@ -16,7 +31,7 @@ export function supportChangeTokenBalance(Assertion: Chai.AssertionStatic) { function ( this: any, token: Token, - account: Account | string, + account: Addressable | string, balanceChange: EthersT.BigNumberish ) { const ethers = require("ethers") as typeof EthersT; @@ -32,14 +47,14 @@ export function supportChangeTokenBalance(Assertion: Chai.AssertionStatic) { checkToken(token, "changeTokenBalance"); const checkBalanceChange = ([actualChange, address, tokenDescription]: [ - EthersT.BigNumber, + bigint, string, string ]) => { const assert = buildAssert(negated, checkBalanceChange); assert( - actualChange.eq(ethers.BigNumber.from(balanceChange)), + actualChange === ethers.toBigInt(balanceChange), `Expected the balance of ${tokenDescription} tokens for "${address}" to change by ${balanceChange.toString()}, but it changed by ${actualChange.toString()}`, `Expected the balance of ${tokenDescription} tokens for "${address}" NOT to change by ${balanceChange.toString()}, but it did` ); @@ -63,7 +78,7 @@ export function supportChangeTokenBalance(Assertion: Chai.AssertionStatic) { function ( this: any, token: Token, - accounts: Array, + accounts: Array, balanceChanges: EthersT.BigNumberish[] ) { const ethers = require("ethers") as typeof EthersT; @@ -76,13 +91,7 @@ export function supportChangeTokenBalance(Assertion: Chai.AssertionStatic) { subject = subject(); } - checkToken(token, "changeTokenBalances"); - - if (accounts.length !== balanceChanges.length) { - throw new Error( - `The number of accounts (${accounts.length}) is different than the number of expected balance changes (${balanceChanges.length})` - ); - } + validateInput(this._obj, token, accounts, balanceChanges); const balanceChangesPromise = Promise.all( accounts.map((account) => getBalanceChange(subject, token, account)) @@ -93,12 +102,12 @@ export function supportChangeTokenBalance(Assertion: Chai.AssertionStatic) { actualChanges, addresses, tokenDescription, - ]: [EthersT.BigNumber[], string[], string]) => { + ]: [bigint[], string[], string]) => { const assert = buildAssert(negated, checkBalanceChanges); assert( - actualChanges.every((change, ind) => - change.eq(ethers.BigNumber.from(balanceChanges[ind])) + actualChanges.every( + (change, ind) => change === ethers.toBigInt(balanceChanges[ind]) ), `Expected the balances of ${tokenDescription} tokens for ${ addresses as any @@ -127,12 +136,34 @@ export function supportChangeTokenBalance(Assertion: Chai.AssertionStatic) { ); } +function validateInput( + obj: any, + token: Token, + accounts: Array, + balanceChanges: EthersT.BigNumberish[] +) { + try { + checkToken(token, "changeTokenBalances"); + + if (accounts.length !== balanceChanges.length) { + throw new Error( + `The number of accounts (${accounts.length}) is different than the number of expected balance changes (${balanceChanges.length})` + ); + } + } catch (e) { + // if the input validation fails, we discard the subject since it could + // potentially be a rejected promise + Promise.resolve(obj).catch(() => {}); + throw e; + } +} + function checkToken(token: unknown, method: string) { - if (typeof token !== "object" || token === null || !("functions" in token)) { + if (typeof token !== "object" || token === null || !("interface" in token)) { throw new Error( `The first argument of ${method} must be the contract instance of the token` ); - } else if ((token as any).functions.balanceOf === undefined) { + } else if ((token as any).interface.getFunction("balanceOf") === null) { throw new Error("The given contract instance is not an ERC20 token"); } } @@ -140,7 +171,7 @@ function checkToken(token: unknown, method: string) { export async function getBalanceChange( transaction: TransactionResponse | Promise, token: Token, - account: Account | string + account: Addressable | string ) { const ethers = require("ethers") as typeof EthersT; const hre = await import("hardhat"); @@ -149,6 +180,7 @@ export async function getBalanceChange( const txResponse = await transaction; const txReceipt = await txResponse.wait(); + assertIsNotNull(txReceipt, "txReceipt"); const txBlockNumber = txReceipt.blockNumber; const block = await provider.send("eth_getBlockByHash", [ @@ -172,7 +204,7 @@ export async function getBalanceChange( blockTag: txBlockNumber - 1, }); - return ethers.BigNumber.from(balanceAfter).sub(balanceBefore); + return ethers.toBigInt(balanceAfter) - balanceBefore; } let tokenDescriptionsCache: Record = {}; @@ -182,8 +214,9 @@ let tokenDescriptionsCache: Record = {}; * exist, the address of the token is used. */ async function getTokenDescription(token: Token): Promise { - if (tokenDescriptionsCache[token.address] === undefined) { - let tokenDescription = ``; + const tokenAddress = await token.getAddress(); + if (tokenDescriptionsCache[tokenAddress] === undefined) { + let tokenDescription = ``; try { tokenDescription = await token.symbol(); } catch (e) { @@ -192,10 +225,10 @@ async function getTokenDescription(token: Token): Promise { } catch (e2) {} } - tokenDescriptionsCache[token.address] = tokenDescription; + tokenDescriptionsCache[tokenAddress] = tokenDescription; } - return tokenDescriptionsCache[token.address]; + return tokenDescriptionsCache[tokenAddress]; } // only used by tests diff --git a/packages/hardhat-chai-matchers/src/internal/constants.ts b/packages/hardhat-chai-matchers/src/internal/constants.ts new file mode 100644 index 0000000000..da752e9b4e --- /dev/null +++ b/packages/hardhat-chai-matchers/src/internal/constants.ts @@ -0,0 +1 @@ +export const ASSERTION_ABORTED = "hh-chai-matchers-assertion-aborted"; diff --git a/packages/hardhat-chai-matchers/src/internal/emit.ts b/packages/hardhat-chai-matchers/src/internal/emit.ts index d6ea26791c..3dbd68842b 100644 --- a/packages/hardhat-chai-matchers/src/internal/emit.ts +++ b/packages/hardhat-chai-matchers/src/internal/emit.ts @@ -1,18 +1,17 @@ -import type { - providers, - utils as EthersUtils, - Contract, - Transaction, -} from "ethers"; +import type EthersT from "ethers"; +import type { Contract, Transaction } from "ethers"; import { AssertionError } from "chai"; import util from "util"; import ordinal from "ordinal"; import { AssertWithSsfi, buildAssert, Ssfi } from "../utils"; +import { ASSERTION_ABORTED } from "./constants"; +import { assertIsNotNull } from "./utils"; +import { HardhatChaiMatchersAssertionError } from "./errors"; -type EventFragment = EthersUtils.EventFragment; -type Interface = EthersUtils.Interface; -type Provider = providers.Provider; +type EventFragment = EthersT.EventFragment; +type Interface = EthersT.Interface; +type Provider = EthersT.Provider; export const EMIT_CALLED = "emitAssertionCalled"; @@ -20,7 +19,7 @@ async function waitForPendingTransaction( tx: Promise | Transaction | string, provider: Provider ) { - let hash: string | undefined; + let hash: string | null; if (tx instanceof Promise) { ({ hash } = await tx); } else if (typeof tx === "string") { @@ -28,10 +27,10 @@ async function waitForPendingTransaction( } else { ({ hash } = tx); } - if (hash === undefined) { + if (hash === null) { throw new Error(`${JSON.stringify(tx)} is not a valid transaction`); } - return provider.waitForTransaction(hash); + return provider.getTransactionReceipt(hash); } export function supportEmit( @@ -47,28 +46,39 @@ export function supportEmit( const promise = this.then === undefined ? Promise.resolve() : this; - const onSuccess = (receipt: providers.TransactionReceipt) => { + const onSuccess = (receipt: EthersT.TransactionReceipt) => { + // abort if the assertion chain was aborted, for example because + // a `.not` was combined with a `.withArgs` + if (chaiUtils.flag(this, ASSERTION_ABORTED) === true) { + return; + } + const assert = buildAssert(negated, onSuccess); - let eventFragment: EventFragment | undefined; + let eventFragment: EventFragment | null = null; try { eventFragment = contract.interface.getEvent(eventName); } catch (e) { // ignore error } - if (eventFragment === undefined) { + if (eventFragment === null) { throw new AssertionError( `Event "${eventName}" doesn't exist in the contract` ); } - const topic = contract.interface.getEventTopic(eventFragment); + const topic = eventFragment.topicHash; + const contractAddress = contract.target; + if (typeof contractAddress !== "string") { + throw new HardhatChaiMatchersAssertionError( + `The contract target should be a string` + ); + } this.logs = receipt.logs .filter((log) => log.topics.includes(topic)) .filter( - (log) => - log.address.toLowerCase() === contract.address.toLowerCase() + (log) => log.address.toLowerCase() === contractAddress.toLowerCase() ); assert( @@ -80,9 +90,26 @@ export function supportEmit( chaiUtils.flag(this, "contract", contract); }; - const derivedPromise = promise - .then(() => waitForPendingTransaction(tx, contract.provider)) - .then(onSuccess); + const derivedPromise = promise.then(() => { + // abort if the assertion chain was aborted, for example because + // a `.not` was combined with a `.withArgs` + if (chaiUtils.flag(this, ASSERTION_ABORTED) === true) { + return; + } + + if (contract.runner === null || contract.runner.provider === null) { + throw new HardhatChaiMatchersAssertionError( + "contract.runner.provider shouldn't be null" + ); + } + + return waitForPendingTransaction(tx, contract.runner.provider).then( + (receipt) => { + assertIsNotNull(receipt, "receipt"); + return onSuccess(receipt); + } + ); + }); chaiUtils.flag(this, EMIT_CALLED, true); @@ -124,11 +151,12 @@ function assertArgsArraysEqual( assert: AssertWithSsfi, ssfi: Ssfi ) { - const { utils } = require("ethers") as { utils: typeof EthersUtils }; - - const actualArgs = ( + const ethers = require("ethers") as typeof EthersT; + const parsedLog = ( chaiUtils.flag(context, "contract").interface as Interface - ).parseLog(log).args; + ).parseLog(log); + assertIsNotNull(parsedLog, "parsedLog"); + const actualArgs = parsedLog.args; const eventName = chaiUtils.flag(context, "eventName"); assert( actualArgs.length === expectedArgs.length, @@ -159,7 +187,7 @@ function assertArgsArraysEqual( } } else if (expectedArgs[index] instanceof Uint8Array) { new Assertion(actualArgs[index], undefined, ssfi, true).equal( - utils.hexlify(expectedArgs[index]) + ethers.hexlify(expectedArgs[index]) ); } else if ( expectedArgs[index]?.length !== undefined && @@ -195,10 +223,10 @@ function assertArgsArraysEqual( expectedArgs[index], "The actual value was an indexed and hashed value of the event argument. The expected value provided to the assertion should be the actual event argument (the pre-image of the hash). You provided the hash itself. Please supply the the actual event argument (the pre-image of the hash) instead." ); - const expectedArgBytes = utils.isHexString(expectedArgs[index]) - ? utils.arrayify(expectedArgs[index]) - : utils.toUtf8Bytes(expectedArgs[index]); - const expectedHash = utils.keccak256(expectedArgBytes); + const expectedArgBytes = ethers.isHexString(expectedArgs[index]) + ? ethers.getBytes(expectedArgs[index]) + : ethers.toUtf8Bytes(expectedArgs[index]); + const expectedHash = ethers.keccak256(expectedArgBytes); new Assertion(actualArgs[index].hash, undefined, ssfi, true).to.equal( expectedHash, `The actual value was an indexed and hashed value of the event argument. The expected value provided to the assertion was hashed to produce ${expectedHash}. The actual hash and the expected hash did not match` diff --git a/packages/hardhat-chai-matchers/src/internal/errors.ts b/packages/hardhat-chai-matchers/src/internal/errors.ts index c6f141cb69..87665ce845 100644 --- a/packages/hardhat-chai-matchers/src/internal/errors.ts +++ b/packages/hardhat-chai-matchers/src/internal/errors.ts @@ -1,9 +1,25 @@ -import { CustomError } from "hardhat/common"; +import { NomicLabsHardhatPluginError } from "hardhat/plugins"; -export class HardhatChaiMatchersDecodingError extends CustomError { +export class HardhatChaiMatchersError extends NomicLabsHardhatPluginError { + constructor(message: string, parent?: Error) { + super("@nomicfoundation/hardhat-chai-matchers", message, parent); + } +} + +export class HardhatChaiMatchersDecodingError extends HardhatChaiMatchersError { constructor(encodedData: string, type: string, parent: Error) { const message = `There was an error decoding '${encodedData}' as a ${type}`; super(message, parent); } } + +/** + * This class is used to assert assumptions in our implementation. Chai's + * AssertionError should be used for user assertions. + */ +export class HardhatChaiMatchersAssertionError extends HardhatChaiMatchersError { + constructor(message: string) { + super(`Assertion error: ${message}`); + } +} diff --git a/packages/hardhat-chai-matchers/src/internal/misc/account.ts b/packages/hardhat-chai-matchers/src/internal/misc/account.ts index ca77d3f87b..c4d7e536ce 100644 --- a/packages/hardhat-chai-matchers/src/internal/misc/account.ts +++ b/packages/hardhat-chai-matchers/src/internal/misc/account.ts @@ -1,21 +1,24 @@ -import type { Contract, Signer, Wallet } from "ethers"; +import type { Addressable } from "ethers"; import assert from "assert"; -export type Account = Signer | Contract; +import { HardhatChaiMatchersAssertionError } from "../errors"; -export function isAccount(account: Account): account is Contract | Wallet { - const ethers = require("ethers"); - return account instanceof ethers.Contract || account instanceof ethers.Wallet; -} +export async function getAddressOf( + account: Addressable | string +): Promise { + const { isAddressable } = await import("ethers"); -export async function getAddressOf(account: Account | string) { if (typeof account === "string") { assert(/^0x[0-9a-fA-F]{40}$/.test(account), `Invalid address ${account}`); return account; - } else if (isAccount(account)) { - return account.address; - } else { + } + + if (isAddressable(account)) { return account.getAddress(); } + + throw new HardhatChaiMatchersAssertionError( + `Expected string or addressable, got ${account as any}` + ); } diff --git a/packages/hardhat-chai-matchers/src/internal/misc/balance.ts b/packages/hardhat-chai-matchers/src/internal/misc/balance.ts index b70f6460e3..9bd93f6827 100644 --- a/packages/hardhat-chai-matchers/src/internal/misc/balance.ts +++ b/packages/hardhat-chai-matchers/src/internal/misc/balance.ts @@ -1,18 +1,20 @@ -import { Account, getAddressOf } from "./account"; +import type { Addressable } from "ethers"; + +import { getAddressOf } from "./account"; export interface BalanceChangeOptions { includeFee?: boolean; } -export function getAddresses(accounts: Array) { +export function getAddresses(accounts: Array) { return Promise.all(accounts.map((account) => getAddressOf(account))); } export async function getBalances( - accounts: Array, + accounts: Array, blockNumber?: number -) { - const { BigNumber } = await import("ethers"); +): Promise { + const { toBigInt } = await import("ethers"); const hre = await import("hardhat"); const provider = hre.ethers.provider; @@ -23,7 +25,7 @@ export async function getBalances( address, `0x${blockNumber?.toString(16) ?? 0}`, ]); - return BigNumber.from(result); + return toBigInt(result); }) ); } diff --git a/packages/hardhat-chai-matchers/src/internal/reverted/panic.ts b/packages/hardhat-chai-matchers/src/internal/reverted/panic.ts index e94c5508fd..d64b7074ba 100644 --- a/packages/hardhat-chai-matchers/src/internal/reverted/panic.ts +++ b/packages/hardhat-chai-matchers/src/internal/reverted/panic.ts @@ -1,5 +1,3 @@ -import type { BigNumber } from "ethers"; - export const PANIC_CODES = { ASSERTION_ERROR: 0x1, ARITHMETIC_UNDER_OR_OVERFLOW: 0x11, @@ -13,27 +11,25 @@ export const PANIC_CODES = { }; // copied from hardhat-core -export function panicErrorCodeToReason( - errorCode: BigNumber -): string | undefined { - switch (errorCode.toNumber()) { - case 0x1: +export function panicErrorCodeToReason(errorCode: bigint): string | undefined { + switch (errorCode) { + case 0x1n: return "Assertion error"; - case 0x11: + case 0x11n: return "Arithmetic operation underflowed or overflowed outside of an unchecked block"; - case 0x12: + case 0x12n: return "Division or modulo division by zero"; - case 0x21: + case 0x21n: return "Tried to convert a value into an enum, but the value was too big or negative"; - case 0x22: + case 0x22n: return "Incorrectly encoded storage byte array"; - case 0x31: + case 0x31n: return ".pop() was called on an empty array"; - case 0x32: + case 0x32n: return "Array accessed at an out-of-bounds or negative index"; - case 0x41: + case 0x41n: return "Too much memory was allocated, or an array was created that is too large"; - case 0x51: + case 0x51n: return "Called a zero-initialized variable of internal function type"; } } diff --git a/packages/hardhat-chai-matchers/src/internal/reverted/reverted.ts b/packages/hardhat-chai-matchers/src/internal/reverted/reverted.ts index 105dcdb445..57ba59340a 100644 --- a/packages/hardhat-chai-matchers/src/internal/reverted/reverted.ts +++ b/packages/hardhat-chai-matchers/src/internal/reverted/reverted.ts @@ -1,4 +1,7 @@ +import type EthersT from "ethers"; + import { buildAssert } from "../../utils"; +import { assertIsNotNull } from "../utils"; import { decodeReturnData, getReturnDataFromError } from "./utils"; export function supportReverted(Assertion: Chai.AssertionStatic) { @@ -27,6 +30,7 @@ export function supportReverted(Assertion: Chai.AssertionStatic) { const receipt = await getTransactionReceipt(hash); + assertIsNotNull(receipt, "receipt"); assert( receipt.status === 0, "Expected transaction to be reverted", @@ -52,6 +56,7 @@ export function supportReverted(Assertion: Chai.AssertionStatic) { }; const onError = (error: any) => { + const { toBeHex } = require("ethers") as typeof EthersT; const assert = buildAssert(negated, onError); const returnData = getReturnDataFromError(error); const decodedReturnData = decodeReturnData(returnData); @@ -73,9 +78,9 @@ export function supportReverted(Assertion: Chai.AssertionStatic) { assert( true, undefined, - `Expected transaction NOT to be reverted, but it reverted with panic code ${decodedReturnData.code.toHexString()} (${ - decodedReturnData.description - })` + `Expected transaction NOT to be reverted, but it reverted with panic code ${toBeHex( + decodedReturnData.code + )} (${decodedReturnData.description})` ); } else { const _exhaustiveCheck: never = decodedReturnData; diff --git a/packages/hardhat-chai-matchers/src/internal/reverted/revertedWith.ts b/packages/hardhat-chai-matchers/src/internal/reverted/revertedWith.ts index 4b8d903405..4bd50ea865 100644 --- a/packages/hardhat-chai-matchers/src/internal/reverted/revertedWith.ts +++ b/packages/hardhat-chai-matchers/src/internal/reverted/revertedWith.ts @@ -1,3 +1,5 @@ +import type EthersT from "ethers"; + import { buildAssert } from "../../utils"; import { decodeReturnData, getReturnDataFromError } from "./utils"; @@ -13,6 +15,9 @@ export function supportRevertedWith(Assertion: Chai.AssertionStatic) { !(expectedReason instanceof RegExp) && typeof expectedReason !== "string" ) { + // if the input validation fails, we discard the subject since it could + // potentially be a rejected promise + Promise.resolve(this._obj).catch(() => {}); throw new TypeError( "Expected the revert reason to be a string or a regular expression" ); @@ -33,6 +38,7 @@ export function supportRevertedWith(Assertion: Chai.AssertionStatic) { }; const onError = (error: any) => { + const { toBeHex } = require("ethers") as typeof EthersT; const assert = buildAssert(negated, onError); const returnData = getReturnDataFromError(error); @@ -57,9 +63,9 @@ export function supportRevertedWith(Assertion: Chai.AssertionStatic) { } else if (decodedReturnData.kind === "Panic") { assert( false, - `Expected transaction to be reverted with reason '${expectedReasonString}', but it reverted with panic code ${decodedReturnData.code.toHexString()} (${ - decodedReturnData.description - })` + `Expected transaction to be reverted with reason '${expectedReasonString}', but it reverted with panic code ${toBeHex( + decodedReturnData.code + )} (${decodedReturnData.description})` ); } else if (decodedReturnData.kind === "Custom") { assert( diff --git a/packages/hardhat-chai-matchers/src/internal/reverted/revertedWithCustomError.ts b/packages/hardhat-chai-matchers/src/internal/reverted/revertedWithCustomError.ts index f5e0ea26fd..afe5c66aa9 100644 --- a/packages/hardhat-chai-matchers/src/internal/reverted/revertedWithCustomError.ts +++ b/packages/hardhat-chai-matchers/src/internal/reverted/revertedWithCustomError.ts @@ -1,15 +1,23 @@ +import type EthersT from "ethers"; + import { AssertionError } from "chai"; import ordinal from "ordinal"; +import { ASSERTION_ABORTED } from "../constants"; +import { assertIsNotNull } from "../utils"; import { buildAssert, Ssfi } from "../../utils"; -import { decodeReturnData, getReturnDataFromError } from "./utils"; +import { + decodeReturnData, + getReturnDataFromError, + resultToArray, +} from "./utils"; export const REVERTED_WITH_CUSTOM_ERROR_CALLED = "customErrorAssertionCalled"; interface CustomErrorAssertionData { - contractInterface: any; + contractInterface: EthersT.Interface; returnData: string; - customError: CustomError; + customError: EthersT.ErrorFragment; } export function supportRevertedWithCustomError( @@ -18,38 +26,25 @@ export function supportRevertedWithCustomError( ) { Assertion.addMethod( "revertedWithCustomError", - function (this: any, contract: any, expectedCustomErrorName: string) { + function ( + this: any, + contract: EthersT.BaseContract, + expectedCustomErrorName: string + ) { // capture negated flag before async code executes; see buildAssert's jsdoc const negated = this.__flags.negate; - // check the case where users forget to pass the contract as the first - // argument - if (typeof contract === "string" || contract?.interface === undefined) { - throw new TypeError( - "The first argument of .revertedWithCustomError must be the contract that defines the custom error" - ); - } - - // validate custom error name - if (typeof expectedCustomErrorName !== "string") { - throw new TypeError("Expected the custom error name to be a string"); - } - - const iface: any = contract.interface; - - const expectedCustomError = findCustomErrorByName( - iface, + const { iface, expectedCustomError } = validateInput( + this._obj, + contract, expectedCustomErrorName ); - // check that interface contains the given custom error - if (expectedCustomError === undefined) { - throw new Error( - `The given contract doesn't have a custom error named '${expectedCustomErrorName}'` - ); - } - const onSuccess = () => { + if (utils.flag(this, ASSERTION_ABORTED) === true) { + return; + } + const assert = buildAssert(negated, onSuccess); assert( @@ -59,6 +54,12 @@ export function supportRevertedWithCustomError( }; const onError = (error: any) => { + if (utils.flag(this, ASSERTION_ABORTED) === true) { + return; + } + + const { toBeHex } = require("ethers") as typeof EthersT; + const assert = buildAssert(negated, onError); const returnData = getReturnDataFromError(error); @@ -77,12 +78,12 @@ export function supportRevertedWithCustomError( } else if (decodedReturnData.kind === "Panic") { assert( false, - `Expected transaction to be reverted with custom error '${expectedCustomErrorName}', but it reverted with panic code ${decodedReturnData.code.toHexString()} (${ - decodedReturnData.description - })` + `Expected transaction to be reverted with custom error '${expectedCustomErrorName}', but it reverted with panic code ${toBeHex( + decodedReturnData.code + )} (${decodedReturnData.description})` ); } else if (decodedReturnData.kind === "Custom") { - if (decodedReturnData.id === expectedCustomError.id) { + if (decodedReturnData.id === expectedCustomError.selector) { // add flag with the data needed for .withArgs const customErrorAssertionData: CustomErrorAssertionData = { contractInterface: iface, @@ -99,12 +100,9 @@ export function supportRevertedWithCustomError( } else { // try to decode the actual custom error // this will only work when the error comes from the given contract - const actualCustomError = findCustomErrorById( - iface, - decodedReturnData.id - ); + const actualCustomError = iface.getError(decodedReturnData.id); - if (actualCustomError === undefined) { + if (actualCustomError === null) { assert( false, `Expected transaction to be reverted with custom error '${expectedCustomErrorName}', but it reverted with a different custom error` @@ -138,10 +136,49 @@ export function supportRevertedWithCustomError( ); } +function validateInput( + obj: any, + contract: EthersT.BaseContract, + expectedCustomErrorName: string +): { iface: EthersT.Interface; expectedCustomError: EthersT.ErrorFragment } { + try { + // check the case where users forget to pass the contract as the first + // argument + if (typeof contract === "string" || contract?.interface === undefined) { + // discard subject since it could potentially be a rejected promise + throw new TypeError( + "The first argument of .revertedWithCustomError must be the contract that defines the custom error" + ); + } + + // validate custom error name + if (typeof expectedCustomErrorName !== "string") { + throw new TypeError("Expected the custom error name to be a string"); + } + + const iface = contract.interface; + const expectedCustomError = iface.getError(expectedCustomErrorName); + + // check that interface contains the given custom error + if (expectedCustomError === null) { + throw new Error( + `The given contract doesn't have a custom error named '${expectedCustomErrorName}'` + ); + } + + return { iface, expectedCustomError }; + } catch (e) { + // if the input validation fails, we discard the subject since it could + // potentially be a rejected promise + Promise.resolve(obj).catch(() => {}); + throw e; + } +} + export async function revertedWithCustomErrorWithArgs( context: any, Assertion: Chai.AssertionStatic, - utils: Chai.ChaiUtils, + _utils: Chai.ChaiUtils, expectedArgs: any[], ssfi: Ssfi ) { @@ -160,9 +197,10 @@ export async function revertedWithCustomErrorWithArgs( const { contractInterface, customError, returnData } = customErrorAssertionData; - const errorFragment = contractInterface.errors[customError.signature]; + const errorFragment = contractInterface.getError(customError.name); + assertIsNotNull(errorFragment, "errorFragment"); // We transform ether's Array-like object into an actual array as it's safer - const actualArgs = Array.from( + const actualArgs = resultToArray( contractInterface.decodeErrorResult(errorFragment, returnData) ); @@ -210,51 +248,3 @@ export async function revertedWithCustomErrorWithArgs( } } } - -interface CustomError { - name: string; - id: string; - signature: string; -} - -function findCustomErrorByName( - iface: any, - name: string -): CustomError | undefined { - const ethers = require("ethers"); - - const customErrorEntry = Object.entries(iface.errors).find( - ([, fragment]: any) => fragment.name === name - ); - - if (customErrorEntry === undefined) { - return undefined; - } - - const [customErrorSignature] = customErrorEntry; - const customErrorId = ethers.utils.id(customErrorSignature).slice(0, 10); - - return { - id: customErrorId, - name, - signature: customErrorSignature, - }; -} - -function findCustomErrorById(iface: any, id: string): CustomError | undefined { - const ethers = require("ethers"); - - const customErrorEntry: any = Object.entries(iface.errors).find( - ([signature]: any) => ethers.utils.id(signature).slice(0, 10) === id - ); - - if (customErrorEntry === undefined) { - return undefined; - } - - return { - id, - name: customErrorEntry[1].name, - signature: customErrorEntry[0], - }; -} diff --git a/packages/hardhat-chai-matchers/src/internal/reverted/revertedWithPanic.ts b/packages/hardhat-chai-matchers/src/internal/reverted/revertedWithPanic.ts index 9ec6050d3d..022e3ab561 100644 --- a/packages/hardhat-chai-matchers/src/internal/reverted/revertedWithPanic.ts +++ b/packages/hardhat-chai-matchers/src/internal/reverted/revertedWithPanic.ts @@ -1,4 +1,4 @@ -import type { BigNumber } from "ethers"; +import type EthersT from "ethers"; import { normalizeToBigInt } from "hardhat/common"; @@ -10,33 +10,37 @@ export function supportRevertedWithPanic(Assertion: Chai.AssertionStatic) { Assertion.addMethod( "revertedWithPanic", function (this: any, expectedCodeArg: any) { - const ethers = require("ethers"); + const ethers = require("ethers") as typeof EthersT; // capture negated flag before async code executes; see buildAssert's jsdoc const negated = this.__flags.negate; - let expectedCode: BigNumber | undefined; + let expectedCode: bigint | undefined; try { if (expectedCodeArg !== undefined) { - const normalizedCode = normalizeToBigInt(expectedCodeArg); - expectedCode = ethers.BigNumber.from(normalizedCode); + expectedCode = normalizeToBigInt(expectedCodeArg); } } catch { + // if the input validation fails, we discard the subject since it could + // potentially be a rejected promise + Promise.resolve(this._obj).catch(() => {}); throw new TypeError( `Expected the given panic code to be a number-like value, but got '${expectedCodeArg}'` ); } - const code: number | undefined = expectedCode as any; + const code: bigint | undefined = expectedCode; let description: string | undefined; let formattedPanicCode: string; if (code === undefined) { formattedPanicCode = "some panic code"; } else { - const codeBN = ethers.BigNumber.from(code); + const codeBN = ethers.toBigInt(code); description = panicErrorCodeToReason(codeBN) ?? "unknown panic code"; - formattedPanicCode = `panic code ${codeBN.toHexString()} (${description})`; + formattedPanicCode = `panic code ${ethers.toBeHex( + codeBN + )} (${description})`; } const onSuccess = () => { @@ -67,19 +71,19 @@ export function supportRevertedWithPanic(Assertion: Chai.AssertionStatic) { } else if (decodedReturnData.kind === "Panic") { if (code !== undefined) { assert( - decodedReturnData.code.eq(code), - `Expected transaction to be reverted with ${formattedPanicCode}, but it reverted with panic code ${decodedReturnData.code.toHexString()} (${ - decodedReturnData.description - })`, + decodedReturnData.code === code, + `Expected transaction to be reverted with ${formattedPanicCode}, but it reverted with panic code ${ethers.toBeHex( + decodedReturnData.code + )} (${decodedReturnData.description})`, `Expected transaction NOT to be reverted with ${formattedPanicCode}, but it was` ); } else { assert( true, undefined, - `Expected transaction NOT to be reverted with ${formattedPanicCode}, but it reverted with panic code ${decodedReturnData.code.toHexString()} (${ - decodedReturnData.description - })` + `Expected transaction NOT to be reverted with ${formattedPanicCode}, but it reverted with panic code ${ethers.toBeHex( + decodedReturnData.code + )} (${decodedReturnData.description})` ); } } else if (decodedReturnData.kind === "Custom") { diff --git a/packages/hardhat-chai-matchers/src/internal/reverted/revertedWithoutReason.ts b/packages/hardhat-chai-matchers/src/internal/reverted/revertedWithoutReason.ts index f9a5286377..0a7da1a859 100644 --- a/packages/hardhat-chai-matchers/src/internal/reverted/revertedWithoutReason.ts +++ b/packages/hardhat-chai-matchers/src/internal/reverted/revertedWithoutReason.ts @@ -1,3 +1,5 @@ +import type EthersT from "ethers"; + import { buildAssert } from "../../utils"; import { decodeReturnData, getReturnDataFromError } from "./utils"; @@ -16,6 +18,7 @@ export function supportRevertedWithoutReason(Assertion: Chai.AssertionStatic) { }; const onError = (error: any) => { + const { toBeHex } = require("ethers") as typeof EthersT; const assert = buildAssert(negated, onError); const returnData = getReturnDataFromError(error); @@ -35,9 +38,9 @@ export function supportRevertedWithoutReason(Assertion: Chai.AssertionStatic) { } else if (decodedReturnData.kind === "Panic") { assert( false, - `Expected transaction to be reverted without a reason, but it reverted with panic code ${decodedReturnData.code.toHexString()} (${ - decodedReturnData.description - })` + `Expected transaction to be reverted without a reason, but it reverted with panic code ${toBeHex( + decodedReturnData.code + )} (${decodedReturnData.description})` ); } else if (decodedReturnData.kind === "Custom") { assert( diff --git a/packages/hardhat-chai-matchers/src/internal/reverted/utils.ts b/packages/hardhat-chai-matchers/src/internal/reverted/utils.ts index 84d537ff7b..253ed71562 100644 --- a/packages/hardhat-chai-matchers/src/internal/reverted/utils.ts +++ b/packages/hardhat-chai-matchers/src/internal/reverted/utils.ts @@ -1,4 +1,4 @@ -import type { BigNumber } from "ethers"; +import type EthersT from "ethers"; import { AssertionError } from "chai"; @@ -51,7 +51,7 @@ type DecodedReturnData = } | { kind: "Panic"; - code: BigNumber; + code: bigint; description: string; } | { @@ -61,7 +61,9 @@ type DecodedReturnData = }; export function decodeReturnData(returnData: string): DecodedReturnData { - const { defaultAbiCoder: abi } = require("@ethersproject/abi"); + const { AbiCoder } = require("ethers") as typeof EthersT; + const abi = new AbiCoder(); + if (returnData === "0x") { return { kind: "Empty" }; } else if (returnData.startsWith(ERROR_STRING_PREFIX)) { @@ -79,7 +81,7 @@ export function decodeReturnData(returnData: string): DecodedReturnData { }; } else if (returnData.startsWith(PANIC_CODE_PREFIX)) { const encodedReason = returnData.slice(PANIC_CODE_PREFIX.length); - let code: BigNumber; + let code: bigint; try { code = abi.decode(["uint256"], `0x${encodedReason}`)[0]; } catch (e: any) { @@ -101,3 +103,25 @@ export function decodeReturnData(returnData: string): DecodedReturnData { data: `0x${returnData.slice(10)}`, }; } + +/** + * Takes an ethers result object and converts it into a (potentially nested) array. + * + * For example, given this error: + * + * struct Point(uint x, uint y) + * error MyError(string, Point) + * + * revert MyError("foo", Point(1, 2)) + * + * The resulting array will be: ["foo", [1n, 2n]] + */ +export function resultToArray(result: EthersT.Result): any[] { + return result + .toArray() + .map((x) => + typeof x === "object" && x !== null && "toArray" in x + ? resultToArray(x) + : x + ); +} diff --git a/packages/hardhat-chai-matchers/src/internal/utils.ts b/packages/hardhat-chai-matchers/src/internal/utils.ts new file mode 100644 index 0000000000..b3b672c2e4 --- /dev/null +++ b/packages/hardhat-chai-matchers/src/internal/utils.ts @@ -0,0 +1,12 @@ +import { HardhatChaiMatchersAssertionError } from "./errors"; + +export function assertIsNotNull( + value: T, + valueName: string +): asserts value is Exclude { + if (value === null) { + throw new HardhatChaiMatchersAssertionError( + `${valueName} should not be null` + ); + } +} diff --git a/packages/hardhat-chai-matchers/src/internal/withArgs.ts b/packages/hardhat-chai-matchers/src/internal/withArgs.ts index 856c055e59..29c3e3eda6 100644 --- a/packages/hardhat-chai-matchers/src/internal/withArgs.ts +++ b/packages/hardhat-chai-matchers/src/internal/withArgs.ts @@ -1,6 +1,7 @@ import { AssertionError } from "chai"; import { isBigNumber, normalizeToBigInt } from "hardhat/common"; +import { ASSERTION_ABORTED } from "./constants"; import { emitWithArgs, EMIT_CALLED } from "./emit"; import { @@ -51,24 +52,7 @@ export function supportWithArgs( utils: Chai.ChaiUtils ) { Assertion.addMethod("withArgs", function (this: any, ...expectedArgs: any[]) { - if (Boolean(this.__flags.negate)) { - throw new Error("Do not combine .not. with .withArgs()"); - } - - const emitCalled = utils.flag(this, EMIT_CALLED) === true; - const revertedWithCustomErrorCalled = - utils.flag(this, REVERTED_WITH_CUSTOM_ERROR_CALLED) === true; - - if (!emitCalled && !revertedWithCustomErrorCalled) { - throw new Error( - "withArgs can only be used in combination with a previous .emit or .revertedWithCustomError assertion" - ); - } - if (emitCalled && revertedWithCustomErrorCalled) { - throw new Error( - "withArgs called with both .emit and .revertedWithCustomError, but these assertions cannot be combined" - ); - } + const { emitCalled } = validateInput.call(this, utils); const promise = this.then === undefined ? Promise.resolve() : this; @@ -93,3 +77,39 @@ export function supportWithArgs( return this; }); } + +function validateInput( + this: any, + utils: Chai.ChaiUtils +): { emitCalled: boolean } { + try { + if (Boolean(this.__flags.negate)) { + throw new Error("Do not combine .not. with .withArgs()"); + } + + const emitCalled = utils.flag(this, EMIT_CALLED) === true; + const revertedWithCustomErrorCalled = + utils.flag(this, REVERTED_WITH_CUSTOM_ERROR_CALLED) === true; + + if (!emitCalled && !revertedWithCustomErrorCalled) { + throw new Error( + "withArgs can only be used in combination with a previous .emit or .revertedWithCustomError assertion" + ); + } + if (emitCalled && revertedWithCustomErrorCalled) { + throw new Error( + "withArgs called with both .emit and .revertedWithCustomError, but these assertions cannot be combined" + ); + } + + return { emitCalled }; + } catch (e) { + // signal that validation failed to allow the matchers to finish early + utils.flag(this, ASSERTION_ABORTED, true); + + // discard subject since it could potentially be a rejected promise + Promise.resolve(this._obj).catch(() => {}); + + throw e; + } +} diff --git a/packages/hardhat-chai-matchers/test/bigNumber.ts b/packages/hardhat-chai-matchers/test/bigNumber.ts index 2bfcff1967..f60f967534 100644 --- a/packages/hardhat-chai-matchers/test/bigNumber.ts +++ b/packages/hardhat-chai-matchers/test/bigNumber.ts @@ -1,5 +1,4 @@ import { expect, AssertionError } from "chai"; -import { BigNumber as BigNumberEthers } from "ethers"; import { BigNumber as BigNumberJs } from "bignumber.js"; import BN from "bn.js"; @@ -7,11 +6,10 @@ import { HardhatError } from "hardhat/internal/core/errors"; import "../src/internal/add-chai-matchers"; -type SupportedNumber = number | bigint | BN | BigNumberEthers | BigNumberJs; +type SupportedNumber = number | bigint | BN | BigNumberJs; const numberToBigNumberConversions = [ (n: number) => BigInt(n), - (n: number) => BigNumberEthers.from(n), (n: number) => new BN(n), (n: number) => new BigNumberJs(n), ]; @@ -21,8 +19,6 @@ describe("BigNumber matchers", function () { if (typeof n === "object") { if (n instanceof BN) { return "BN"; - } else if (n instanceof BigNumberEthers) { - return "ethers.BigNumber"; } else if (n instanceof BigNumberJs) { return "bignumber.js"; } @@ -582,7 +578,6 @@ describe("BigNumber matchers", function () { // a few particular combinations of types don't work: if ( typeof convertedActual === "string" && - !BigNumberEthers.isBigNumber(convertedExpected) && !BN.isBN(convertedExpected) && !BigNumberJs.isBigNumber(convertedExpected) ) { @@ -879,7 +874,7 @@ describe("BigNumber matchers", function () { // We are not checking the content of the arrays/objects because // it depends on the type of the numbers (plain numbers, native - // bigints, ethers's BigNumbers) + // bigints) // Ideally the output would be normalized and we could check the // actual content more easily. @@ -1208,11 +1203,6 @@ describe("BigNumber matchers", function () { "custom message" ); - // number and ethers bignumber - expect(() => - expect(1).to.equal(BigNumberEthers.from(2), "custom message") - ).to.throw(AssertionError, "custom message"); - // same but for deep comparisons expect(() => expect([1]).to.equal([2], "custom message")).to.throw( AssertionError, @@ -1224,10 +1214,5 @@ describe("BigNumber matchers", function () { AssertionError, "custom message" ); - - // number and ethers bignumber - expect(() => - expect([1]).to.equal([BigNumberEthers.from(2)], "custom message") - ).to.throw(AssertionError, "custom message"); }); }); diff --git a/packages/hardhat-chai-matchers/test/changeEtherBalance.ts b/packages/hardhat-chai-matchers/test/changeEtherBalance.ts index 12fc629588..c2c7adf932 100644 --- a/packages/hardhat-chai-matchers/test/changeEtherBalance.ts +++ b/packages/hardhat-chai-matchers/test/changeEtherBalance.ts @@ -1,10 +1,10 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { expect, AssertionError } from "chai"; -import { BigNumber, Contract } from "ethers"; import path from "path"; import util from "util"; import "../src/internal/add-chai-matchers"; +import { ChangeEtherBalance } from "./contracts"; import { useEnvironment, useEnvironmentWithNode } from "./helpers"; describe("INTEGRATION: changeEtherBalance matcher", function () { @@ -14,16 +14,18 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { runTests(); }); - describe("connected to a hardhat node", function () { + // TODO re-enable this when + // https://github.com/ethers-io/ethers.js/issues/4014 is fixed + describe.skip("connected to a hardhat node", function () { useEnvironmentWithNode("hardhat-project"); runTests(); }); function runTests() { - let sender: SignerWithAddress; - let receiver: SignerWithAddress; - let contract: Contract; + let sender: HardhatEthersSigner; + let receiver: HardhatEthersSigner; + let contract: ChangeEtherBalance; let txGasFees: number; beforeEach(async function () { @@ -31,7 +33,9 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { sender = wallets[0]; receiver = wallets[1]; contract = await ( - await this.hre.ethers.getContractFactory("ChangeEtherBalance") + await this.hre.ethers.getContractFactory<[], ChangeEtherBalance>( + "ChangeEtherBalance" + ) ).deploy(); txGasFees = 1 * 21_000; await this.hre.network.provider.send( @@ -53,13 +57,22 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { it("Should fail when block contains more than one transaction", async function () { await this.hre.network.provider.send("evm_setAutomine", [false]); - await sender.sendTransaction({ to: receiver.address, value: 200 }); + + // we set a gas limit to avoid using the whole block gas limit + await sender.sendTransaction({ + to: receiver.address, + value: 200, + gasLimit: 30_000, + }); + await this.hre.network.provider.send("evm_setAutomine", [true]); + await expect( expect(() => sender.sendTransaction({ to: receiver.address, value: 200, + gasLimit: 30_000, }) ).to.changeEtherBalance(sender, -200, { includeFee: true }) ).to.be.eventually.rejectedWith( @@ -86,15 +99,6 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { ).to.changeEtherBalance(sender, BigInt("-200")); }); - it("Should pass when given an ethers BigNumber", async () => { - await expect(() => - sender.sendTransaction({ - to: receiver.address, - value: 200, - }) - ).to.changeEtherBalance(sender, BigNumber.from("-200")); - }); - it("Should pass when expected balance change is passed as int and is equal to an actual", async () => { await expect(() => sender.sendTransaction({ @@ -136,24 +140,6 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { ).to.changeEtherBalance(sender, -200); }); - it("Should pass when expected balance change is passed as BN and is equal to an actual", async () => { - await expect(() => - sender.sendTransaction({ - to: receiver.address, - value: 200, - }) - ).to.changeEtherBalance(receiver, BigNumber.from(200)); - }); - - it("Should pass on negative case when expected balance change is not equal to an actual", async () => { - await expect(() => - sender.sendTransaction({ - to: receiver.address, - value: 200, - }) - ).to.not.changeEtherBalance(receiver, BigNumber.from(300)); - }); - it("Should throw when fee was not calculated correctly", async () => { await expect( expect(() => @@ -206,7 +192,8 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { }); it("shouldn't run the transaction twice", async function () { - const receiverBalanceBefore = await receiver.getBalance(); + const receiverBalanceBefore: bigint = + await this.hre.ethers.provider.getBalance(receiver); await expect(() => sender.sendTransaction({ @@ -215,11 +202,12 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { }) ).to.changeEtherBalance(sender, -200); - const receiverBalanceChange = (await receiver.getBalance()).sub( - receiverBalanceBefore - ); + const receiverBalanceAfter: bigint = + await this.hre.ethers.provider.getBalance(receiver); + const receiverBalanceChange = + receiverBalanceAfter - receiverBalanceBefore; - expect(receiverBalanceChange.toNumber()).to.equal(200); + expect(receiverBalanceChange).to.equal(200n); }); }); @@ -227,7 +215,7 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { it("Should pass when expected balance change is passed as int and is equal to an actual", async () => { await expect(async () => sender.sendTransaction({ - to: contract.address, + to: contract, value: 200, }) ).to.changeEtherBalance(contract, 200); @@ -300,28 +288,6 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { ).to.changeEtherBalance(sender, -200); }); - it("Should pass when expected balance change is passed as BN and is equal to an actual", async () => { - await expect(() => - sender.sendTransaction({ - to: receiver.address, - maxFeePerGas: 2, - maxPriorityFeePerGas: 1, - value: 200, - }) - ).to.changeEtherBalance(receiver, BigNumber.from(200)); - }); - - it("Should pass on negative case when expected balance change is not equal to an actual", async () => { - await expect(() => - sender.sendTransaction({ - to: receiver.address, - maxFeePerGas: 2, - maxPriorityFeePerGas: 1, - value: 200, - }) - ).to.not.changeEtherBalance(receiver, BigNumber.from(300)); - }); - it("Should throw when fee was not calculated correctly", async () => { await expect( expect(() => @@ -377,7 +343,7 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { it("Should pass when expected balance change is passed as int and is equal to an actual", async () => { await expect(async () => sender.sendTransaction({ - to: contract.address, + to: contract, maxFeePerGas: 2, maxPriorityFeePerGas: 1, value: 200, @@ -387,17 +353,17 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { it("Should take into account transaction fee", async function () { const tx = { - to: contract.address, + to: contract, maxFeePerGas: 2, maxPriorityFeePerGas: 1, value: 200, }; - const gas = await this.hre.ethers.provider.estimateGas(tx); + const gas: bigint = await this.hre.ethers.provider.estimateGas(tx); await expect(() => sender.sendTransaction(tx)).to.changeEtherBalance( sender, - -gas.add(200).toNumber(), + -(gas + 200n), { includeFee: true, } @@ -416,7 +382,8 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { }); it("shouldn't run the transaction twice", async function () { - const receiverBalanceBefore = await receiver.getBalance(); + const receiverBalanceBefore: bigint = + await this.hre.ethers.provider.getBalance(receiver); await expect(() => sender.sendTransaction({ @@ -427,11 +394,12 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { }) ).to.changeEtherBalance(sender, -200); - const receiverBalanceChange = (await receiver.getBalance()).sub( - receiverBalanceBefore - ); + const receiverBalanceAfter: bigint = + await this.hre.ethers.provider.getBalance(receiver); + const receiverBalanceChange = + receiverBalanceAfter - receiverBalanceBefore; - expect(receiverBalanceChange.toNumber()).to.equal(200); + expect(receiverBalanceChange).to.equal(200n); }); }); @@ -455,24 +423,6 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { ).to.changeEtherBalance(receiver, 200); }); - it("Should pass when expected balance change is passed as BN and is equal to an actual", async () => { - await expect( - await sender.sendTransaction({ - to: receiver.address, - value: 200, - }) - ).to.changeEtherBalance(sender, BigNumber.from(-200)); - }); - - it("Should pass on negative case when expected balance change is not equal to an actual", async () => { - await expect( - await sender.sendTransaction({ - to: receiver.address, - value: 200, - }) - ).to.not.changeEtherBalance(receiver, BigNumber.from(300)); - }); - it("Should throw when expected balance change value was different from an actual", async () => { await expect( expect( @@ -506,7 +456,7 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { it("Should pass when expected balance change is passed as int and is equal to an actual", async () => { await expect( await sender.sendTransaction({ - to: contract.address, + to: contract, value: 200, }) ).to.changeEtherBalance(contract, 200); @@ -534,24 +484,6 @@ describe("INTEGRATION: changeEtherBalance matcher", function () { ).to.changeEtherBalance(receiver, 200); }); - it("Should pass when expected balance change is passed as BN and is equal to an actual", async () => { - await expect( - sender.sendTransaction({ - to: receiver.address, - value: 200, - }) - ).to.changeEtherBalance(sender, BigNumber.from(-200)); - }); - - it("Should pass on negative case when expected balance change is not equal to an actual", async () => { - await expect( - sender.sendTransaction({ - to: receiver.address, - value: 200, - }) - ).to.not.changeEtherBalance(receiver, BigNumber.from(300)); - }); - it("Should throw when expected balance change value was different from an actual", async () => { await expect( expect( diff --git a/packages/hardhat-chai-matchers/test/changeEtherBalances.ts b/packages/hardhat-chai-matchers/test/changeEtherBalances.ts index 3fbbde9a94..dd06d39e2a 100644 --- a/packages/hardhat-chai-matchers/test/changeEtherBalances.ts +++ b/packages/hardhat-chai-matchers/test/changeEtherBalances.ts @@ -1,10 +1,10 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { expect, AssertionError } from "chai"; -import { BigNumber, Contract } from "ethers"; import path from "path"; import util from "util"; import "../src/internal/add-chai-matchers"; +import { ChangeEtherBalance } from "./contracts"; import { useEnvironment, useEnvironmentWithNode } from "./helpers"; describe("INTEGRATION: changeEtherBalances matcher", function () { @@ -15,15 +15,16 @@ describe("INTEGRATION: changeEtherBalances matcher", function () { }); describe("connected to a hardhat node", function () { + process.env.CHAIN_ID = "12345"; useEnvironmentWithNode("hardhat-project"); runTests(); }); function runTests() { - let sender: SignerWithAddress; - let receiver: SignerWithAddress; - let contract: Contract; + let sender: HardhatEthersSigner; + let receiver: HardhatEthersSigner; + let contract: ChangeEtherBalance; let txGasFees: number; beforeEach(async function () { @@ -31,7 +32,9 @@ describe("INTEGRATION: changeEtherBalances matcher", function () { sender = wallets[0]; receiver = wallets[1]; contract = await ( - await this.hre.ethers.getContractFactory("ChangeEtherBalance") + await this.hre.ethers.getContractFactory<[], ChangeEtherBalance>( + "ChangeEtherBalance" + ) ).deploy(); txGasFees = 1 * 21_000; await this.hre.network.provider.send( @@ -45,7 +48,7 @@ describe("INTEGRATION: changeEtherBalances matcher", function () { it("Should pass when all expected balance changes are equal to actual values", async () => { await expect(() => sender.sendTransaction({ - to: contract.address, + to: contract, value: 200, }) ).to.changeEtherBalances([sender, contract], [-200, 200]); @@ -100,19 +103,6 @@ describe("INTEGRATION: changeEtherBalances matcher", function () { ); }); - it("Should pass when given ethers BigNumber", async () => { - await expect(() => - sender.sendTransaction({ - to: receiver.address, - gasPrice: 1, - value: 200, - }) - ).to.changeEtherBalances( - [sender, receiver], - [BigNumber.from("-200"), BigNumber.from(200)] - ); - }); - it("Should take into account transaction fee (legacy tx)", async () => { await expect(() => sender.sendTransaction({ @@ -213,7 +203,9 @@ describe("INTEGRATION: changeEtherBalances matcher", function () { }); it("shouldn't run the transaction twice", async function () { - const receiverBalanceBefore = await receiver.getBalance(); + const receiverBalanceBefore = await this.hre.ethers.provider.getBalance( + receiver + ); await expect(() => sender.sendTransaction({ @@ -223,11 +215,13 @@ describe("INTEGRATION: changeEtherBalances matcher", function () { }) ).to.changeEtherBalances([sender, receiver], [-200, 200]); - const receiverBalanceChange = (await receiver.getBalance()).sub( - receiverBalanceBefore + const receiverBalanceAfter = await this.hre.ethers.provider.getBalance( + receiver ); + const receiverBalanceChange = + receiverBalanceAfter - receiverBalanceBefore; - expect(receiverBalanceChange.toNumber()).to.equal(200); + expect(receiverBalanceChange).to.equal(200n); }); }); @@ -236,7 +230,7 @@ describe("INTEGRATION: changeEtherBalances matcher", function () { it("Should pass when all expected balance changes are equal to actual values", async () => { await expect( await sender.sendTransaction({ - to: contract.address, + to: contract, value: 200, }) ).to.changeEtherBalances([sender, contract], [-200, 200]); diff --git a/packages/hardhat-chai-matchers/test/changeTokenBalance.ts b/packages/hardhat-chai-matchers/test/changeTokenBalance.ts index a180425095..cecbf91c4e 100644 --- a/packages/hardhat-chai-matchers/test/changeTokenBalance.ts +++ b/packages/hardhat-chai-matchers/test/changeTokenBalance.ts @@ -1,16 +1,19 @@ +import type { TransactionResponse } from "ethers"; + import assert from "assert"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { AssertionError, expect } from "chai"; -import { BigNumber, Contract, providers } from "ethers"; import path from "path"; import util from "util"; import "../src/internal/add-chai-matchers"; -import { clearTokenDescriptionsCache } from "../src/internal/changeTokenBalance"; +import { + clearTokenDescriptionsCache, + Token, +} from "../src/internal/changeTokenBalance"; +import { MatchersContract } from "./contracts"; import { useEnvironment, useEnvironmentWithNode } from "./helpers"; -type TransactionResponse = providers.TransactionResponse; - describe("INTEGRATION: changeTokenBalance and changeTokenBalances matchers", function () { describe("with the in-process hardhat network", function () { useEnvironment("hardhat-project"); @@ -29,23 +32,35 @@ describe("INTEGRATION: changeTokenBalance and changeTokenBalances matchers", fun }); function runTests() { - let sender: SignerWithAddress; - let receiver: SignerWithAddress; - let mockToken: Contract; + let sender: HardhatEthersSigner; + let receiver: HardhatEthersSigner; + let mockToken: Token; + let matchers: MatchersContract; beforeEach(async function () { const wallets = await this.hre.ethers.getSigners(); sender = wallets[0]; receiver = wallets[1]; - const MockToken = await this.hre.ethers.getContractFactory("MockToken"); + const MockToken = await this.hre.ethers.getContractFactory<[], Token>( + "MockToken" + ); mockToken = await MockToken.deploy(); + + const Matchers = await this.hre.ethers.getContractFactory< + [], + MatchersContract + >("Matchers"); + matchers = await Matchers.deploy(); }); describe("transaction that doesn't move tokens", () => { it("with a promise of a TxResponse", async function () { + const transactionResponse = sender.sendTransaction({ + to: receiver.address, + }); await runAllAsserts( - sender.sendTransaction({ to: receiver.address }), + transactionResponse, mockToken, [sender, receiver], [0, 0] @@ -232,11 +247,10 @@ describe("INTEGRATION: changeTokenBalance and changeTokenBalances matchers", fun mockToken.transfer(receiver.address, 50) ).to.changeTokenBalance(mockToken, receiver, 50); - const receiverBalanceChange = ( - await mockToken.balanceOf(receiver.address) - ).sub(receiverBalanceBefore); + const receiverBalanceChange = + (await mockToken.balanceOf(receiver.address)) - receiverBalanceBefore; - expect(receiverBalanceChange.toNumber()).to.equal(50); + expect(receiverBalanceChange).to.equal(50n); }); it("changeTokenBalances shouldn't run the transaction twice", async function () { @@ -248,11 +262,10 @@ describe("INTEGRATION: changeTokenBalance and changeTokenBalances matchers", fun mockToken.transfer(receiver.address, 50) ).to.changeTokenBalances(mockToken, [sender, receiver], [-50, 50]); - const receiverBalanceChange = ( - await mockToken.balanceOf(receiver.address) - ).sub(receiverBalanceBefore); + const receiverBalanceChange = + (await mockToken.balanceOf(receiver.address)) - receiverBalanceBefore; - expect(receiverBalanceChange.toNumber()).to.equal(50); + expect(receiverBalanceChange).to.equal(50n); }); it("negated", async function () { @@ -334,9 +347,10 @@ describe("INTEGRATION: changeTokenBalance and changeTokenBalances matchers", fun }); it("uses the token name if the contract doesn't have a symbol", async function () { - const TokenWithOnlyName = await this.hre.ethers.getContractFactory( - "TokenWithOnlyName" - ); + const TokenWithOnlyName = await this.hre.ethers.getContractFactory< + [], + Token + >("TokenWithOnlyName"); const tokenWithOnlyName = await TokenWithOnlyName.deploy(); await expect( @@ -360,7 +374,7 @@ describe("INTEGRATION: changeTokenBalance and changeTokenBalances matchers", fun it("uses the contract address if the contract doesn't have name or symbol", async function () { const TokenWithoutNameNorSymbol = - await this.hre.ethers.getContractFactory( + await this.hre.ethers.getContractFactory<[], Token>( "TokenWithoutNameNorSymbol" ); const tokenWithoutNameNorSymbol = @@ -429,13 +443,17 @@ describe("INTEGRATION: changeTokenBalance and changeTokenBalances matchers", fun it("tx is not the only one in the block", async function () { await this.hre.network.provider.send("evm_setAutomine", [false]); - await sender.sendTransaction({ to: receiver.address }); + // we set a gas limit to avoid using the whole block gas limit + await sender.sendTransaction({ + to: receiver.address, + gasLimit: 30_000, + }); await this.hre.network.provider.send("evm_setAutomine", [true]); await expect( expect( - mockToken.transfer(receiver.address, 50) + mockToken.transfer(receiver.address, 50, { gasLimit: 100_000 }) ).to.changeTokenBalance(mockToken, sender, -50) ).to.be.rejectedWith(Error, "Multiple transactions found in block"); }); @@ -501,16 +519,33 @@ describe("INTEGRATION: changeTokenBalance and changeTokenBalances matchers", fun ); }); + it("arrays have different length, subject is a rejected promise", async function () { + expect(() => + expect(matchers.revertsWithoutReason()).to.changeTokenBalances( + mockToken, + [sender], + [-50, 50] + ) + ).to.throw( + Error, + "The number of accounts (1) is different than the number of expected balance changes (2)" + ); + }); + it("tx is not the only one in the block", async function () { await this.hre.network.provider.send("evm_setAutomine", [false]); - await sender.sendTransaction({ to: receiver.address }); + // we set a gas limit to avoid using the whole block gas limit + await sender.sendTransaction({ + to: receiver.address, + gasLimit: 30_000, + }); await this.hre.network.provider.send("evm_setAutomine", [true]); await expect( expect( - mockToken.transfer(receiver.address, 50) + mockToken.transfer(receiver.address, 50, { gasLimit: 100_000 }) ).to.changeTokenBalances(mockToken, [sender, receiver], [-50, 50]) ).to.be.rejectedWith(Error, "Multiple transactions found in block"); }); @@ -543,38 +578,6 @@ describe("INTEGRATION: changeTokenBalance and changeTokenBalances matchers", fun [BigInt(-50), BigInt(50)] ); }); - - it("ethers's bignumbers are accepted", async function () { - await expect( - mockToken.transfer(receiver.address, 50) - ).to.changeTokenBalance(mockToken, sender, BigNumber.from(-50)); - - await expect( - mockToken.transfer(receiver.address, 50) - ).to.changeTokenBalances( - mockToken, - [sender, receiver], - [BigNumber.from(-50), BigNumber.from(50)] - ); - }); - - it("mixed types are accepted", async function () { - await expect( - mockToken.transfer(receiver.address, 50) - ).to.changeTokenBalances( - mockToken, - [sender, receiver], - [BigInt(-50), BigNumber.from(50)] - ); - - await expect( - mockToken.transfer(receiver.address, 50) - ).to.changeTokenBalances( - mockToken, - [sender, receiver], - [BigNumber.from(-50), BigInt(50)] - ); - }); }); // smoke tests for stack traces @@ -638,9 +641,9 @@ async function runAllAsserts( | Promise | (() => TransactionResponse) | (() => Promise), - token: Contract, - accounts: Array, - balances: Array + token: Token, + accounts: Array, + balances: Array ) { // changeTokenBalances works for the given arrays await expect(expr).to.changeTokenBalances(token, accounts, balances); diff --git a/packages/hardhat-chai-matchers/test/contracts.ts b/packages/hardhat-chai-matchers/test/contracts.ts new file mode 100644 index 0000000000..c955e55bcc --- /dev/null +++ b/packages/hardhat-chai-matchers/test/contracts.ts @@ -0,0 +1,127 @@ +import { + BaseContract, + BaseContractMethod, + ContractTransactionResponse, + BigNumberish, +} from "ethers"; + +export type MatchersContract = BaseContract & { + panicAssert: BaseContractMethod<[], void, ContractTransactionResponse>; + revertWithCustomErrorWithInt: BaseContractMethod< + [BigNumberish], + void, + ContractTransactionResponse + >; + revertWithCustomErrorWithPair: BaseContractMethod< + [BigNumberish, BigNumberish], + void, + ContractTransactionResponse + >; + revertWithCustomErrorWithUint: BaseContractMethod< + [BigNumberish], + void, + ContractTransactionResponse + >; + revertWithCustomErrorWithUintAndString: BaseContractMethod< + [BigNumberish, string], + void, + ContractTransactionResponse + >; + revertWithSomeCustomError: BaseContractMethod< + [], + void, + ContractTransactionResponse + >; + revertsWith: BaseContractMethod<[string], void, ContractTransactionResponse>; + revertsWithoutReason: BaseContractMethod< + [], + void, + ContractTransactionResponse + >; + succeeds: BaseContractMethod<[], void, ContractTransactionResponse>; +}; + +export type ChangeEtherBalance = BaseContract & { + returnHalf: BaseContractMethod<[], void, ContractTransactionResponse>; + transferTo: BaseContractMethod<[string], void, ContractTransactionResponse>; +}; + +export type EventsContract = BaseContract & { + doNotEmit: BaseContractMethod<[], void, ContractTransactionResponse>; + emitBytes32: BaseContractMethod<[string], void, ContractTransactionResponse>; + emitBytes32Array: BaseContractMethod< + [string, string], + void, + ContractTransactionResponse + >; + emitBytes: BaseContractMethod<[string], void, ContractTransactionResponse>; + emitIndexedBytes32: BaseContractMethod< + [string], + void, + ContractTransactionResponse + >; + emitIndexedBytes: BaseContractMethod< + [string], + void, + ContractTransactionResponse + >; + emitIndexedString: BaseContractMethod< + [string], + void, + ContractTransactionResponse + >; + emitInt: BaseContractMethod< + [BigNumberish], + void, + ContractTransactionResponse + >; + emitNestedUintFromAnotherContract: BaseContractMethod< + [BigNumberish], + void, + ContractTransactionResponse + >; + emitNestedUintFromSameContract: BaseContractMethod< + [BigNumberish], + void, + ContractTransactionResponse + >; + emitString: BaseContractMethod<[string], void, ContractTransactionResponse>; + emitStruct: BaseContractMethod< + [BigNumberish, BigNumberish], + void, + ContractTransactionResponse + >; + emitTwoUints: BaseContractMethod< + [BigNumberish, BigNumberish], + void, + ContractTransactionResponse + >; + emitTwoUintsAndTwoStrings: BaseContractMethod< + [BigNumberish, BigNumberish, string, string], + void, + ContractTransactionResponse + >; + emitUint: BaseContractMethod< + [BigNumberish], + void, + ContractTransactionResponse + >; + emitUintAndString: BaseContractMethod< + [BigNumberish, string], + void, + ContractTransactionResponse + >; + emitUintArray: BaseContractMethod< + [BigNumberish, BigNumberish], + void, + ContractTransactionResponse + >; + emitUintTwice: BaseContractMethod< + [BigNumberish, BigNumberish], + void, + ContractTransactionResponse + >; + emitWithoutArgs: BaseContractMethod<[], void, ContractTransactionResponse>; +}; + +export type AnotherContract = BaseContract & {}; diff --git a/packages/hardhat-chai-matchers/test/events.ts b/packages/hardhat-chai-matchers/test/events.ts index 7bd6dd7f6d..88392ccedf 100644 --- a/packages/hardhat-chai-matchers/test/events.ts +++ b/packages/hardhat-chai-matchers/test/events.ts @@ -1,15 +1,17 @@ import { expect, AssertionError } from "chai"; -import { BigNumber, Contract, ethers } from "ethers"; +import { ethers } from "ethers"; import { anyUint, anyValue } from "../src/withArgs"; import { useEnvironment, useEnvironmentWithNode } from "./helpers"; import "../src/internal/add-chai-matchers"; +import { AnotherContract, EventsContract, MatchersContract } from "./contracts"; describe(".to.emit (contract events)", () => { - let contract: Contract; - let otherContract: Contract; + let contract: EventsContract; + let otherContract: AnotherContract; + let matchers: MatchersContract; describe("with the in-process hardhat network", function () { useEnvironment("hardhat-project"); @@ -28,9 +30,18 @@ describe(".to.emit (contract events)", () => { otherContract = await ( await this.hre.ethers.getContractFactory("AnotherContract") ).deploy(); + contract = await ( - await this.hre.ethers.getContractFactory("Events") - ).deploy(otherContract.address); + await this.hre.ethers.getContractFactory<[string], EventsContract>( + "Events" + ) + ).deploy(await otherContract.getAddress()); + + const Matchers = await this.hre.ethers.getContractFactory< + [], + MatchersContract + >("Matchers"); + matchers = await Matchers.deploy(); }); it("Should fail when expecting an event that's not in the contract", async function () { @@ -82,6 +93,14 @@ describe(".to.emit (contract events)", () => { ).to.throw(Error, "Do not combine .not. with .withArgs()"); }); + it("Should fail when used with .not, subject is a rejected promise", async function () { + expect(() => + expect(matchers.revertsWithoutReason()) + .not.to.emit(contract, "WithUintArg") + .withArgs(1) + ).to.throw(Error, "Do not combine .not. with .withArgs()"); + }); + it("should fail if withArgs is called on its own", async function () { expect(() => expect(contract.emitUint(1)) @@ -124,13 +143,9 @@ describe(".to.emit (contract events)", () => { }); const string1 = "string1"; - const string1Bytes = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes(string1) - ); + const string1Bytes = ethers.hexlify(ethers.toUtf8Bytes(string1)); const string2 = "string2"; - const string2Bytes = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes(string2) - ); + const string2Bytes = ethers.hexlify(ethers.toUtf8Bytes(string2)); // for abbreviating long strings in diff views like chai does: function abbrev(longString: string): string { @@ -138,7 +153,7 @@ describe(".to.emit (contract events)", () => { } function hash(s: string): string { - return ethers.utils.keccak256(s); + return ethers.keccak256(s); } describe("with a string argument", function () { @@ -264,8 +279,8 @@ describe(".to.emit (contract events)", () => { }); }); - const string1Bytes32 = ethers.utils.zeroPad(string1Bytes, 32); - const string2Bytes32 = ethers.utils.zeroPad(string2Bytes, 32); + const string1Bytes32 = ethers.zeroPadValue(string1Bytes, 32); + const string2Bytes32 = ethers.zeroPadValue(string2Bytes, 32); describe("with a bytes32 argument", function () { it("Should match the argument", async function () { await expect(contract.emitBytes32(string1Bytes32)) @@ -281,8 +296,8 @@ describe(".to.emit (contract events)", () => { ).to.be.eventually.rejectedWith( AssertionError, `expected '${abbrev( - ethers.utils.hexlify(string2Bytes32) - )}' to equal '${abbrev(ethers.utils.hexlify(string1Bytes32))}'` + ethers.hexlify(string2Bytes32) + )}' to equal '${abbrev(ethers.hexlify(string1Bytes32))}'` ); }); }); @@ -302,8 +317,8 @@ describe(".to.emit (contract events)", () => { ).to.be.eventually.rejectedWith( AssertionError, `expected '${abbrev( - ethers.utils.hexlify(string2Bytes32) - )}' to equal '${abbrev(ethers.utils.hexlify(string1Bytes32))}'` + ethers.hexlify(string2Bytes32) + )}' to equal '${abbrev(ethers.hexlify(string1Bytes32))}'` ); }); @@ -321,12 +336,6 @@ describe(".to.emit (contract events)", () => { .withArgs([1, 2]); }); - it("Should succeed when expectations are met with BigNumber", async function () { - await expect(contract.emitUintArray(1, 2)) - .to.emit(contract, "WithUintArray") - .withArgs([BigInt(1), BigNumber.from(2)]); - }); - it("Should fail when expectations are not met", async function () { await expect( expect(contract.emitUintArray(1, 2)) diff --git a/packages/hardhat-chai-matchers/test/fixture-projects/hardhat-project/hardhat.config.js b/packages/hardhat-chai-matchers/test/fixture-projects/hardhat-project/hardhat.config.js index 1940c36a74..d8edfc19ae 100644 --- a/packages/hardhat-chai-matchers/test/fixture-projects/hardhat-project/hardhat.config.js +++ b/packages/hardhat-chai-matchers/test/fixture-projects/hardhat-project/hardhat.config.js @@ -1,8 +1,11 @@ -require("@nomiclabs/hardhat-ethers"); +require("@nomicfoundation/hardhat-ethers"); module.exports = { solidity: "0.8.4", networks: { + hardhat: { + chainId: Number(process.env.CHAIN_ID ?? "31337"), + }, localhost: { url: `http://127.0.0.1:${process.env.HARDHAT_NODE_PORT}`, }, diff --git a/packages/hardhat-chai-matchers/test/helpers.ts b/packages/hardhat-chai-matchers/test/helpers.ts index 1aa41438f0..dd8485522e 100644 --- a/packages/hardhat-chai-matchers/test/helpers.ts +++ b/packages/hardhat-chai-matchers/test/helpers.ts @@ -6,7 +6,7 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import path from "path"; // we assume that all the fixture projects use the hardhat-ethers plugin -import "@nomiclabs/hardhat-ethers/internal/type-extensions"; +import "@nomicfoundation/hardhat-ethers/internal/type-extensions"; declare module "mocha" { interface Context { @@ -118,8 +118,8 @@ export async function runSuccessfulAsserts({ }) { await successfulAssert(matchers[method](...args)); await successfulAssert(matchers[`${method}View`](...args)); - await successfulAssert(matchers.estimateGas[method](...args)); - await successfulAssert(matchers.callStatic[method](...args)); + await successfulAssert(matchers[method].estimateGas(...args)); + await successfulAssert(matchers[method].staticCall(...args)); } /** @@ -147,9 +147,9 @@ export async function runFailedAsserts({ failedAssert(matchers[`${method}View`](...args)) ).to.be.rejectedWith(AssertionError, failedAssertReason); await expect( - failedAssert(matchers.estimateGas[method](...args)) + failedAssert(matchers[method].estimateGas(...args)) ).to.be.rejectedWith(AssertionError, failedAssertReason); await expect( - failedAssert(matchers.callStatic[method](...args)) + failedAssert(matchers[method].staticCall(...args)) ).to.be.rejectedWith(AssertionError, failedAssertReason); } diff --git a/packages/hardhat-chai-matchers/test/panic.ts b/packages/hardhat-chai-matchers/test/panic.ts index 1f19ebe993..efb6549161 100644 --- a/packages/hardhat-chai-matchers/test/panic.ts +++ b/packages/hardhat-chai-matchers/test/panic.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { BigNumber } from "ethers"; +import { toBigInt } from "ethers"; import { PANIC_CODES, @@ -9,7 +9,7 @@ import { describe("panic codes", function () { it("all exported panic codes should have a description", async function () { for (const [key, code] of Object.entries(PANIC_CODES)) { - const description = panicErrorCodeToReason(BigNumber.from(code)); + const description = panicErrorCodeToReason(toBigInt(code)); assert.isDefined(description, `No description for panic code ${key}`); } }); diff --git a/packages/hardhat-chai-matchers/test/reverted/reverted.ts b/packages/hardhat-chai-matchers/test/reverted/reverted.ts index 18ab3b12dc..4d7a378536 100644 --- a/packages/hardhat-chai-matchers/test/reverted/reverted.ts +++ b/packages/hardhat-chai-matchers/test/reverted/reverted.ts @@ -11,6 +11,7 @@ import { } from "../helpers"; import "../../src/internal/add-chai-matchers"; +import { MatchersContract } from "../contracts"; describe("INTEGRATION: Reverted", function () { describe("with the in-process hardhat network", function () { @@ -27,9 +28,12 @@ describe("INTEGRATION: Reverted", function () { function runTests() { // deploy Matchers contract before each test - let matchers: any; + let matchers: MatchersContract; beforeEach("deploy matchers contract", async function () { - const Matchers = await this.hre.ethers.getContractFactory("Matchers"); + const Matchers = await this.hre.ethers.getContractFactory< + [], + MatchersContract + >("Matchers"); matchers = await Matchers.deploy(); }); @@ -189,9 +193,9 @@ describe("INTEGRATION: Reverted", function () { it("TxReceipt of a reverted transaction", async function () { const tx = await mineRevertedTransaction(this.hre); - const receipt = await this.hre.ethers.provider.waitForTransaction( + const receipt = await this.hre.ethers.provider.getTransactionReceipt( tx.hash - ); // tx.wait rejects, so we use provider.waitForTransaction + ); // tx.wait rejects, so we use provider.getTransactionReceipt await expect(receipt).to.be.reverted; await expectAssertionError( @@ -213,9 +217,9 @@ describe("INTEGRATION: Reverted", function () { it("promise of a TxReceipt of a reverted transaction", async function () { const tx = await mineRevertedTransaction(this.hre); - const receiptPromise = this.hre.ethers.provider.waitForTransaction( + const receiptPromise = this.hre.ethers.provider.getTransactionReceipt( tx.hash - ); // tx.wait rejects, so we use provider.waitForTransaction + ); // tx.wait rejects, so we use provider.getTransactionReceipt await expect(receiptPromise).to.be.reverted; await expectAssertionError( @@ -352,12 +356,15 @@ describe("INTEGRATION: Reverted", function () { randomPrivateKey, this.hre.ethers.provider ); + const matchersFromSenderWithoutFunds = matchers.connect( + signer + ) as MatchersContract; // this transaction will fail because of lack of funds, not because of a // revert await expect( expect( - matchers.connect(signer).revertsWithoutReason({ + matchersFromSenderWithoutFunds.revertsWithoutReason({ gasLimit: 1_000_000, }) ).to.not.be.reverted @@ -374,7 +381,9 @@ describe("INTEGRATION: Reverted", function () { try { await expect(matchers.succeeds()).to.be.reverted; } catch (e: any) { - expect(util.inspect(e)).to.include( + const errorString = util.inspect(e); + expect(errorString).to.include("Expected transaction to be reverted"); + expect(errorString).to.include( path.join("test", "reverted", "reverted.ts") ); diff --git a/packages/hardhat-chai-matchers/test/reverted/revertedWith.ts b/packages/hardhat-chai-matchers/test/reverted/revertedWith.ts index ca4257c6d7..02ffe4298c 100644 --- a/packages/hardhat-chai-matchers/test/reverted/revertedWith.ts +++ b/packages/hardhat-chai-matchers/test/reverted/revertedWith.ts @@ -11,6 +11,7 @@ import { } from "../helpers"; import "../../src/internal/add-chai-matchers"; +import { MatchersContract } from "../contracts"; describe("INTEGRATION: Reverted with", function () { describe("with the in-process hardhat network", function () { @@ -27,10 +28,13 @@ describe("INTEGRATION: Reverted with", function () { function runTests() { // deploy Matchers contract before each test - let matchers: any; + let matchers: MatchersContract; beforeEach("deploy matchers contract", async function () { - const Matchers = await this.hre.ethers.getContractFactory("Matchers"); + const Matchers = await this.hre.ethers.getContractFactory< + [], + MatchersContract + >("Matchers"); matchers = await Matchers.deploy(); }); @@ -213,6 +217,18 @@ describe("INTEGRATION: Reverted with", function () { ); }); + it("non-string as expectation, subject is a rejected promise", async function () { + const tx = matchers.revertsWithoutReason(); + + expect(() => + // @ts-expect-error + expect(tx).to.be.revertedWith(10) + ).to.throw( + TypeError, + "Expected the revert reason to be a string or a regular expression" + ); + }); + it("errors that are not related to a reverted transaction", async function () { // use an address that almost surely doesn't have balance const randomPrivateKey = @@ -221,12 +237,15 @@ describe("INTEGRATION: Reverted with", function () { randomPrivateKey, this.hre.ethers.provider ); + const matchersFromSenderWithoutFunds = matchers.connect( + signer + ) as MatchersContract; // this transaction will fail because of lack of funds, not because of a // revert await expect( expect( - matchers.connect(signer).revertsWithoutReason({ + matchersFromSenderWithoutFunds.revertsWithoutReason({ gasLimit: 1_000_000, }) ).to.not.be.revertedWith("some reason") @@ -243,7 +262,11 @@ describe("INTEGRATION: Reverted with", function () { try { await expect(matchers.revertsWith("bar")).to.be.revertedWith("foo"); } catch (e: any) { - expect(util.inspect(e)).to.include( + const errorString = util.inspect(e); + expect(errorString).to.include( + "Expected transaction to be reverted with reason 'foo', but it reverted with reason 'bar'" + ); + expect(errorString).to.include( path.join("test", "reverted", "revertedWith.ts") ); diff --git a/packages/hardhat-chai-matchers/test/reverted/revertedWithCustomError.ts b/packages/hardhat-chai-matchers/test/reverted/revertedWithCustomError.ts index e5a9d2a037..7df2848cd3 100644 --- a/packages/hardhat-chai-matchers/test/reverted/revertedWithCustomError.ts +++ b/packages/hardhat-chai-matchers/test/reverted/revertedWithCustomError.ts @@ -1,5 +1,4 @@ import { AssertionError, expect } from "chai"; -import { BigNumber } from "ethers"; import { ProviderError } from "hardhat/internal/core/providers/errors"; import path from "path"; import util from "util"; @@ -13,6 +12,7 @@ import { import "../../src/internal/add-chai-matchers"; import { anyUint, anyValue } from "../../src/withArgs"; +import { MatchersContract } from "../contracts"; describe("INTEGRATION: Reverted with custom error", function () { describe("with the in-process hardhat network", function () { @@ -29,9 +29,12 @@ describe("INTEGRATION: Reverted with custom error", function () { function runTests() { // deploy Matchers contract before each test - let matchers: any; + let matchers: MatchersContract; beforeEach("deploy matchers contract", async function () { - const Matchers = await this.hre.ethers.getContractFactory("Matchers"); + const Matchers = await this.hre.ethers.getContractFactory< + [], + MatchersContract + >("Matchers"); matchers = await Matchers.deploy(); }); @@ -368,20 +371,6 @@ describe("INTEGRATION: Reverted with custom error", function () { ); }); - it("should work with bigints and bignumbers", async function () { - await expect(matchers.revertWithCustomErrorWithUint(1)) - .to.be.revertedWithCustomError(matchers, "CustomErrorWithUint") - .withArgs(BigInt(1)); - - await expect(matchers.revertWithCustomErrorWithUint(1)) - .to.be.revertedWithCustomError(matchers, "CustomErrorWithUint") - .withArgs(BigNumber.from(1)); - - await expect(matchers.revertWithCustomErrorWithPair(1, 2)) - .to.be.revertedWithCustomError(matchers, "CustomErrorWithPair") - .withArgs([BigInt(1), BigNumber.from(2)]); - }); - it("should work with predicates", async function () { await expect(matchers.revertWithCustomErrorWithUint(1)) .to.be.revertedWithCustomError(matchers, "CustomErrorWithUint") @@ -460,12 +449,15 @@ describe("INTEGRATION: Reverted with custom error", function () { randomPrivateKey, this.hre.ethers.provider ); + const matchersFromSenderWithoutFunds = matchers.connect( + signer + ) as MatchersContract; // this transaction will fail because of lack of funds, not because of a // revert await expect( expect( - matchers.connect(signer).revertsWithoutReason({ + matchersFromSenderWithoutFunds.revertsWithoutReason({ gasLimit: 1_000_000, }) ).to.not.be.revertedWithCustomError(matchers, "SomeCustomError") @@ -481,10 +473,14 @@ describe("INTEGRATION: Reverted with custom error", function () { it("includes test file", async function () { try { await expect( - matchers.revertedWith("some reason") + matchers.revertsWith("some reason") ).to.be.revertedWithCustomError(matchers, "SomeCustomError"); } catch (e: any) { - expect(util.inspect(e)).to.include( + const errorString = util.inspect(e); + expect(errorString).to.include( + "Expected transaction to be reverted with custom error 'SomeCustomError', but it reverted with reason 'some reason'" + ); + expect(errorString).to.include( path.join("test", "reverted", "revertedWithCustomError.ts") ); diff --git a/packages/hardhat-chai-matchers/test/reverted/revertedWithPanic.ts b/packages/hardhat-chai-matchers/test/reverted/revertedWithPanic.ts index 82c3071dc7..0ca7e47a0d 100644 --- a/packages/hardhat-chai-matchers/test/reverted/revertedWithPanic.ts +++ b/packages/hardhat-chai-matchers/test/reverted/revertedWithPanic.ts @@ -1,11 +1,11 @@ import { AssertionError, expect } from "chai"; -import { BigNumber } from "ethers"; import { ProviderError } from "hardhat/internal/core/providers/errors"; import path from "path"; import util from "util"; import "../../src/internal/add-chai-matchers"; import { PANIC_CODES } from "../../src/panic"; +import { MatchersContract } from "../contracts"; import { runSuccessfulAsserts, runFailedAsserts, @@ -28,9 +28,12 @@ describe("INTEGRATION: Reverted with panic", function () { function runTests() { // deploy Matchers contract before each test - let matchers: any; + let matchers: MatchersContract; beforeEach("deploy matchers contract", async function () { - const Matchers = await this.hre.ethers.getContractFactory("Matchers"); + const Matchers = await this.hre.ethers.getContractFactory< + [], + MatchersContract + >("Matchers"); matchers = await Matchers.deploy(); }); @@ -269,15 +272,6 @@ describe("INTEGRATION: Reverted with panic", function () { successfulAssert: (x) => expect(x).not.to.be.revertedWithPanic("1"), }); }); - - it("ethers's BigNumber", async function () { - await runSuccessfulAsserts({ - matchers, - method: "succeeds", - successfulAssert: (x) => - expect(x).not.to.be.revertedWithPanic(BigNumber.from(1)), - }); - }); }); describe("invalid values", function () { @@ -296,6 +290,15 @@ describe("INTEGRATION: Reverted with panic", function () { ); }); + it("non-number as expectation, subject is a rejected promise", async function () { + const tx = matchers.revertsWithoutReason(); + + expect(() => expect(tx).to.be.revertedWithPanic("invalid")).to.throw( + TypeError, + "Expected the given panic code to be a number-like value, but got 'invalid'" + ); + }); + it("errors that are not related to a reverted transaction", async function () { // use an address that almost surely doesn't have balance const randomPrivateKey = @@ -304,12 +307,15 @@ describe("INTEGRATION: Reverted with panic", function () { randomPrivateKey, this.hre.ethers.provider ); + const matchersFromSenderWithoutFunds = matchers.connect( + signer + ) as MatchersContract; // this transaction will fail because of lack of funds, not because of a // revert await expect( expect( - matchers.connect(signer).revertsWithoutReason({ + matchersFromSenderWithoutFunds.revertsWithoutReason({ gasLimit: 1_000_000, }) ).to.not.be.revertedWithPanic() @@ -326,7 +332,11 @@ describe("INTEGRATION: Reverted with panic", function () { try { await expect(matchers.panicAssert()).to.not.be.revertedWithPanic(); } catch (e: any) { - expect(util.inspect(e)).to.include( + const errorString = util.inspect(e); + expect(errorString).to.include( + "Expected transaction NOT to be reverted with some panic code, but it reverted with panic code 0x01 (Assertion error)" + ); + expect(errorString).to.include( path.join("test", "reverted", "revertedWithPanic.ts") ); diff --git a/packages/hardhat-chai-matchers/test/reverted/revertedWithoutReason.ts b/packages/hardhat-chai-matchers/test/reverted/revertedWithoutReason.ts index 3fa8f7f425..8fcb3fd263 100644 --- a/packages/hardhat-chai-matchers/test/reverted/revertedWithoutReason.ts +++ b/packages/hardhat-chai-matchers/test/reverted/revertedWithoutReason.ts @@ -11,6 +11,7 @@ import { } from "../helpers"; import "../../src/internal/add-chai-matchers"; +import { MatchersContract } from "../contracts"; describe("INTEGRATION: Reverted without reason", function () { describe("with the in-process hardhat network", function () { @@ -27,9 +28,12 @@ describe("INTEGRATION: Reverted without reason", function () { function runTests() { // deploy Matchers contract before each test - let matchers: any; + let matchers: MatchersContract; beforeEach("deploy matchers contract", async function () { - const Matchers = await this.hre.ethers.getContractFactory("Matchers"); + const Matchers = await this.hre.ethers.getContractFactory< + [], + MatchersContract + >("Matchers"); matchers = await Matchers.deploy(); }); @@ -155,12 +159,15 @@ describe("INTEGRATION: Reverted without reason", function () { randomPrivateKey, this.hre.ethers.provider ); + const matchersFromSenderWithoutFunds = matchers.connect( + signer + ) as MatchersContract; // this transaction will fail because of lack of funds, not because of a // revert await expect( expect( - matchers.connect(signer).revertsWithoutReason({ + matchersFromSenderWithoutFunds.revertsWithoutReason({ gasLimit: 1_000_000, }) ).to.not.be.revertedWithoutReason() @@ -179,7 +186,11 @@ describe("INTEGRATION: Reverted without reason", function () { matchers.revertsWithoutReason() ).to.not.be.revertedWithoutReason(); } catch (e: any) { - expect(util.inspect(e)).to.include( + const errorString = util.inspect(e); + expect(errorString).to.include( + "Expected transaction NOT to be reverted without a reason, but it was" + ); + expect(errorString).to.include( path.join("test", "reverted", "revertedWithoutReason.ts") ); diff --git a/packages/hardhat-core/CHANGELOG.md b/packages/hardhat-core/CHANGELOG.md index 6d618fd1f4..0290b5686d 100644 --- a/packages/hardhat-core/CHANGELOG.md +++ b/packages/hardhat-core/CHANGELOG.md @@ -1,5 +1,17 @@ # hardhat +## 2.15.0 + +### Minor Changes + +- 99995d53b: The sample projects now use the new version of the Toolbox + +## 2.14.1 + +### Patch Changes + +- e99498638: Added block numbers for all mainnet hardforks + ## 2.14.0 ### Minor Changes diff --git a/packages/hardhat-core/LICENSE b/packages/hardhat-core/LICENSE index 845dc73d24..9156bcfc0b 100644 --- a/packages/hardhat-core/LICENSE +++ b/packages/hardhat-core/LICENSE @@ -5,7 +5,7 @@ the MIT License as defined below. The MIT License -Copyright (c) 2019 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-core/package.json b/packages/hardhat-core/package.json index b60fde820a..a951330067 100644 --- a/packages/hardhat-core/package.json +++ b/packages/hardhat-core/package.json @@ -1,6 +1,6 @@ { "name": "hardhat", - "version": "2.14.0", + "version": "2.15.0", "author": "Nomic Labs LLC", "license": "MIT", "homepage": "https://hardhat.org", @@ -87,7 +87,8 @@ "eslint-plugin-import": "2.24.1", "eslint-plugin-no-only-tests": "3.0.0", "eslint-plugin-prettier": "3.4.0", - "ethers": "^5.0.0", + "ethers": "^6.1.0", + "ethers-v5": "npm:ethers@5", "mocha": "^10.0.0", "prettier": "2.4.1", "rimraf": "^3.0.2", diff --git a/packages/hardhat-core/sample-projects/javascript-esm/scripts/deploy.js b/packages/hardhat-core/sample-projects/javascript-esm/scripts/deploy.js index 05f8a19cd9..b17ce00262 100644 --- a/packages/hardhat-core/sample-projects/javascript-esm/scripts/deploy.js +++ b/packages/hardhat-core/sample-projects/javascript-esm/scripts/deploy.js @@ -9,15 +9,16 @@ import hre from "hardhat"; const currentTimestampInSeconds = Math.round(Date.now() / 1000); const unlockTime = currentTimestampInSeconds + 60; -const lockedAmount = hre.ethers.utils.parseEther("0.001"); +const lockedAmount = hre.ethers.parseEther("0.001"); -const Lock = await hre.ethers.getContractFactory("Lock"); -const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); +const lock = await ethers.deployContract("Lock", [unlockTime], { + value: lockedAmount, +}); -await lock.deployed(); +await lock.waitForDeployment(); console.log( - `Lock with ${ethers.utils.formatEther( + `Lock with ${ethers.formatEther( lockedAmount - )}ETH and unlock timestamp ${unlockTime} deployed to ${lock.address}` + )}ETH and unlock timestamp ${unlockTime} deployed to ${lock.target}` ); diff --git a/packages/hardhat-core/sample-projects/javascript-esm/test/Lock.js b/packages/hardhat-core/sample-projects/javascript-esm/test/Lock.js index a046170d20..ef8d8745e2 100644 --- a/packages/hardhat-core/sample-projects/javascript-esm/test/Lock.js +++ b/packages/hardhat-core/sample-projects/javascript-esm/test/Lock.js @@ -1,4 +1,7 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { + time, + loadFixture, +} from "@nomicfoundation/hardhat-toolbox/network-helpers.js"; import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs.js"; import { expect } from "chai"; @@ -40,7 +43,7 @@ describe("Lock", function () { deployOneYearLockFixture ); - expect(await ethers.provider.getBalance(lock.address)).to.equal( + expect(await ethers.provider.getBalance(lock.target)).to.equal( lockedAmount ); }); diff --git a/packages/hardhat-core/sample-projects/javascript/scripts/deploy.js b/packages/hardhat-core/sample-projects/javascript/scripts/deploy.js index 745b18d4f7..39c08d67b2 100644 --- a/packages/hardhat-core/sample-projects/javascript/scripts/deploy.js +++ b/packages/hardhat-core/sample-projects/javascript/scripts/deploy.js @@ -10,17 +10,18 @@ async function main() { const currentTimestampInSeconds = Math.round(Date.now() / 1000); const unlockTime = currentTimestampInSeconds + 60; - const lockedAmount = hre.ethers.utils.parseEther("0.001"); + const lockedAmount = hre.ethers.parseEther("0.001"); - const Lock = await hre.ethers.getContractFactory("Lock"); - const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); + const lock = await hre.ethers.deployContract("Lock", [unlockTime], { + value: lockedAmount, + }); - await lock.deployed(); + await lock.waitForDeployment(); console.log( - `Lock with ${ethers.utils.formatEther( + `Lock with ${ethers.formatEther( lockedAmount - )}ETH and unlock timestamp ${unlockTime} deployed to ${lock.address}` + )}ETH and unlock timestamp ${unlockTime} deployed to ${lock.target}` ); } diff --git a/packages/hardhat-core/sample-projects/javascript/test/Lock.js b/packages/hardhat-core/sample-projects/javascript/test/Lock.js index 6a161e6d44..f0e6ba1b2c 100644 --- a/packages/hardhat-core/sample-projects/javascript/test/Lock.js +++ b/packages/hardhat-core/sample-projects/javascript/test/Lock.js @@ -1,7 +1,7 @@ const { time, loadFixture, -} = require("@nomicfoundation/hardhat-network-helpers"); +} = require("@nomicfoundation/hardhat-toolbox/network-helpers"); const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); const { expect } = require("chai"); @@ -43,7 +43,7 @@ describe("Lock", function () { deployOneYearLockFixture ); - expect(await ethers.provider.getBalance(lock.address)).to.equal( + expect(await ethers.provider.getBalance(lock.target)).to.equal( lockedAmount ); }); diff --git a/packages/hardhat-core/sample-projects/typescript/scripts/deploy.ts b/packages/hardhat-core/sample-projects/typescript/scripts/deploy.ts index cf9c177ea7..181925391f 100644 --- a/packages/hardhat-core/sample-projects/typescript/scripts/deploy.ts +++ b/packages/hardhat-core/sample-projects/typescript/scripts/deploy.ts @@ -4,15 +4,18 @@ async function main() { const currentTimestampInSeconds = Math.round(Date.now() / 1000); const unlockTime = currentTimestampInSeconds + 60; - const lockedAmount = ethers.utils.parseEther("0.001"); + const lockedAmount = ethers.parseEther("0.001"); - const Lock = await ethers.getContractFactory("Lock"); - const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); + const lock = await ethers.deployContract("Lock", [unlockTime], { + value: lockedAmount, + }); - await lock.deployed(); + await lock.waitForDeployment(); console.log( - `Lock with ${ethers.utils.formatEther(lockedAmount)}ETH and unlock timestamp ${unlockTime} deployed to ${lock.address}` + `Lock with ${ethers.formatEther( + lockedAmount + )}ETH and unlock timestamp ${unlockTime} deployed to ${lock.target}` ); } diff --git a/packages/hardhat-core/sample-projects/typescript/test/Lock.ts b/packages/hardhat-core/sample-projects/typescript/test/Lock.ts index 92e0dc7f40..a6e866b400 100644 --- a/packages/hardhat-core/sample-projects/typescript/test/Lock.ts +++ b/packages/hardhat-core/sample-projects/typescript/test/Lock.ts @@ -1,4 +1,7 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { + time, + loadFixture, +} from "@nomicfoundation/hardhat-toolbox/network-helpers"; import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; import { expect } from "chai"; import { ethers } from "hardhat"; @@ -41,7 +44,7 @@ describe("Lock", function () { deployOneYearLockFixture ); - expect(await ethers.provider.getBalance(lock.address)).to.equal( + expect(await ethers.provider.getBalance(lock.target)).to.equal( lockedAmount ); }); diff --git a/packages/hardhat-core/scripts/test-recent-mainnet-block.ts b/packages/hardhat-core/scripts/test-recent-mainnet-block.ts index d7c64ebab0..d677218f9c 100644 --- a/packages/hardhat-core/scripts/test-recent-mainnet-block.ts +++ b/packages/hardhat-core/scripts/test-recent-mainnet-block.ts @@ -4,11 +4,11 @@ import { makeForkClient } from "../src/internal/hardhat-network/provider/utils/m import { runFullBlock } from "../test/internal/hardhat-network/provider/utils/runFullBlock"; async function main() { - const rpcUrl = process.env.INFURA_URL; + const rpcUrl = process.env.ALCHEMY_URL; if (rpcUrl === undefined || rpcUrl === "") { console.error( - "[test-recent-mainnet-block] Missing INFURA_URL environment variable" + "[test-recent-mainnet-block] Missing ALCHEMY_URL environment variable" ); process.exit(1); } diff --git a/packages/hardhat-core/src/builtin-tasks/compile.ts b/packages/hardhat-core/src/builtin-tasks/compile.ts index 9575321e39..140d7ec1dc 100644 --- a/packages/hardhat-core/src/builtin-tasks/compile.ts +++ b/packages/hardhat-core/src/builtin-tasks/compile.ts @@ -405,27 +405,19 @@ subtask(TASK_COMPILE_SOLIDITY_COMPILE_JOBS) log(`Compiling ${compilationJobs.length} jobs`); - const versionList: string[] = []; for (const job of compilationJobs) { const solcVersion = job.getSolcConfig().version; - if (!versionList.includes(solcVersion)) { - // versions older than 0.4.11 don't work with hardhat - // see issue https://github.com/nomiclabs/hardhat/issues/2004 - if ( - semver.lt(solcVersion, COMPILE_TASK_FIRST_SOLC_VERSION_SUPPORTED) - ) { - throw new HardhatError( - ERRORS.BUILTIN_TASKS.COMPILE_TASK_UNSUPPORTED_SOLC_VERSION, - { - version: solcVersion, - firstSupportedVersion: - COMPILE_TASK_FIRST_SOLC_VERSION_SUPPORTED, - } - ); - } - - versionList.push(solcVersion); + // versions older than 0.4.11 don't work with hardhat + // see issue https://github.com/nomiclabs/hardhat/issues/2004 + if (semver.lt(solcVersion, COMPILE_TASK_FIRST_SOLC_VERSION_SUPPORTED)) { + throw new HardhatError( + ERRORS.BUILTIN_TASKS.COMPILE_TASK_UNSUPPORTED_SOLC_VERSION, + { + version: solcVersion, + firstSupportedVersion: COMPILE_TASK_FIRST_SOLC_VERSION_SUPPORTED, + } + ); } } diff --git a/packages/hardhat-core/src/common/bigInt.ts b/packages/hardhat-core/src/common/bigInt.ts index 95cd0958af..5546a488bd 100644 --- a/packages/hardhat-core/src/common/bigInt.ts +++ b/packages/hardhat-core/src/common/bigInt.ts @@ -1,4 +1,4 @@ -import type { BigNumber as EthersBigNumberType } from "ethers"; +import type { BigNumber as EthersBigNumberType } from "ethers-v5"; // eslint-disable-next-line import/no-extraneous-dependencies import type { BigNumber as BigNumberJsType } from "bignumber.js"; // eslint-disable-next-line import/no-extraneous-dependencies diff --git a/packages/hardhat-core/src/internal/cli/project-creation.ts b/packages/hardhat-core/src/internal/cli/project-creation.ts index 513438b2db..760d3c0d91 100644 --- a/packages/hardhat-core/src/internal/cli/project-creation.ts +++ b/packages/hardhat-core/src/internal/cli/project-creation.ts @@ -41,24 +41,22 @@ type SampleProjectTypeCreationAction = const HARDHAT_PACKAGE_NAME = "hardhat"; const PROJECT_DEPENDENCIES: Dependencies = { - "@nomicfoundation/hardhat-toolbox": "^2.0.0", + "@nomicfoundation/hardhat-toolbox": "^3.0.0", }; const PEER_DEPENDENCIES: Dependencies = { - hardhat: "^2.11.1", + hardhat: "^2.14.0", "@nomicfoundation/hardhat-network-helpers": "^1.0.0", - "@nomicfoundation/hardhat-chai-matchers": "^1.0.0", - "@nomiclabs/hardhat-ethers": "^2.0.0", - "@nomiclabs/hardhat-etherscan": "^3.0.0", + "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@nomicfoundation/hardhat-verify": "^1.0.0", chai: "^4.2.0", - ethers: "^5.4.7", + ethers: "^6.4.0", "hardhat-gas-reporter": "^1.0.8", "solidity-coverage": "^0.8.0", - "@typechain/hardhat": "^6.1.2", + "@typechain/hardhat": "^8.0.0", typechain: "^8.1.0", - "@typechain/ethers-v5": "^10.1.0", - "@ethersproject/abi": "^5.4.7", - "@ethersproject/providers": "^5.4.7", + "@typechain/ethers-v6": "^0.4.0", }; const TYPESCRIPT_DEPENDENCIES: Dependencies = {}; diff --git a/packages/hardhat-core/src/internal/core/config/default-config.ts b/packages/hardhat-core/src/internal/core/config/default-config.ts index ec014daf62..b4f2a90e9a 100644 --- a/packages/hardhat-core/src/internal/core/config/default-config.ts +++ b/packages/hardhat-core/src/internal/core/config/default-config.ts @@ -60,13 +60,22 @@ export const defaultHardhatNetworkParams: Omit< 1, // mainnet { hardforkHistory: new Map([ - [HardforkName.BYZANTIUM, 4370000], - [HardforkName.CONSTANTINOPLE, 7280000], - [HardforkName.PETERSBURG, 7280000], - [HardforkName.ISTANBUL, 9069000], - [HardforkName.MUIR_GLACIER, 9200000], - [HardforkName.BERLIN, 12244000], - [HardforkName.LONDON, 12965000], + [HardforkName.FRONTIER, 0], + [HardforkName.HOMESTEAD, 1_150_000], + [HardforkName.DAO, 1_920_000], + [HardforkName.TANGERINE_WHISTLE, 2_463_000], + [HardforkName.SPURIOUS_DRAGON, 2_675_000], + [HardforkName.BYZANTIUM, 4_370_000], + [HardforkName.CONSTANTINOPLE, 7_280_000], + [HardforkName.PETERSBURG, 7_280_000], + [HardforkName.ISTANBUL, 9_069_000], + [HardforkName.MUIR_GLACIER, 9_200_000], + [HardforkName.BERLIN, 1_2244_000], + [HardforkName.LONDON, 12_965_000], + [HardforkName.ARROW_GLACIER, 13_773_000], + [HardforkName.GRAY_GLACIER, 15_050_000], + [HardforkName.MERGE, 15_537_394], + [HardforkName.SHANGHAI, 17_034_870], ]), }, ], diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts index 98c49665ca..0152e16d58 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts @@ -2387,6 +2387,22 @@ Hardhat Network's forking functionality only works with blocks from at least spu // know anything about the txs in the current block } + originalCommon = (this._vm as any)._common; + + (this._vm as any)._common = Common.custom( + { + chainId: + this._forkBlockNumber === undefined || + blockContext.header.number >= this._forkBlockNumber + ? this._configChainId + : this._forkNetworkId, + networkId: this._forkNetworkId ?? this._configNetworkId, + }, + { + hardfork: this._selectHardfork(blockContext.header.number), + } + ); + // If this VM is running without EIP4895, but the block has withdrawals, // we remove them and the withdrawal root from the block if ( @@ -2438,22 +2454,6 @@ Hardhat Network's forking functionality only works with blocks from at least spu (blockContext.header as any).baseFeePerGas = 0n; } - originalCommon = (this._vm as any)._common; - - (this._vm as any)._common = Common.custom( - { - chainId: - this._forkBlockNumber === undefined || - blockContext.header.number >= this._forkBlockNumber - ? this._configChainId - : this._forkNetworkId, - networkId: this._forkNetworkId ?? this._configNetworkId, - }, - { - hardfork: this._selectHardfork(blockContext.header.number), - } - ); - return await this._vm.runTx({ block: blockContext, tx, diff --git a/packages/hardhat-core/test/fixture-projects/hardhat-network-fork-from-old-block/hardhat.config.js b/packages/hardhat-core/test/fixture-projects/hardhat-network-fork-from-old-block/hardhat.config.js index e8832e44ec..41ddc2d38c 100644 --- a/packages/hardhat-core/test/fixture-projects/hardhat-network-fork-from-old-block/hardhat.config.js +++ b/packages/hardhat-core/test/fixture-projects/hardhat-network-fork-from-old-block/hardhat.config.js @@ -2,7 +2,7 @@ module.exports = { networks: { hardhat: { forking: { - url: process.env.INFURA_URL, + url: process.env.ALCHEMY_URL, blockNumber: 2463000, }, }, diff --git a/packages/hardhat-core/test/fixture-projects/hardhat-network-fork-tangerine-whistle/hardhat.config.js b/packages/hardhat-core/test/fixture-projects/hardhat-network-fork-tangerine-whistle/hardhat.config.js index 6f610736cc..ea9c1f0cd4 100644 --- a/packages/hardhat-core/test/fixture-projects/hardhat-network-fork-tangerine-whistle/hardhat.config.js +++ b/packages/hardhat-core/test/fixture-projects/hardhat-network-fork-tangerine-whistle/hardhat.config.js @@ -3,7 +3,7 @@ module.exports = { hardhat: { hardfork: "tangerineWhistle", forking: { - url: process.env.INFURA_URL, + url: process.env.ALCHEMY_URL, }, }, }, diff --git a/packages/hardhat-core/test/internal/core/config/default-config.ts b/packages/hardhat-core/test/internal/core/config/default-config.ts new file mode 100644 index 0000000000..9aa9ed455c --- /dev/null +++ b/packages/hardhat-core/test/internal/core/config/default-config.ts @@ -0,0 +1,23 @@ +import { assert } from "chai"; + +import { defaultHardhatNetworkParams } from "../../../../src/internal/core/config/default-config"; +import { HardforkName } from "../../../../src/internal/util/hardforks"; + +describe("Default config", function () { + it("should include block numbers for all hardforks", async function () { + const mainnetChainConfig = defaultHardhatNetworkParams.chains.get(1); + + if (mainnetChainConfig === undefined) { + assert.fail("Mainnet entry should exist"); + } + + for (const hardfork of Object.values(HardforkName)) { + const hardforkHistoryEntry = + mainnetChainConfig.hardforkHistory.get(hardfork); + assert.isDefined( + hardforkHistoryEntry, + `No hardfork history entry for ${hardfork}` + ); + } + }); +}); diff --git a/packages/hardhat-core/test/internal/hardhat-network/helpers/ethers-provider-wrapper.ts b/packages/hardhat-core/test/internal/hardhat-network/helpers/ethers-provider-wrapper.ts deleted file mode 100644 index 3a5740cf25..0000000000 --- a/packages/hardhat-core/test/internal/hardhat-network/helpers/ethers-provider-wrapper.ts +++ /dev/null @@ -1,34 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import { ethers } from "ethers"; - -import { EthereumProvider } from "../../../../src/types"; - -// This class has been copied from @nomiclabs/hardhat-ethers package to avoid circular dependency - -export class EthersProviderWrapper extends ethers.providers.JsonRpcProvider { - private readonly _hardhatProvider: EthereumProvider; - - constructor(hardhatProvider: EthereumProvider) { - super(); - this._hardhatProvider = hardhatProvider; - } - - public async send(method: string, params: any): Promise { - const result = await this._hardhatProvider.send(method, params); - - // We replicate ethers' behavior. - this.emit("debug", { - action: "send", - request: { - id: 42, - jsonrpc: "2.0", - method, - params, - }, - response: result, - provider: this, - }); - - return result; - } -} diff --git a/packages/hardhat-core/test/internal/hardhat-network/helpers/providers.ts b/packages/hardhat-core/test/internal/hardhat-network/helpers/providers.ts index 3719c67d6d..cb37eb70e3 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/helpers/providers.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/helpers/providers.ts @@ -126,11 +126,11 @@ export const FORKED_PROVIDERS: Array<{ useProvider: (options?: UseProviderOptions) => void; }> = []; -if (INFURA_URL !== undefined) { - const url = INFURA_URL; +if (ALCHEMY_URL !== undefined) { + const url = ALCHEMY_URL; PROVIDERS.push({ - name: "Infura Forked", + name: "Alchemy Forked", isFork: true, isJsonRpc: false, networkId: DEFAULT_NETWORK_ID, @@ -146,7 +146,7 @@ if (INFURA_URL !== undefined) { }); INTERVAL_MINING_PROVIDERS.push({ - name: "Infura Forked", + name: "Alchemy Forked", isFork: true, isJsonRpc: false, useProvider: (options: UseProviderOptions = {}) => { @@ -165,7 +165,7 @@ if (INFURA_URL !== undefined) { }); FORKED_PROVIDERS.push({ - rpcProvider: "Infura", + rpcProvider: "Alchemy", jsonRpcUrl: url, useProvider: (options: UseProviderOptions = {}) => { useProvider({ @@ -178,11 +178,11 @@ if (INFURA_URL !== undefined) { }); } -if (ALCHEMY_URL !== undefined) { - const url = ALCHEMY_URL; +if (INFURA_URL !== undefined) { + const url = INFURA_URL; FORKED_PROVIDERS.push({ - rpcProvider: "Alchemy", + rpcProvider: "Infura", jsonRpcUrl: url, useProvider: (options: UseProviderOptions = {}) => { useProvider({ diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/baseFeePerGas.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/baseFeePerGas.ts index 7e013b3ad5..2c5bd0a8ad 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/baseFeePerGas.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/baseFeePerGas.ts @@ -10,7 +10,7 @@ import { } from "../../../../src/internal/core/jsonrpc/types/base-types"; import { EthereumProvider } from "../../../../src/types"; import { makeForkClient } from "../../../../src/internal/hardhat-network/provider/utils/makeForkClient"; -import { INFURA_URL } from "../../../setup"; +import { ALCHEMY_URL } from "../../../setup"; import { rpcToBlockData } from "../../../../src/internal/hardhat-network/provider/fork/rpcToBlockData"; async function getLatestBaseFeePerGas(provider: EthereumProvider) { @@ -102,14 +102,14 @@ describe("Block's baseFeePerGas", function () { useProvider(); it("Should use the same base fee as the one remote networks's forked block", async function () { - if (INFURA_URL === undefined) { + if (ALCHEMY_URL === undefined) { this.skip(); return; } const blockNumber = await this.provider.send("eth_blockNumber"); const { forkClient } = await makeForkClient({ - jsonRpcUrl: INFURA_URL!, + jsonRpcUrl: ALCHEMY_URL!, }); const remoteLatestBlockBaseFeePerGas = diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/fork/ForkBlockchain.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/fork/ForkBlockchain.ts index 9c6927946c..30f37b6a28 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/fork/ForkBlockchain.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/fork/ForkBlockchain.ts @@ -11,7 +11,7 @@ import { JsonRpcClient } from "../../../../../src/internal/hardhat-network/jsonr import { ForkBlockchain } from "../../../../../src/internal/hardhat-network/provider/fork/ForkBlockchain"; import { randomHashBuffer } from "../../../../../src/internal/hardhat-network/provider/utils/random"; import { makeForkClient } from "../../../../../src/internal/hardhat-network/provider/utils/makeForkClient"; -import { INFURA_URL } from "../../../../setup"; +import { ALCHEMY_URL } from "../../../../setup"; import { createTestLog, createTestReceipt, @@ -45,14 +45,14 @@ describe("ForkBlockchain", () => { } before(async function () { - if (INFURA_URL === undefined) { + if (ALCHEMY_URL === undefined) { this.skip(); return; } }); beforeEach(async () => { - const clientResult = await makeForkClient({ jsonRpcUrl: INFURA_URL! }); + const clientResult = await makeForkClient({ jsonRpcUrl: ALCHEMY_URL! }); client = clientResult.forkClient; forkBlockNumber = clientResult.forkBlockNumber; diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/fork/ForkStateManager.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/fork/ForkStateManager.ts index f133a082db..c5b4688013 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/fork/ForkStateManager.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/fork/ForkStateManager.ts @@ -17,7 +17,7 @@ import { } from "../../../../../src/internal/hardhat-network/provider/utils/random"; import { makeForkClient } from "../../../../../src/internal/hardhat-network/provider/utils/makeForkClient"; import { keccak256 } from "../../../../../src/internal/util/keccak"; -import { INFURA_URL } from "../../../../setup"; +import { ALCHEMY_URL } from "../../../../setup"; import { DAI_ADDRESS, DAI_TOTAL_SUPPLY_STORAGE_POSITION, @@ -32,14 +32,14 @@ describe("ForkStateManager", () => { let fsm: ForkStateManager; before(async function () { - if (INFURA_URL === undefined) { + if (ALCHEMY_URL === undefined) { this.skip(); return; } }); beforeEach(async () => { - const clientResult = await makeForkClient({ jsonRpcUrl: INFURA_URL! }); + const clientResult = await makeForkClient({ jsonRpcUrl: ALCHEMY_URL! }); client = clientResult.forkClient; forkBlockNumber = clientResult.forkBlockNumber; diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/forked-provider.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/forked-provider.ts index 9606e5e8b7..fcdbe19bc7 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/forked-provider.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/forked-provider.ts @@ -9,7 +9,7 @@ import { } from "../../../../src/internal/core/jsonrpc/types/base-types"; import { InvalidInputError } from "../../../../src/internal/core/providers/errors"; import { LegacyRpcTransactionOutput } from "../../../../src/internal/hardhat-network/provider/output"; -import { INFURA_URL } from "../../../setup"; +import { ALCHEMY_URL } from "../../../setup"; import { workaroundWindowsCiFailures } from "../../../utils/workaround-windows-ci-failures"; import { assertQuantity, @@ -485,14 +485,14 @@ describe("Forked provider", function () { describe("blocks timestamps", () => { it("should use a timestamp relative to the forked block timestamp", async function () { - if (INFURA_URL === undefined) { + if (ALCHEMY_URL === undefined) { this.skip(); } await this.provider.send("hardhat_reset", [ { forking: { - jsonRpcUrl: INFURA_URL, + jsonRpcUrl: ALCHEMY_URL, blockNumber: 11565019, // first block of 2021 }, }, diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/hardhat-network-options.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/hardhat-network-options.ts index f1cf6b534c..2d25a991cc 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/hardhat-network-options.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/hardhat-network-options.ts @@ -10,7 +10,7 @@ import { HardhatNetworkConfig } from "../../../../src/types"; import { useEnvironment } from "../../../helpers/environment"; import { expectErrorAsync } from "../../../helpers/errors"; import { useFixtureProject } from "../../../helpers/project"; -import { INFURA_URL } from "../../../setup"; +import { ALCHEMY_URL } from "../../../setup"; describe("Hardhat Network special options", function () { describe("allowUnlimitedContractSize", function () { @@ -111,7 +111,7 @@ describe("Hardhat Network special options", function () { }); describe("When forking", function () { - if (INFURA_URL === undefined) { + if (ALCHEMY_URL === undefined) { return; } diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/interval-mining-provider.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/interval-mining-provider.ts index 200efbbfed..755132b951 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/interval-mining-provider.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/interval-mining-provider.ts @@ -2,7 +2,7 @@ import { assert } from "chai"; import sinon from "sinon"; import { rpcQuantityToNumber } from "../../../../src/internal/core/jsonrpc/types/base-types"; -import { INFURA_URL } from "../../../setup"; +import { ALCHEMY_URL } from "../../../setup"; import { workaroundWindowsCiFailures } from "../../../utils/workaround-windows-ci-failures"; import { setCWD } from "../helpers/cwd"; import { INTERVAL_MINING_PROVIDERS } from "../helpers/providers"; @@ -69,7 +69,7 @@ describe("Interval mining provider", function () { await this.provider.send("hardhat_reset", [ { forking: { - jsonRpcUrl: INFURA_URL, + jsonRpcUrl: ALCHEMY_URL, blockNumber: safeBlockInThePast, }, }, diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/debug.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/debug.ts index e422038e64..55a88c8050 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/debug.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/debug.ts @@ -13,7 +13,7 @@ import { trace as mainnetReturnsDataTrace } from "../../../../fixture-debug-trac import { trace as mainnetReturnsDataTraceGeth } from "../../../../fixture-debug-traces/mainnetReturnsDataTraceGeth"; import { trace as mainnetRevertTrace } from "../../../../fixture-debug-traces/mainnetRevertTrace"; import { trace as modifiesStateTrace } from "../../../../fixture-debug-traces/modifiesStateTrace"; -import { INFURA_URL } from "../../../../setup"; +import { ALCHEMY_URL } from "../../../../setup"; import { assertInvalidInputError } from "../../helpers/assertions"; import { FORK_TESTS_CACHE_PATH } from "../../helpers/constants"; import { EXAMPLE_CONTRACT } from "../../helpers/contracts"; @@ -191,11 +191,11 @@ describe.skip("Debug module", function () { let provider: EthereumProvider; beforeEach(function () { - if (INFURA_URL === undefined) { + if (ALCHEMY_URL === undefined) { this.skip(); } const forkConfig: ForkConfig = { - jsonRpcUrl: INFURA_URL!, + jsonRpcUrl: ALCHEMY_URL!, blockNumber: 11_954_000, }; @@ -315,11 +315,11 @@ describe.skip("Debug module", function () { let provider: EthereumProvider; beforeEach(function () { - if (INFURA_URL === undefined) { + if (ALCHEMY_URL === undefined) { this.skip(); } const forkConfig: ForkConfig = { - jsonRpcUrl: INFURA_URL!, + jsonRpcUrl: ALCHEMY_URL!, blockNumber: 15_204_358, }; diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/getTransactionByHash.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/getTransactionByHash.ts index 02533498d1..efc07c2a5e 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/getTransactionByHash.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/getTransactionByHash.ts @@ -19,7 +19,7 @@ import { EIP1559RpcTransactionOutput, LegacyRpcTransactionOutput, } from "../../../../../../../src/internal/hardhat-network/provider/output"; -import { INFURA_URL } from "../../../../../../setup"; +import { ALCHEMY_URL } from "../../../../../../setup"; import { workaroundWindowsCiFailures } from "../../../../../../utils/workaround-windows-ci-failures"; import { assertAccessListTransaction, @@ -324,13 +324,13 @@ describe("Eth module", function () { }); it("should get an existing transaction from goerli", async function () { - if (!isFork || INFURA_URL === undefined) { + if (!isFork || ALCHEMY_URL === undefined) { this.skip(); } - const goerliUrl = INFURA_URL.replace("mainnet", "goerli"); + const goerliUrl = ALCHEMY_URL.replace("mainnet", "goerli"); // If "mainnet" is not present the replacement failed so we skip the test - if (goerliUrl === INFURA_URL) { + if (goerliUrl === ALCHEMY_URL) { this.skip(); } diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/subscribe.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/subscribe.ts index 18e60ca840..a6c1e1ce81 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/subscribe.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/subscribe.ts @@ -182,12 +182,12 @@ describe("Eth module", function () { DEFAULT_ACCOUNTS_ADDRESSES[0] ); - const abiEncoder = new ethers.utils.Interface(contractA.abi); + const abiEncoder = new ethers.Interface(contractA.abi); const filterId = await this.provider.send("eth_subscribe", [ "logs", { address, - topic: abiEncoder.getEventTopic("TokensMinted"), + topic: abiEncoder.getEvent("TokensMinted")?.topicHash, }, ]); diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/websocket.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/websocket.ts index f3db1e7613..ef3de84d18 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/websocket.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/websocket.ts @@ -342,14 +342,12 @@ describe("Eth module", function () { }); describe("ethers.WebSocketProvider", function () { - let provider: ethers.providers.WebSocketProvider; + let provider: ethers.WebSocketProvider; beforeEach(async function () { if (this.serverInfo !== undefined) { const { address, port } = this.serverInfo; - provider = new ethers.providers.WebSocketProvider( - `ws://${address}:${port}` - ); + provider = new ethers.WebSocketProvider(`ws://${address}:${port}`); } else { this.skip(); } @@ -375,7 +373,7 @@ describe("Eth module", function () { ); await sleep(100); - const signer = provider.getSigner(); + const signer = await provider.getSigner(); await signer.sendTransaction({ to: await signer.getAddress(), }); @@ -384,8 +382,8 @@ describe("Eth module", function () { }); it("contract events work", async function () { - const signer = provider.getSigner(); - const Factory = new ethers.ContractFactory( + const signer = await provider.getSigner(); + const Factory = new ethers.ContractFactory<[], ethers.Contract>( EXAMPLE_CONTRACT.abi, EXAMPLE_CONTRACT.bytecode, signer diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/hardhat.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/hardhat.ts index 3f3e06a385..bd24a7de1b 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/hardhat.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/hardhat.ts @@ -11,7 +11,7 @@ import { } from "../../../../../src/internal/core/jsonrpc/types/base-types"; import { CompilerOutputContract } from "../../../../../src/types/artifacts"; import { expectErrorAsync } from "../../../../helpers/errors"; -import { INFURA_URL } from "../../../../setup"; +import { ALCHEMY_URL } from "../../../../setup"; import { workaroundWindowsCiFailures } from "../../../../utils/workaround-windows-ci-failures"; import { assertInternalError, @@ -1305,7 +1305,7 @@ describe("Hardhat module", function () { describe("hardhat_reset", function () { before(function () { - if (INFURA_URL === undefined) { + if (ALCHEMY_URL === undefined) { this.skip(); } }); @@ -1334,7 +1334,7 @@ describe("Hardhat module", function () { await assertInvalidArgumentsError(this.provider, "hardhat_reset", [ { forking: { - jsonRpcUrl: INFURA_URL, + jsonRpcUrl: ALCHEMY_URL, blockNumber: "0", }, }, @@ -1345,7 +1345,7 @@ describe("Hardhat module", function () { const result = await this.provider.send("hardhat_reset", [ { forking: { - jsonRpcUrl: INFURA_URL, + jsonRpcUrl: ALCHEMY_URL, blockNumber: safeBlockInThePast, }, }, @@ -1435,7 +1435,7 @@ describe("Hardhat module", function () { await this.provider.send("hardhat_reset", [ { forking: { - jsonRpcUrl: INFURA_URL, + jsonRpcUrl: ALCHEMY_URL, blockNumber: safeBlockInThePast, }, }, @@ -1448,13 +1448,13 @@ describe("Hardhat module", function () { await this.provider.send("hardhat_reset", [ { forking: { - jsonRpcUrl: INFURA_URL, + jsonRpcUrl: ALCHEMY_URL, blockNumber: safeBlockInThePast, }, }, ]); await this.provider.send("hardhat_reset", [ - { forking: { jsonRpcUrl: INFURA_URL } }, + { forking: { jsonRpcUrl: ALCHEMY_URL } }, ]); // This condition is rather loose as Infura can sometimes return @@ -1483,7 +1483,7 @@ describe("Hardhat module", function () { await this.provider.send("hardhat_reset", [ { forking: { - jsonRpcUrl: INFURA_URL, + jsonRpcUrl: ALCHEMY_URL, blockNumber: safeBlockInThePast, }, }, @@ -1495,7 +1495,7 @@ describe("Hardhat module", function () { await this.provider.send("hardhat_reset", [ { forking: { - jsonRpcUrl: INFURA_URL, + jsonRpcUrl: ALCHEMY_URL, blockNumber: safeBlockInThePast, }, }, @@ -1701,7 +1701,7 @@ describe("Hardhat module", function () { describe("hardhat_setCode", function () { let contractNine: CompilerOutputContract; - let abiEncoder: ethers.utils.Interface; + let abiEncoder: ethers.Interface; before(async function () { [ , @@ -1715,7 +1715,7 @@ describe("Hardhat module", function () { function returnNine() public pure returns (int) { return 9; } } `); - abiEncoder = new ethers.utils.Interface(contractNine.abi); + abiEncoder = new ethers.Interface(contractNine.abi); }); it("should reject an invalid address", async function () { @@ -2182,7 +2182,7 @@ describe("Hardhat module", function () { ]); // Assert: Verify that the contract retrieves the modified value. - const abiEncoder = new ethers.utils.Interface(storageContract.abi); + const abiEncoder = new ethers.Interface(storageContract.abi); assert.equal( await this.provider.send("eth_call", [ { diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts index 1914b7010a..430721e182 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts @@ -23,7 +23,7 @@ import { HardhatNetworkChainConfig, HardhatNetworkChainsConfig, } from "../../../../src/types"; -import { INFURA_URL } from "../../../setup"; +import { ALCHEMY_URL } from "../../../setup"; import { assertQuantity } from "../helpers/assertions"; import { EMPTY_ACCOUNT_ADDRESS, @@ -694,7 +694,7 @@ describe("HardhatNode", () => { }); describe("full block", function () { - if (INFURA_URL === undefined) { + if (ALCHEMY_URL === undefined) { return; } @@ -703,43 +703,44 @@ describe("HardhatNode", () => { // its receipts contain the state root, and we can't compute it { networkName: "mainnet", - url: INFURA_URL, + url: ALCHEMY_URL, blockToRun: 4370001n, chainId: 1, }, { networkName: "mainnet", - url: INFURA_URL, + url: ALCHEMY_URL, blockToRun: 7280001n, chainId: 1, }, { networkName: "mainnet", - url: INFURA_URL, + url: ALCHEMY_URL, blockToRun: 9069001n, chainId: 1, }, { networkName: "mainnet", - url: INFURA_URL, + url: ALCHEMY_URL, blockToRun: 9300077n, chainId: 1, }, { networkName: "mainnet", - url: INFURA_URL, + url: ALCHEMY_URL, blockToRun: 17_050_001n, // post-shanghai chainId: 1, }, { networkName: "goerli", - url: INFURA_URL.replace("mainnet", "goerli"), + url: ALCHEMY_URL.replace("mainnet", "goerli"), blockToRun: 7728449n, // this block has both EIP-2930 and EIP-1559 txs chainId: 5, }, { networkName: "sepolia", - url: INFURA_URL.replace("mainnet", "sepolia"), + url: ALCHEMY_URL.replace("alchemyapi.io", "g.alchemy.com") // temporary fix until we fix our github secret + .replace("mainnet", "sepolia"), blockToRun: 3095000n, // this block is post-shanghai chainId: 11155111, }, @@ -844,7 +845,7 @@ describe("HardhatNode", () => { block: bigint, targetNode: HardhatNode ): Promise { - const contractInterface = new ethers.utils.Interface([ + const contractInterface = new ethers.Interface([ "function Hello() public pure returns (string)", ]); @@ -871,7 +872,7 @@ describe("HardhatNode", () => { describe("should run calls in the right hardfork context", async function () { this.timeout(10000); before(function () { - if (INFURA_URL === undefined) { + if (ALCHEMY_URL === undefined) { this.skip(); return; } @@ -890,7 +891,7 @@ describe("HardhatNode", () => { networkId: 1, hardfork: "london", forkConfig: { - jsonRpcUrl: INFURA_URL!, + jsonRpcUrl: ALCHEMY_URL!, blockNumber: Number(eip1559ActivationBlock), }, forkCachePath: FORK_TESTS_CACHE_PATH, @@ -1046,7 +1047,7 @@ describe("HardhatNode", () => { }); it("should support a historical call in the context of a block added via mineBlocks()", async function () { - if (INFURA_URL === undefined) { + if (ALCHEMY_URL === undefined) { this.skip(); return; } @@ -1056,7 +1057,7 @@ describe("HardhatNode", () => { networkId: 1, hardfork: "london", forkConfig: { - jsonRpcUrl: INFURA_URL, + jsonRpcUrl: ALCHEMY_URL, blockNumber: 12965000, // eip1559ActivationBlock }, forkCachePath: FORK_TESTS_CACHE_PATH, diff --git a/packages/hardhat-ethers/.eslintrc.js b/packages/hardhat-ethers/.eslintrc.js index 44ed8ed6d5..7f3a838a47 100644 --- a/packages/hardhat-ethers/.eslintrc.js +++ b/packages/hardhat-ethers/.eslintrc.js @@ -4,4 +4,7 @@ module.exports = { project: `${__dirname}/src/tsconfig.json`, sourceType: "module", }, + rules: { + "@typescript-eslint/no-non-null-assertion": "error" + } }; diff --git a/packages/hardhat-ethers/.mocharc.json b/packages/hardhat-ethers/.mocharc.json index 775e35460d..4cc970a890 100644 --- a/packages/hardhat-ethers/.mocharc.json +++ b/packages/hardhat-ethers/.mocharc.json @@ -1,6 +1,6 @@ { "require": "ts-node/register/files", - "file": "../common/run-with-ganache", + "file": "../common/run-with-hardhat", "ignore": ["test/fixture-projects/**/*"], "timeout": 10000 } diff --git a/packages/hardhat-ethers/.prettierignore b/packages/hardhat-ethers/.prettierignore index c66ddcfc8a..b78ac2c14d 100644 --- a/packages/hardhat-ethers/.prettierignore +++ b/packages/hardhat-ethers/.prettierignore @@ -9,4 +9,5 @@ /build-test /test/fixture-projects/**/artifacts /test/fixture-projects/**/cache +/test/fixture-projects/generated-fixtures CHANGELOG.md diff --git a/packages/hardhat-ethers/CHANGELOG.md b/packages/hardhat-ethers/CHANGELOG.md index ac99cf9573..d223e52215 100644 --- a/packages/hardhat-ethers/CHANGELOG.md +++ b/packages/hardhat-ethers/CHANGELOG.md @@ -1,5 +1,17 @@ # @nomiclabs/hardhat-ethers +## 3.0.2 + +### Patch Changes + +- eb1ae069b: Fixed a problem when `waitForDeployment` was used in live networks. + +## 3.0.1 + +### Patch Changes + +- a9c159f96: The `helper.deployContract` now accepts transaction overrides + ## 2.2.3 ### Patch Changes diff --git a/packages/hardhat-ethers/LICENSE b/packages/hardhat-ethers/LICENSE index 3b8858c555..3b7e8c7eab 100644 --- a/packages/hardhat-ethers/LICENSE +++ b/packages/hardhat-ethers/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-ethers/README.md b/packages/hardhat-ethers/README.md index 47ee313acc..47f8f973fa 100644 --- a/packages/hardhat-ethers/README.md +++ b/packages/hardhat-ethers/README.md @@ -1,4 +1,4 @@ -[![npm](https://img.shields.io/npm/v/@nomiclabs/hardhat-ethers.svg)](https://www.npmjs.com/package/@nomiclabs/hardhat-ethers) [![hardhat](https://hardhat.org/buidler-plugin-badge.svg?1)](https://hardhat.org) +[![npm](https://img.shields.io/npm/v/@nomicfoundation/hardhat-ethers.svg)](https://www.npmjs.com/package/@nomicfoundation/hardhat-ethers) [![hardhat](https://hardhat.org/buidler-plugin-badge.svg?1)](https://hardhat.org) # hardhat-ethers @@ -11,19 +11,19 @@ This plugin brings to Hardhat the Ethereum library `ethers.js`, which allows you ## Installation ```bash -npm install --save-dev @nomiclabs/hardhat-ethers 'ethers@^5.0.0' +npm install --save-dev @nomicfoundation/hardhat-ethers ethers ``` And add the following statement to your `hardhat.config.js`: ```js -require("@nomiclabs/hardhat-ethers"); +require("@nomicfoundation/hardhat-ethers"); ``` Or, if you are using TypeScript, add this to your `hardhat.config.ts`: ```js -import "@nomiclabs/hardhat-ethers"; +import "@nomicfoundation/hardhat-ethers"; ``` ## Tasks @@ -34,11 +34,11 @@ This plugin creates no additional tasks. This plugins adds an `ethers` object to the Hardhat Runtime Environment. -This object has the [same API](https://docs.ethers.io/v5/single-page/) as `ethers.js`, with some extra Hardhat-specific functionality. +This object has the [same API](https://docs.ethers.org/v6/single-page/) as `ethers.js`, with some extra Hardhat-specific functionality. ### Provider object -A `provider` field is added to `ethers`, which is an [`ethers.providers.Provider`](https://docs.ethers.io/v5/single-page/#/v5/api/providers/provider/) automatically connected to the selected network. +A `provider` field is added to `ethers`, which is an [`ethers.Provider`](https://docs.ethers.org/v6/single-page/#api_providers__Provider) automatically connected to the selected network. ### Helpers @@ -79,9 +79,7 @@ function getContractFactoryFromArtifact(artifact: Artifact, factoryOptions: Fact function getContractAtFromArtifact(artifact: Artifact, address: string, signer?: ethers.Signer): Promise; ``` -The [`Contract`s](https://docs.ethers.io/v5/single-page/#/v5/api/contract/contract/) and [`ContractFactory`s](https://docs.ethers.io/v5/single-page/#/v5/api/contract/contract-factory/) returned by these helpers are connected to the first [signer](https://docs.ethers.io/v5/single-page/#/v5/api/signer/) returned by `getSigners` by default. - -If there is no signer available, `getContractAt` returns [read-only](https://docs.ethers.io/v5/single-page/#/v5/api/contract/contract/-%23-Contract--readonly) contracts. +The [`Contract`s](https://docs.ethers.org/v6/single-page/#api_contract__Contract) and [`ContractFactory`s](https://docs.ethers.org/v6/single-page/#api_contract__ContractFactory) returned by these helpers are connected to the first [signer](https://docs.ethers.org/v6/single-page/#api_providers__Signer) returned by `getSigners` by default. ## Usage @@ -90,16 +88,15 @@ There are no additional steps you need to take for this plugin to work. Install it and access ethers through the Hardhat Runtime Environment anywhere you need it (tasks, scripts, tests, etc). For example, in your `hardhat.config.js`: ```js -require("@nomiclabs/hardhat-ethers"); +require("@nomicfoundation/hardhat-ethers"); // task action function receives the Hardhat Runtime Environment as second argument task( "blockNumber", "Prints the current block number", async (_, { ethers }) => { - await ethers.provider.getBlockNumber().then((blockNumber) => { - console.log("Current block number: " + blockNumber); - }); + const blockNumber = await ethers.provider.getBlockNumber(); + console.log("Current block number: " + blockNumber); } ); @@ -133,7 +130,3 @@ To create a contract factory, all libraries must be linked. An error will be thr Ethers.js polls the network to check if some event was emitted (except when a `WebSocketProvider` is used; see below). This polling is done every 4 seconds. If you have a script or test that is not emitting an event, it's likely that the execution is finishing before the event is detected by the polling mechanism. If you are connecting to a Hardhat node using a `WebSocketProvider`, events should be emitted immediately. But keep in mind that you'll have to create this provider manually, since Hardhat only supports configuring networks via http. That is, you can't add a `localhost` network with a URL like `ws://127.0.0.1:8545`. - -### Gas transaction parameters in `hardhat.config` are not used - -When using this plugin, the `gas`, `gasPrice` and `gasMultiplier` parameters from your `hardhat.config` are not automatically applied to transactions. In order to provide such values to your transactions, specify them as [overrides](https://docs.ethers.io/v5/single-page/#/v5/api/contract/contract/-%23-contract-functionsSend) on the transaction itself. diff --git a/packages/hardhat-ethers/package.json b/packages/hardhat-ethers/package.json index 2e315938a1..51dbc74e21 100644 --- a/packages/hardhat-ethers/package.json +++ b/packages/hardhat-ethers/package.json @@ -1,10 +1,10 @@ { - "name": "@nomiclabs/hardhat-ethers", - "version": "2.2.3", + "name": "@nomicfoundation/hardhat-ethers", + "version": "3.0.2", "description": "Hardhat plugin for ethers", - "homepage": "https://github.com/nomiclabs/hardhat/tree/main/packages/hardhat-ethers", - "repository": "github:nomiclabs/hardhat", - "author": "Nomic Labs LLC", + "homepage": "https://github.com/nomicfoundation/hardhat/tree/main/packages/hardhat-ethers", + "repository": "github:nomicfoundation/hardhat", + "author": "Nomic Foundation", "license": "MIT", "main": "internal/index.js", "types": "internal/index.d.ts", @@ -37,6 +37,9 @@ "LICENSE", "README.md" ], + "dependencies": { + "debug": "^4.1.1" + }, "devDependencies": { "@types/chai": "^4.2.0", "@types/chai-as-promised": "^7.1.3", @@ -46,12 +49,13 @@ "@typescript-eslint/parser": "5.53.0", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", + "chalk": "^2.4.2", "eslint": "^7.29.0", "eslint-config-prettier": "8.3.0", "eslint-plugin-import": "2.24.1", "eslint-plugin-no-only-tests": "3.0.0", "eslint-plugin-prettier": "3.4.0", - "ethers": "^5.0.0", + "ethers": "^6.1.0", "hardhat": "^2.0.0", "mocha": "^10.0.0", "prettier": "2.4.1", @@ -60,7 +64,7 @@ "typescript": "~4.7.4" }, "peerDependencies": { - "ethers": "^5.0.0", + "ethers": "^6.1.0", "hardhat": "^2.0.0" } } diff --git a/packages/hardhat-ethers/src/dist/src/signer-with-address.ts b/packages/hardhat-ethers/src/dist/src/signer-with-address.ts index 51cc2d2bfc..890a7a60b1 100644 --- a/packages/hardhat-ethers/src/dist/src/signer-with-address.ts +++ b/packages/hardhat-ethers/src/dist/src/signer-with-address.ts @@ -1 +1 @@ -export { SignerWithAddress } from "../../signers"; +export { HardhatEthersSigner as SignerWithAddress } from "../../signers"; diff --git a/packages/hardhat-ethers/src/internal/errors.ts b/packages/hardhat-ethers/src/internal/errors.ts new file mode 100644 index 0000000000..1e4dc8a8cc --- /dev/null +++ b/packages/hardhat-ethers/src/internal/errors.ts @@ -0,0 +1,35 @@ +import { NomicLabsHardhatPluginError } from "hardhat/plugins"; + +export class HardhatEthersError extends NomicLabsHardhatPluginError { + constructor(message: string, parent?: Error) { + super("@nomicfoundation/hardhat-ethers", message, parent); + } +} + +export class NotImplementedError extends HardhatEthersError { + constructor(method: string) { + super(`Method '${method}' is not implemented`); + } +} + +export class NonStringEventError extends HardhatEthersError { + constructor(method: string, event: any) { + super(`Method '${method}' only supports string events, got '${event}'`); + } +} + +export class AccountIndexOutOfRange extends HardhatEthersError { + constructor(accountIndex: number, accountsLength: number) { + super( + `Tried to get account with index ${accountIndex} but there are ${accountsLength} accounts` + ); + } +} + +export class BroadcastedTxDifferentHash extends HardhatEthersError { + constructor(txHash: string, broadcastedTxHash: string) { + super( + `Expected broadcasted transaction to have hash '${txHash}', but got '${broadcastedTxHash}'` + ); + } +} diff --git a/packages/hardhat-ethers/src/internal/ethers-provider-wrapper.ts b/packages/hardhat-ethers/src/internal/ethers-provider-wrapper.ts deleted file mode 100644 index b0aa8c9afd..0000000000 --- a/packages/hardhat-ethers/src/internal/ethers-provider-wrapper.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ethers } from "ethers"; -import { EthereumProvider } from "hardhat/types"; - -export class EthersProviderWrapper extends ethers.providers.JsonRpcProvider { - private readonly _hardhatProvider: EthereumProvider; - - constructor(hardhatProvider: EthereumProvider) { - super(); - this._hardhatProvider = hardhatProvider; - } - - public async send(method: string, params: any): Promise { - const result = await this._hardhatProvider.send(method, params); - - // We replicate ethers' behavior. - this.emit("debug", { - action: "send", - request: { - id: 42, - jsonrpc: "2.0", - method, - params, - }, - response: result, - provider: this, - }); - - return result; - } - - public toJSON() { - return ""; - } -} diff --git a/packages/hardhat-ethers/src/internal/ethers-utils.ts b/packages/hardhat-ethers/src/internal/ethers-utils.ts new file mode 100644 index 0000000000..68cd19c3f9 --- /dev/null +++ b/packages/hardhat-ethers/src/internal/ethers-utils.ts @@ -0,0 +1,424 @@ +// these helpers functions were copied verbatim from ethers + +import type { + TransactionRequest, + PreparedTransactionRequest, + BlockParams, + TransactionResponseParams, + TransactionReceiptParams, + LogParams, + JsonRpcTransactionRequest, +} from "ethers"; + +import { + accessListify, + assert, + assertArgument, + getAddress, + getBigInt, + getCreateAddress, + getNumber, + hexlify, + isHexString, + Signature, + toQuantity, +} from "ethers"; +import { HardhatEthersError } from "./errors"; + +export type FormatFunc = (value: any) => any; + +export function copyRequest( + req: TransactionRequest +): PreparedTransactionRequest { + const result: any = {}; + + // These could be addresses, ENS names or Addressables + if (req.to !== null && req.to !== undefined) { + result.to = req.to; + } + if (req.from !== null && req.from !== undefined) { + result.from = req.from; + } + + if (req.data !== null && req.data !== undefined) { + result.data = hexlify(req.data); + } + + const bigIntKeys = + "chainId,gasLimit,gasPrice,maxFeePerGas,maxPriorityFeePerGas,value".split( + /,/ + ); + for (const key of bigIntKeys) { + if ( + !(key in req) || + (req as any)[key] === null || + (req as any)[key] === undefined + ) { + continue; + } + result[key] = getBigInt((req as any)[key], `request.${key}`); + } + + const numberKeys = "type,nonce".split(/,/); + for (const key of numberKeys) { + if ( + !(key in req) || + (req as any)[key] === null || + (req as any)[key] === undefined + ) { + continue; + } + result[key] = getNumber((req as any)[key], `request.${key}`); + } + + if (req.accessList !== null && req.accessList !== undefined) { + result.accessList = accessListify(req.accessList); + } + + if ("blockTag" in req) { + result.blockTag = req.blockTag; + } + + if ("enableCcipRead" in req) { + result.enableCcipReadEnabled = Boolean(req.enableCcipRead); + } + + if ("customData" in req) { + result.customData = req.customData; + } + + return result; +} + +export async function resolveProperties(value: { + [P in keyof T]: T[P] | Promise; +}): Promise { + const keys = Object.keys(value); + const results = await Promise.all( + keys.map((k) => Promise.resolve(value[k as keyof T])) + ); + return results.reduce((accum: any, v, index) => { + accum[keys[index]] = v; + return accum; + }, {} as { [P in keyof T]: T[P] }); +} + +export function formatBlock(value: any): BlockParams { + const result = _formatBlock(value); + result.transactions = value.transactions.map( + (tx: string | TransactionResponseParams) => { + if (typeof tx === "string") { + return tx; + } + return formatTransactionResponse(tx); + } + ); + return result; +} + +const _formatBlock = object({ + hash: allowNull(formatHash), + parentHash: formatHash, + number: getNumber, + + timestamp: getNumber, + nonce: allowNull(formatData), + difficulty: getBigInt, + + gasLimit: getBigInt, + gasUsed: getBigInt, + + miner: allowNull(getAddress), + extraData: formatData, + + baseFeePerGas: allowNull(getBigInt), +}); + +function object( + format: Record, + altNames?: Record +): FormatFunc { + return (value: any) => { + const result: any = {}; + // eslint-disable-next-line guard-for-in + for (const key in format) { + let srcKey = key; + if (altNames !== undefined && key in altNames && !(srcKey in value)) { + for (const altKey of altNames[key]) { + if (altKey in value) { + srcKey = altKey; + break; + } + } + } + + try { + const nv = format[key](value[srcKey]); + if (nv !== undefined) { + result[key] = nv; + } + } catch (error) { + const message = error instanceof Error ? error.message : "not-an-error"; + assert( + false, + `invalid value for value.${key} (${message})`, + "BAD_DATA", + { value } + ); + } + } + return result; + }; +} + +function allowNull(format: FormatFunc, nullValue?: any): FormatFunc { + return function (value: any) { + // eslint-disable-next-line eqeqeq + if (value === null || value === undefined) { + return nullValue; + } + return format(value); + }; +} + +function formatHash(value: any): string { + assertArgument(isHexString(value, 32), "invalid hash", "value", value); + return value; +} + +function formatData(value: string): string { + assertArgument(isHexString(value, true), "invalid data", "value", value); + return value; +} + +export function formatTransactionResponse( + value: any +): TransactionResponseParams { + // Some clients (TestRPC) do strange things like return 0x0 for the + // 0 address; correct this to be a real address + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (value.to && getBigInt(value.to) === 0n) { + value.to = "0x0000000000000000000000000000000000000000"; + } + + const result = object( + { + hash: formatHash, + + type: (v: any) => { + // eslint-disable-next-line eqeqeq + if (v === "0x" || v == null) { + return 0; + } + return getNumber(v); + }, + accessList: allowNull(accessListify, null), + + blockHash: allowNull(formatHash, null), + blockNumber: allowNull(getNumber, null), + transactionIndex: allowNull(getNumber, null), + + from: getAddress, + + // either (gasPrice) or (maxPriorityFeePerGas + maxFeePerGas) must be set + gasPrice: allowNull(getBigInt), + maxPriorityFeePerGas: allowNull(getBigInt), + maxFeePerGas: allowNull(getBigInt), + + gasLimit: getBigInt, + to: allowNull(getAddress, null), + value: getBigInt, + nonce: getNumber, + data: formatData, + + creates: allowNull(getAddress, null), + + chainId: allowNull(getBigInt, null), + }, + { + data: ["input"], + gasLimit: ["gas"], + } + )(value); + + // If to and creates are empty, populate the creates from the value + // eslint-disable-next-line eqeqeq + if (result.to == null && result.creates == null) { + result.creates = getCreateAddress(result); + } + + // @TODO: Check fee data + + // Add an access list to supported transaction types + // eslint-disable-next-line eqeqeq + if ((value.type === 1 || value.type === 2) && value.accessList == null) { + result.accessList = []; + } + + // Compute the signature + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (value.signature) { + result.signature = Signature.from(value.signature); + } else { + result.signature = Signature.from(value); + } + + // Some backends omit ChainId on legacy transactions, but we can compute it + // eslint-disable-next-line eqeqeq + if (result.chainId == null) { + const chainId = result.signature.legacyChainId; + // eslint-disable-next-line eqeqeq + if (chainId != null) { + result.chainId = chainId; + } + } + + // 0x0000... should actually be null + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (result.blockHash && getBigInt(result.blockHash) === 0n) { + result.blockHash = null; + } + + return result; +} + +function arrayOf(format: FormatFunc): FormatFunc { + return (array: any) => { + if (!Array.isArray(array)) { + throw new HardhatEthersError("not an array"); + } + return array.map((i) => format(i)); + }; +} + +const _formatReceiptLog = object( + { + transactionIndex: getNumber, + blockNumber: getNumber, + transactionHash: formatHash, + address: getAddress, + topics: arrayOf(formatHash), + data: formatData, + index: getNumber, + blockHash: formatHash, + }, + { + index: ["logIndex"], + } +); + +const _formatTransactionReceipt = object( + { + to: allowNull(getAddress, null), + from: allowNull(getAddress, null), + contractAddress: allowNull(getAddress, null), + // should be allowNull(hash), but broken-EIP-658 support is handled in receipt + index: getNumber, + root: allowNull(hexlify), + gasUsed: getBigInt, + logsBloom: allowNull(formatData), + blockHash: formatHash, + hash: formatHash, + logs: arrayOf(formatReceiptLog), + blockNumber: getNumber, + cumulativeGasUsed: getBigInt, + effectiveGasPrice: allowNull(getBigInt), + status: allowNull(getNumber), + type: allowNull(getNumber, 0), + }, + { + effectiveGasPrice: ["gasPrice"], + hash: ["transactionHash"], + index: ["transactionIndex"], + } +); + +export function formatTransactionReceipt(value: any): TransactionReceiptParams { + return _formatTransactionReceipt(value); +} + +export function formatReceiptLog(value: any): LogParams { + return _formatReceiptLog(value); +} + +function formatBoolean(value: any): boolean { + switch (value) { + case true: + case "true": + return true; + case false: + case "false": + return false; + } + assertArgument( + false, + `invalid boolean; ${JSON.stringify(value)}`, + "value", + value + ); +} + +const _formatLog = object( + { + address: getAddress, + blockHash: formatHash, + blockNumber: getNumber, + data: formatData, + index: getNumber, + removed: formatBoolean, + topics: arrayOf(formatHash), + transactionHash: formatHash, + transactionIndex: getNumber, + }, + { + index: ["logIndex"], + } +); + +export function formatLog(value: any): LogParams { + return _formatLog(value); +} + +export function getRpcTransaction( + tx: TransactionRequest +): JsonRpcTransactionRequest { + const result: JsonRpcTransactionRequest = {}; + + // JSON-RPC now requires numeric values to be "quantity" values + [ + "chainId", + "gasLimit", + "gasPrice", + "type", + "maxFeePerGas", + "maxPriorityFeePerGas", + "nonce", + "value", + ].forEach((key) => { + if ((tx as any)[key] === null || (tx as any)[key] === undefined) { + return; + } + let dstKey = key; + if (key === "gasLimit") { + dstKey = "gas"; + } + (result as any)[dstKey] = toQuantity( + getBigInt((tx as any)[key], `tx.${key}`) + ); + }); + + // Make sure addresses and data are lowercase + ["from", "to", "data"].forEach((key) => { + if ((tx as any)[key] === null || (tx as any)[key] === undefined) { + return; + } + (result as any)[key] = hexlify((tx as any)[key]); + }); + + // Normalize the access list object + if (tx.accessList !== null && tx.accessList !== undefined) { + result.accessList = accessListify(tx.accessList); + } + + return result; +} diff --git a/packages/hardhat-ethers/src/internal/hardhat-ethers-provider.ts b/packages/hardhat-ethers/src/internal/hardhat-ethers-provider.ts new file mode 100644 index 0000000000..0e18241210 --- /dev/null +++ b/packages/hardhat-ethers/src/internal/hardhat-ethers-provider.ts @@ -0,0 +1,881 @@ +import type { + AddressLike, + BlockTag, + TransactionRequest, + Filter, + FilterByBlockHash, + Listener, + ProviderEvent, + PerformActionTransaction, + TransactionResponseParams, + BlockParams, + TransactionReceiptParams, + LogParams, + PerformActionFilter, +} from "ethers"; + +import debug from "debug"; +import { + Block, + FeeData, + Log, + Network as EthersNetwork, + Transaction, + TransactionReceipt, + TransactionResponse, + ethers, + getBigInt, + isHexString, + resolveAddress, + toQuantity, +} from "ethers"; +import { EthereumProvider } from "hardhat/types"; +import { HardhatEthersSigner } from "../signers"; +import { + copyRequest, + formatBlock, + formatLog, + formatTransactionReceipt, + formatTransactionResponse, + getRpcTransaction, +} from "./ethers-utils"; +import { + AccountIndexOutOfRange, + BroadcastedTxDifferentHash, + HardhatEthersError, + NonStringEventError, + NotImplementedError, +} from "./errors"; + +const log = debug("hardhat:hardhat-ethers:provider"); + +export class HardhatEthersProvider implements ethers.Provider { + private _isHardhatNetworkCached: boolean | undefined; + + private _transactionHashListeners: Map< + string, + Array<{ + listener: Listener; + once: boolean; + }> + > = new Map(); + + private _transactionHashPollingInterval: NodeJS.Timeout | undefined; + + constructor( + private readonly _hardhatProvider: EthereumProvider, + private readonly _networkName: string + ) {} + + public get provider(): this { + return this; + } + + public destroy() {} + + public async send(method: string, params?: any[]): Promise { + return this._hardhatProvider.send(method, params); + } + + public async getSigner( + address?: number | string + ): Promise { + if (address === null || address === undefined) { + address = 0; + } + + const accountsPromise = this.send("eth_accounts", []); + + // Account index + if (typeof address === "number") { + const accounts: string[] = await accountsPromise; + if (address >= accounts.length) { + throw new AccountIndexOutOfRange(address, accounts.length); + } + return HardhatEthersSigner.create(this, accounts[address]); + } + + if (typeof address === "string") { + return HardhatEthersSigner.create(this, address); + } + + throw new HardhatEthersError(`Couldn't get account ${address as any}`); + } + + public async getBlockNumber(): Promise { + const blockNumber = await this._hardhatProvider.send("eth_blockNumber"); + + return Number(blockNumber); + } + + public async getNetwork(): Promise { + const chainId = await this._hardhatProvider.send("eth_chainId"); + return new EthersNetwork(this._networkName, Number(chainId)); + } + + public async getFeeData(): Promise { + let gasPrice: bigint | undefined; + let maxFeePerGas: bigint | undefined; + let maxPriorityFeePerGas: bigint | undefined; + + try { + gasPrice = BigInt(await this._hardhatProvider.send("eth_gasPrice")); + } catch {} + + const latestBlock = await this.getBlock("latest"); + const baseFeePerGas = latestBlock?.baseFeePerGas; + if (baseFeePerGas !== undefined && baseFeePerGas !== null) { + maxPriorityFeePerGas = 1_000_000_000n; + maxFeePerGas = 2n * baseFeePerGas + maxPriorityFeePerGas; + } + + return new FeeData(gasPrice, maxFeePerGas, maxPriorityFeePerGas); + } + + public async getBalance( + address: AddressLike, + blockTag?: BlockTag | undefined + ): Promise { + const resolvedAddress = await this._getAddress(address); + const resolvedBlockTag = await this._getBlockTag(blockTag); + const rpcBlockTag = this._getRpcBlockTag(resolvedBlockTag); + + const balance = await this._hardhatProvider.send("eth_getBalance", [ + resolvedAddress, + rpcBlockTag, + ]); + + return BigInt(balance); + } + + public async getTransactionCount( + address: AddressLike, + blockTag?: BlockTag | undefined + ): Promise { + const resolvedAddress = await this._getAddress(address); + const resolvedBlockTag = await this._getBlockTag(blockTag); + const rpcBlockTag = this._getRpcBlockTag(resolvedBlockTag); + + const transactionCount = await this._hardhatProvider.send( + "eth_getTransactionCount", + [resolvedAddress, rpcBlockTag] + ); + + return Number(transactionCount); + } + + public async getCode( + address: AddressLike, + blockTag?: BlockTag | undefined + ): Promise { + const resolvedAddress = await this._getAddress(address); + const resolvedBlockTag = await this._getBlockTag(blockTag); + const rpcBlockTag = this._getRpcBlockTag(resolvedBlockTag); + + return this._hardhatProvider.send("eth_getCode", [ + resolvedAddress, + rpcBlockTag, + ]); + } + + public async getStorage( + address: AddressLike, + position: ethers.BigNumberish, + blockTag?: BlockTag | undefined + ): Promise { + const resolvedAddress = await this._getAddress(address); + const resolvedPosition = getBigInt(position, "position"); + const resolvedBlockTag = await this._getBlockTag(blockTag); + const rpcBlockTag = this._getRpcBlockTag(resolvedBlockTag); + + return this._hardhatProvider.send("eth_getStorageAt", [ + resolvedAddress, + `0x${resolvedPosition.toString(16)}`, + rpcBlockTag, + ]); + } + + public async estimateGas(tx: TransactionRequest): Promise { + const blockTag = + tx.blockTag === undefined ? "pending" : this._getBlockTag(tx.blockTag); + const [resolvedTx, resolvedBlockTag] = await Promise.all([ + this._getTransactionRequest(tx), + blockTag, + ]); + + const rpcTransaction = getRpcTransaction(resolvedTx); + const rpcBlockTag = this._getRpcBlockTag(resolvedBlockTag); + + const gasEstimation = await this._hardhatProvider.send("eth_estimateGas", [ + rpcTransaction, + rpcBlockTag, + ]); + + return BigInt(gasEstimation); + } + + public async call(tx: TransactionRequest): Promise { + const [resolvedTx, resolvedBlockTag] = await Promise.all([ + this._getTransactionRequest(tx), + this._getBlockTag(tx.blockTag), + ]); + const rpcTransaction = getRpcTransaction(resolvedTx); + const rpcBlockTag = this._getRpcBlockTag(resolvedBlockTag); + + return this._hardhatProvider.send("eth_call", [ + rpcTransaction, + rpcBlockTag, + ]); + } + + public async broadcastTransaction( + signedTx: string + ): Promise { + const hashPromise = this._hardhatProvider.send("eth_sendRawTransaction", [ + signedTx, + ]); + + const [hash, blockNumber] = await Promise.all([ + hashPromise, + this.getBlockNumber(), + ]); + + const tx = Transaction.from(signedTx); + if (tx.hash === null) { + throw new HardhatEthersError( + "Assertion error: hash of signed tx shouldn't be null" + ); + } + + if (tx.hash !== hash) { + throw new BroadcastedTxDifferentHash(tx.hash, hash); + } + + return this._wrapTransactionResponse(tx as any).replaceableTransaction( + blockNumber + ); + } + + public async getBlock( + blockHashOrBlockTag: BlockTag, + prefetchTxs?: boolean | undefined + ): Promise { + const block = await this._getBlock( + blockHashOrBlockTag, + prefetchTxs ?? false + ); + + // eslint-disable-next-line eqeqeq + if (block == null) { + return null; + } + + return this._wrapBlock(block); + } + + public async getTransaction( + hash: string + ): Promise { + const transaction = await this._hardhatProvider.send( + "eth_getTransactionByHash", + [hash] + ); + + // eslint-disable-next-line eqeqeq + if (transaction == null) { + return null; + } + + return this._wrapTransactionResponse( + formatTransactionResponse(transaction) + ); + } + + public async getTransactionReceipt( + hash: string + ): Promise { + const receipt = await this._hardhatProvider.send( + "eth_getTransactionReceipt", + [hash] + ); + + // eslint-disable-next-line eqeqeq + if (receipt == null) { + return null; + } + + return this._wrapTransactionReceipt(receipt); + } + + public async getTransactionResult(_hash: string): Promise { + throw new NotImplementedError("HardhatEthersProvider.getTransactionResult"); + } + + public async getLogs( + filter: Filter | FilterByBlockHash + ): Promise { + const resolvedFilter = await this._getFilter(filter); + + const logs = await this._hardhatProvider.send("eth_getLogs", [ + resolvedFilter, + ]); + + return logs.map((l: any) => this._wrapLog(formatLog(l))); + } + + public async resolveName(_ensName: string): Promise { + throw new NotImplementedError("HardhatEthersProvider.resolveName"); + } + + public async lookupAddress(_address: string): Promise { + throw new NotImplementedError("HardhatEthersProvider.lookupAddress"); + } + + public async waitForTransaction( + _hash: string, + _confirms?: number | undefined, + _timeout?: number | undefined + ): Promise { + throw new NotImplementedError("HardhatEthersProvider.waitForTransaction"); + } + + public async waitForBlock( + _blockTag?: BlockTag | undefined + ): Promise { + throw new NotImplementedError("HardhatEthersProvider.waitForBlock"); + } + + public async on(event: ProviderEvent, listener: Listener): Promise { + if (typeof event === "string") { + if (isTransactionHash(event)) { + await this._onTransactionHash(event, listener, { once: false }); + } else { + this._hardhatProvider.on(event, listener); + } + } else { + throw new NonStringEventError("on", event); + } + + return this; + } + + public async once(event: ProviderEvent, listener: Listener): Promise { + if (typeof event === "string") { + if (isTransactionHash(event)) { + await this._onTransactionHash(event, listener, { once: true }); + } else { + this._hardhatProvider.once(event, listener); + } + } else { + throw new NonStringEventError("once", event); + } + + return this; + } + + public async emit(event: ProviderEvent, ...args: any[]): Promise { + if (typeof event === "string") { + if (isTransactionHash(event)) { + return this._emitTransactionHash(event, ...args); + } else { + return this._hardhatProvider.emit(event, ...args); + } + } else { + throw new NonStringEventError("emit", event); + } + } + + public async listenerCount( + event?: ProviderEvent | undefined + ): Promise { + if (typeof event === "string") { + if (isTransactionHash(event)) { + return this._transactionHashListeners.get(event)?.length ?? 0; + } else { + return this._hardhatProvider.listenerCount(event); + } + } else { + throw new NonStringEventError("listenerCount", event); + } + } + + public async listeners( + event?: ProviderEvent | undefined + ): Promise { + if (typeof event === "string") { + if (isTransactionHash(event)) { + return ( + this._transactionHashListeners + .get(event) + ?.map(({ listener }) => listener) ?? [] + ); + } else { + return this._hardhatProvider.listeners(event) as any; + } + } else { + throw new NonStringEventError("listeners", event); + } + } + + public async off( + event: ProviderEvent, + listener?: Listener | undefined + ): Promise { + if (typeof event === "string") { + if (isTransactionHash(event)) { + this._offTransactionHash(event, listener); + } else if (listener !== undefined) { + this._hardhatProvider.off(event, listener); + } else { + const registeredListeners = this._hardhatProvider.listeners(event); + for (const registeredListener of registeredListeners) { + this._hardhatProvider.off(event, registeredListener as any); + } + } + } else { + throw new NonStringEventError("off", event); + } + + return this; + } + + public async removeAllListeners( + event?: ProviderEvent | undefined + ): Promise { + if (event === undefined) { + this._hardhatProvider.removeAllListeners(event); + } else if (typeof event === "string") { + if (isTransactionHash(event)) { + this._transactionHashListeners.delete(event); + } else { + this._hardhatProvider.removeAllListeners(event); + } + } else { + throw new NonStringEventError("removeAllListeners", event); + } + + return this; + } + + public async addListener( + event: ProviderEvent, + listener: Listener + ): Promise { + if (typeof event === "string") { + if (isTransactionHash(event)) { + await this._onTransactionHash(event, listener, { once: false }); + } else { + this._hardhatProvider.addListener(event, listener); + } + } else { + throw new NonStringEventError("addListener", event); + } + + return this; + } + + public async removeListener( + event: ProviderEvent, + listener: Listener + ): Promise { + if (typeof event === "string") { + if (isTransactionHash(event)) { + this._offTransactionHash(event, listener); + } else { + this._hardhatProvider.removeListener(event, listener); + } + } else { + throw new NonStringEventError("removeListener", event); + } + + return this; + } + + public toJSON() { + return ""; + } + + private _getAddress(address: AddressLike): string | Promise { + return resolveAddress(address, this); + } + + private _getBlockTag(blockTag?: BlockTag): string | Promise { + // eslint-disable-next-line eqeqeq + if (blockTag == null) { + return "latest"; + } + + switch (blockTag) { + case "earliest": + return "0x0"; + case "latest": + case "pending": + case "safe": + case "finalized": + return blockTag; + } + + if (isHexString(blockTag)) { + if (isHexString(blockTag, 32)) { + return blockTag; + } + return toQuantity(blockTag); + } + + if (typeof blockTag === "number") { + if (blockTag >= 0) { + return toQuantity(blockTag); + } + return this.getBlockNumber().then((b) => toQuantity(b + blockTag)); + } + + throw new HardhatEthersError(`Invalid blockTag: ${blockTag}`); + } + + private _getTransactionRequest( + _request: TransactionRequest + ): PerformActionTransaction | Promise { + const request = copyRequest(_request) as PerformActionTransaction; + + const promises: Array> = []; + ["to", "from"].forEach((key) => { + if ( + (request as any)[key] === null || + (request as any)[key] === undefined + ) { + return; + } + + const addr = resolveAddress((request as any)[key]); + if (isPromise(addr)) { + promises.push( + (async function () { + (request as any)[key] = await addr; + })() + ); + } else { + (request as any)[key] = addr; + } + }); + + if (request.blockTag !== null && request.blockTag !== undefined) { + const blockTag = this._getBlockTag(request.blockTag); + if (isPromise(blockTag)) { + promises.push( + (async function () { + request.blockTag = await blockTag; + })() + ); + } else { + request.blockTag = blockTag; + } + } + + if (promises.length > 0) { + return (async function () { + await Promise.all(promises); + return request; + })(); + } + + return request; + } + + private _wrapTransactionResponse( + tx: TransactionResponseParams + ): TransactionResponse { + return new TransactionResponse(tx, this); + } + + private async _getBlock( + block: BlockTag | string, + includeTransactions: boolean + ): Promise { + if (isHexString(block, 32)) { + return this._hardhatProvider.send("eth_getBlockByHash", [ + block, + includeTransactions, + ]); + } + + let blockTag = this._getBlockTag(block); + if (typeof blockTag !== "string") { + blockTag = await blockTag; + } + + return this._hardhatProvider.send("eth_getBlockByNumber", [ + blockTag, + includeTransactions, + ]); + } + + private _wrapBlock(value: BlockParams): Block { + return new Block(formatBlock(value), this); + } + + private _wrapTransactionReceipt( + value: TransactionReceiptParams + ): TransactionReceipt { + return new TransactionReceipt(formatTransactionReceipt(value), this); + } + + private _getFilter( + filter: Filter | FilterByBlockHash + ): PerformActionFilter | Promise { + // Create a canonical representation of the topics + const topics = (filter.topics ?? []).map((topic) => { + // eslint-disable-next-line eqeqeq + if (topic == null) { + return null; + } + if (Array.isArray(topic)) { + return concisify(topic.map((t) => t.toLowerCase())); + } + return topic.toLowerCase(); + }); + + const blockHash = "blockHash" in filter ? filter.blockHash : undefined; + + const resolve = ( + _address: string[], + fromBlock?: string, + toBlock?: string + ) => { + let resolvedAddress: undefined | string | string[]; + switch (_address.length) { + case 0: + break; + case 1: + resolvedAddress = _address[0]; + break; + default: + _address.sort(); + resolvedAddress = _address; + } + + if (blockHash !== undefined) { + // eslint-disable-next-line eqeqeq + if (fromBlock != null || toBlock != null) { + throw new HardhatEthersError("invalid filter"); + } + } + + const resolvedFilter: any = {}; + if (resolvedAddress !== undefined) { + resolvedFilter.address = resolvedAddress; + } + if (topics.length > 0) { + resolvedFilter.topics = topics; + } + if (fromBlock !== undefined) { + resolvedFilter.fromBlock = fromBlock; + } + if (toBlock !== undefined) { + resolvedFilter.toBlock = toBlock; + } + if (blockHash !== undefined) { + resolvedFilter.blockHash = blockHash; + } + + return resolvedFilter; + }; + + // Addresses could be async (ENS names or Addressables) + const address: Array> = []; + if (filter.address !== undefined) { + if (Array.isArray(filter.address)) { + for (const addr of filter.address) { + address.push(this._getAddress(addr)); + } + } else { + address.push(this._getAddress(filter.address)); + } + } + + let resolvedFromBlock: undefined | string | Promise; + if ("fromBlock" in filter) { + resolvedFromBlock = this._getBlockTag(filter.fromBlock); + } + + let resolvedToBlock: undefined | string | Promise; + if ("toBlock" in filter) { + resolvedToBlock = this._getBlockTag(filter.toBlock); + } + + if ( + address.filter((a) => typeof a !== "string").length > 0 || + // eslint-disable-next-line eqeqeq + (resolvedFromBlock != null && typeof resolvedFromBlock !== "string") || + // eslint-disable-next-line eqeqeq + (resolvedToBlock != null && typeof resolvedToBlock !== "string") + ) { + return Promise.all([ + Promise.all(address), + resolvedFromBlock, + resolvedToBlock, + ]).then((result) => { + return resolve(result[0], result[1], result[2]); + }); + } + + return resolve(address as string[], resolvedFromBlock, resolvedToBlock); + } + + private _wrapLog(value: LogParams): Log { + return new Log(formatLog(value), this); + } + + private _getRpcBlockTag(blockTag: string): string | { blockHash: string } { + if (isHexString(blockTag, 32)) { + return { blockHash: blockTag }; + } + + return blockTag; + } + + private async _onTransactionHash( + transactionHash: string, + listener: Listener, + { once }: { once: boolean } + ): Promise { + const listeners = this._transactionHashListeners.get(transactionHash) ?? []; + listeners.push({ listener, once }); + this._transactionHashListeners.set(transactionHash, listeners); + await this._startTransactionHashPolling(); + } + + private _offTransactionHash( + transactionHash: string, + listener?: Listener + ): void { + if (listener === undefined) { + this._transactionHashListeners.delete(transactionHash); + } else { + const listeners = this._transactionHashListeners.get(transactionHash); + if (listeners !== undefined) { + const listenerIndex = listeners.findIndex( + (item) => item.listener === listener + ); + + if (listenerIndex >= 0) { + listeners.splice(listenerIndex, 1); + } + + if (listeners.length === 0) { + this._transactionHashListeners.delete(transactionHash); + } + + if (this._transactionHashListeners.size === 0) { + this._stopTransactionHashPolling(); + } + } + } + } + + private async _startTransactionHashPolling() { + const _isHardhatNetwork = await this._isHardhatNetwork(); + + const interval = _isHardhatNetwork ? 50 : 500; + + if (_isHardhatNetwork) { + await this._pollTransactionHashes(); + } + + if (this._transactionHashPollingInterval === undefined) { + this._transactionHashPollingInterval = setInterval(async () => { + await this._pollTransactionHashes(); + }, interval); + } + } + + private _stopTransactionHashPolling() { + if (this._transactionHashPollingInterval !== undefined) { + clearInterval(this._transactionHashPollingInterval); + this._transactionHashPollingInterval = undefined; + } + } + + /** + * Traverse all the registered transaction hashes and check if they were mined. + * + * This function should NOT throw. + */ + private async _pollTransactionHashes() { + try { + const listenersToRemove: Array<[string, Listener]> = []; + + for (const [ + transactionHash, + listeners, + ] of this._transactionHashListeners.entries()) { + const receipt = await this.getTransactionReceipt(transactionHash); + + if (receipt !== null) { + for (const { listener, once } of listeners) { + listener(receipt); + if (once) { + listenersToRemove.push([transactionHash, listener]); + } + } + } + } + + for (const [transactionHash, listener] of listenersToRemove) { + this._offTransactionHash(transactionHash, listener); + } + } catch (e: any) { + log(`Error during transaction hash polling: ${e.message}`); + } + } + + private _emitTransactionHash( + transactionHash: string, + ...args: any[] + ): boolean { + const listeners = this._transactionHashListeners.get(transactionHash); + const listenersToRemove: Listener[] = []; + + if (listeners === undefined) { + return false; + } + + for (const { listener, once } of listeners) { + listener(...args); + if (once) { + listenersToRemove.push(listener); + } + } + + for (const listener of listenersToRemove) { + this._offTransactionHash(transactionHash, listener); + } + + return true; + } + + private async _isHardhatNetwork(): Promise { + if (this._isHardhatNetworkCached === undefined) { + this._isHardhatNetworkCached = false; + try { + await this._hardhatProvider.send("hardhat_metadata"); + this._isHardhatNetworkCached = true; + } catch {} + } + + return this._isHardhatNetworkCached; + } +} + +function isPromise(value: any): value is Promise { + return Boolean(value) && typeof value.then === "function"; +} + +function concisify(items: string[]): string[] { + items = Array.from(new Set(items).values()); + items.sort(); + return items; +} + +function isTransactionHash(x: string): boolean { + return x.startsWith("0x") && x.length === 66; +} diff --git a/packages/hardhat-ethers/src/internal/helpers.ts b/packages/hardhat-ethers/src/internal/helpers.ts index 3d9e95c0f8..bd3bc89ff2 100644 --- a/packages/hardhat-ethers/src/internal/helpers.ts +++ b/packages/hardhat-ethers/src/internal/helpers.ts @@ -1,13 +1,13 @@ -import type { ethers } from "ethers"; -import type { SignerWithAddress } from "../signers"; -import type { FactoryOptions, Libraries } from "../types"; +import type { ethers as EthersT } from "ethers"; +import type { HardhatEthersSigner } from "../signers"; +import type { + DeployContractOptions, + FactoryOptions, + Libraries, +} from "../types"; -import { NomicLabsHardhatPluginError } from "hardhat/plugins"; -import { - Artifact, - HardhatRuntimeEnvironment, - NetworkConfig, -} from "hardhat/types"; +import { Artifact, HardhatRuntimeEnvironment } from "hardhat/types"; +import { HardhatEthersError } from "./errors"; interface Link { sourceName: string; @@ -15,8 +15,6 @@ interface Link { address: string; } -const pluginName = "hardhat-ethers"; - function isArtifact(artifact: any): artifact is Artifact { const { contractName, @@ -41,8 +39,8 @@ function isArtifact(artifact: any): artifact is Artifact { export async function getSigners( hre: HardhatRuntimeEnvironment -): Promise { - const accounts = await hre.ethers.provider.listAccounts(); +): Promise { + const accounts: string[] = await hre.ethers.provider.send("eth_accounts", []); const signersWithAddress = await Promise.all( accounts.map((account) => getSigner(hre, account)) @@ -54,14 +52,15 @@ export async function getSigners( export async function getSigner( hre: HardhatRuntimeEnvironment, address: string -): Promise { - const { SignerWithAddress: SignerWithAddressImpl } = await import( +): Promise { + const { HardhatEthersSigner: SignerWithAddressImpl } = await import( "../signers" ); - const signer = hre.ethers.provider.getSigner(address); - - const signerWithAddress = await SignerWithAddressImpl.create(signer); + const signerWithAddress = await SignerWithAddressImpl.create( + hre.ethers.provider, + address + ); return signerWithAddress; } @@ -69,68 +68,82 @@ export async function getSigner( export async function getImpersonatedSigner( hre: HardhatRuntimeEnvironment, address: string -): Promise { +): Promise { await hre.ethers.provider.send("hardhat_impersonateAccount", [address]); return getSigner(hre, address); } -export function getContractFactory( +export function getContractFactory< + A extends any[] = any[], + I = EthersT.Contract +>( hre: HardhatRuntimeEnvironment, name: string, - signerOrOptions?: ethers.Signer | FactoryOptions -): Promise; + signerOrOptions?: EthersT.Signer | FactoryOptions +): Promise>; -export function getContractFactory( +export function getContractFactory< + A extends any[] = any[], + I = EthersT.Contract +>( hre: HardhatRuntimeEnvironment, abi: any[], - bytecode: ethers.utils.BytesLike, - signer?: ethers.Signer -): Promise; - -export async function getContractFactory( + bytecode: EthersT.BytesLike, + signer?: EthersT.Signer +): Promise>; + +export async function getContractFactory< + A extends any[] = any[], + I = EthersT.Contract +>( hre: HardhatRuntimeEnvironment, nameOrAbi: string | any[], bytecodeOrFactoryOptions?: - | (ethers.Signer | FactoryOptions) - | ethers.utils.BytesLike, - signer?: ethers.Signer -) { + | (EthersT.Signer | FactoryOptions) + | EthersT.BytesLike, + signer?: EthersT.Signer +): Promise> { if (typeof nameOrAbi === "string") { const artifact = await hre.artifacts.readArtifact(nameOrAbi); - return getContractFactoryFromArtifact( + return getContractFactoryFromArtifact( hre, artifact, - bytecodeOrFactoryOptions as ethers.Signer | FactoryOptions | undefined + bytecodeOrFactoryOptions as EthersT.Signer | FactoryOptions | undefined ); } return getContractFactoryByAbiAndBytecode( hre, nameOrAbi, - bytecodeOrFactoryOptions as ethers.utils.BytesLike, + bytecodeOrFactoryOptions as EthersT.BytesLike, signer ); } function isFactoryOptions( - signerOrOptions?: ethers.Signer | FactoryOptions + signerOrOptions?: EthersT.Signer | FactoryOptions ): signerOrOptions is FactoryOptions { - const { Signer } = require("ethers") as typeof ethers; - return signerOrOptions !== undefined && !Signer.isSigner(signerOrOptions); + if (signerOrOptions === undefined || "provider" in signerOrOptions) { + return false; + } + + return true; } -export async function getContractFactoryFromArtifact( +export async function getContractFactoryFromArtifact< + A extends any[] = any[], + I = EthersT.Contract +>( hre: HardhatRuntimeEnvironment, artifact: Artifact, - signerOrOptions?: ethers.Signer | FactoryOptions -) { + signerOrOptions?: EthersT.Signer | FactoryOptions +): Promise> { let libraries: Libraries = {}; - let signer: ethers.Signer | undefined; + let signer: EthersT.Signer | undefined; if (!isArtifact(artifact)) { - throw new NomicLabsHardhatPluginError( - pluginName, + throw new HardhatEthersError( `You are trying to create a contract factory from an artifact, but you have not passed a valid artifact parameter.` ); } @@ -143,8 +156,7 @@ export async function getContractFactoryFromArtifact( } if (artifact.bytecode === "0x") { - throw new NomicLabsHardhatPluginError( - pluginName, + throw new HardhatEthersError( `You are trying to create a contract factory for the contract ${artifact.contractName}, which is abstract and can't be deployed. If you want to call a contract using ${artifact.contractName} as its interface use the "getContractAt" function instead.` ); @@ -164,7 +176,7 @@ async function collectLibrariesAndLink( artifact: Artifact, libraries: Libraries ) { - const { utils } = require("ethers") as typeof ethers; + const ethers = require("ethers") as typeof EthersT; const neededLibraries: Array<{ sourceName: string; @@ -182,10 +194,20 @@ async function collectLibrariesAndLink( for (const [linkedLibraryName, linkedLibraryAddress] of Object.entries( libraries )) { - if (!utils.isAddress(linkedLibraryAddress)) { - throw new NomicLabsHardhatPluginError( - pluginName, - `You tried to link the contract ${artifact.contractName} with the library ${linkedLibraryName}, but provided this invalid address: ${linkedLibraryAddress}` + let resolvedAddress: string; + if (ethers.isAddressable(linkedLibraryAddress)) { + resolvedAddress = await linkedLibraryAddress.getAddress(); + } else { + resolvedAddress = linkedLibraryAddress; + } + + if (!ethers.isAddress(resolvedAddress)) { + throw new HardhatEthersError( + `You tried to link the contract ${ + artifact.contractName + } with the library ${linkedLibraryName}, but provided this invalid address: ${ + resolvedAddress as any + }` ); } @@ -208,8 +230,7 @@ ${libraryFQNames}`; } else { detailedMessage = "This contract doesn't need linking any libraries."; } - throw new NomicLabsHardhatPluginError( - pluginName, + throw new HardhatEthersError( `You tried to link the contract ${artifact.contractName} with ${linkedLibraryName}, which is not one of its libraries. ${detailedMessage}` ); @@ -220,8 +241,7 @@ ${detailedMessage}` .map(({ sourceName, libName }) => `${sourceName}:${libName}`) .map((x) => `* ${x}`) .join("\n"); - throw new NomicLabsHardhatPluginError( - pluginName, + throw new HardhatEthersError( `The library name ${linkedLibraryName} is ambiguous for the contract ${artifact.contractName}. It may resolve to one of the following libraries: ${matchingNeededLibrariesFQNs} @@ -238,8 +258,7 @@ To fix this, choose one of these fully qualified library names and replace where // for it to be given twice in the libraries user input: // once as a library name and another as a fully qualified library name. if (linksToApply.has(neededLibraryFQN)) { - throw new NomicLabsHardhatPluginError( - pluginName, + throw new HardhatEthersError( `The library names ${neededLibrary.libName} and ${neededLibraryFQN} refer to the same library and were given as two separate library links. Remove one of them and review your library links before proceeding.` ); @@ -248,7 +267,7 @@ Remove one of them and review your library links before proceeding.` linksToApply.set(neededLibraryFQN, { sourceName: neededLibrary.sourceName, libraryName: neededLibrary.libName, - address: linkedLibraryAddress, + address: resolvedAddress, }); } @@ -259,12 +278,11 @@ Remove one of them and review your library links before proceeding.` .map((x) => `* ${x}`) .join("\n"); - throw new NomicLabsHardhatPluginError( - pluginName, + throw new HardhatEthersError( `The contract ${artifact.contractName} is missing links for the following libraries: ${missingLibraries} -Learn more about linking contracts at https://hardhat.org/hardhat-runner/plugins/nomiclabs-hardhat-ethers#library-linking +Learn more about linking contracts at https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-ethers#library-linking ` ); } @@ -272,32 +290,30 @@ Learn more about linking contracts at https://hardhat.org/hardhat-runner/plugins return linkBytecode(artifact, [...linksToApply.values()]); } -async function getContractFactoryByAbiAndBytecode( +async function getContractFactoryByAbiAndBytecode< + A extends any[] = any[], + I = EthersT.Contract +>( hre: HardhatRuntimeEnvironment, abi: any[], - bytecode: ethers.utils.BytesLike, - signer?: ethers.Signer -) { - const { ContractFactory } = require("ethers") as typeof ethers; + bytecode: EthersT.BytesLike, + signer?: EthersT.Signer +): Promise> { + const { ContractFactory } = require("ethers") as typeof EthersT; if (signer === undefined) { const signers = await hre.ethers.getSigners(); signer = signers[0]; } - const abiWithAddedGas = addGasToAbiMethodsIfNecessary( - hre.network.config, - abi - ); - - return new ContractFactory(abiWithAddedGas, bytecode, signer); + return new ContractFactory(abi, bytecode, signer); } export async function getContractAt( hre: HardhatRuntimeEnvironment, nameOrAbi: string | any[], - address: string, - signer?: ethers.Signer + address: string | EthersT.Addressable, + signer?: EthersT.Signer ) { if (typeof nameOrAbi === "string") { const artifact = await hre.artifacts.readArtifact(nameOrAbi); @@ -305,7 +321,7 @@ export async function getContractAt( return getContractAtFromArtifact(hre, artifact, address, signer); } - const { Contract } = require("ethers") as typeof ethers; + const ethers = require("ethers") as typeof EthersT; if (signer === undefined) { const signers = await hre.ethers.getSigners(); @@ -314,111 +330,93 @@ export async function getContractAt( // If there's no signer, we want to put the provider for the selected network here. // This allows read only operations on the contract interface. - const signerOrProvider: ethers.Signer | ethers.providers.Provider = + const signerOrProvider: EthersT.Signer | EthersT.Provider = signer !== undefined ? signer : hre.ethers.provider; - const abiWithAddedGas = addGasToAbiMethodsIfNecessary( - hre.network.config, - nameOrAbi - ); + let resolvedAddress; + if (ethers.isAddressable(address)) { + resolvedAddress = await address.getAddress(); + } else { + resolvedAddress = address; + } - return new Contract(address, abiWithAddedGas, signerOrProvider); + return new ethers.Contract(resolvedAddress, nameOrAbi, signerOrProvider); } export async function deployContract( hre: HardhatRuntimeEnvironment, name: string, args?: any[], - signerOrOptions?: ethers.Signer | FactoryOptions -): Promise; + signerOrOptions?: EthersT.Signer | DeployContractOptions +): Promise; export async function deployContract( hre: HardhatRuntimeEnvironment, name: string, - signerOrOptions?: ethers.Signer | FactoryOptions -): Promise; + signerOrOptions?: EthersT.Signer | DeployContractOptions +): Promise; export async function deployContract( hre: HardhatRuntimeEnvironment, name: string, - argsOrSignerOrOptions?: any[] | ethers.Signer | FactoryOptions, - signerOrOptions?: ethers.Signer | FactoryOptions -): Promise { + argsOrSignerOrOptions?: any[] | EthersT.Signer | DeployContractOptions, + signerOrOptions?: EthersT.Signer | DeployContractOptions +): Promise { let args = []; if (Array.isArray(argsOrSignerOrOptions)) { args = argsOrSignerOrOptions; } else { signerOrOptions = argsOrSignerOrOptions; } + + let overrides: EthersT.Overrides = {}; + if (signerOrOptions !== undefined && !("getAddress" in signerOrOptions)) { + const overridesAndFactoryOptions = { ...signerOrOptions }; + + // we delete the factory options properties in case ethers + // rejects unknown properties + delete overridesAndFactoryOptions.signer; + delete overridesAndFactoryOptions.libraries; + + overrides = overridesAndFactoryOptions; + } + const factory = await getContractFactory(hre, name, signerOrOptions); - return factory.deploy(...args); + return factory.deploy(...args, overrides); } export async function getContractAtFromArtifact( hre: HardhatRuntimeEnvironment, artifact: Artifact, - address: string, - signer?: ethers.Signer + address: string | EthersT.Addressable, + signer?: EthersT.Signer ) { + const ethers = require("ethers") as typeof EthersT; if (!isArtifact(artifact)) { - throw new NomicLabsHardhatPluginError( - pluginName, + throw new HardhatEthersError( `You are trying to create a contract by artifact, but you have not passed a valid artifact parameter.` ); } - const factory = await getContractFactoryByAbiAndBytecode( - hre, - artifact.abi, - "0x", - signer - ); - - let contract = factory.attach(address); - // If there's no signer, we connect the contract instance to the provider for the selected network. - if (contract.provider === null) { - contract = contract.connect(hre.ethers.provider); + if (signer === undefined) { + const signers = await hre.ethers.getSigners(); + signer = signers[0]; } - return contract; -} - -// This helper adds a `gas` field to the ABI function elements if the network -// is set up to use a fixed amount of gas. -// This is done so that ethers doesn't automatically estimate gas limits on -// every call. -function addGasToAbiMethodsIfNecessary( - networkConfig: NetworkConfig, - abi: any[] -): any[] { - const { BigNumber } = require("ethers") as typeof ethers; - - if (networkConfig.gas === "auto" || networkConfig.gas === undefined) { - return abi; + let resolvedAddress; + if (ethers.isAddressable(address)) { + resolvedAddress = await address.getAddress(); + } else { + resolvedAddress = address; } - // ethers adds 21000 to whatever the abi `gas` field has. This may lead to - // OOG errors, as people may set the default gas to the same value as the - // block gas limit, especially on Hardhat Network. - // To avoid this, we substract 21000. - // HOTFIX: We substract 1M for now. See: https://github.com/ethers-io/ethers.js/issues/1058#issuecomment-703175279 - const gasLimit = BigNumber.from(networkConfig.gas).sub(1000000).toHexString(); - - const modifiedAbi: any[] = []; + let contract = new ethers.Contract(resolvedAddress, artifact.abi, signer); - for (const abiElement of abi) { - if (abiElement.type !== "function") { - modifiedAbi.push(abiElement); - continue; - } - - modifiedAbi.push({ - ...abiElement, - gas: gasLimit, - }); + if (contract.runner === null) { + contract = contract.connect(hre.ethers.provider) as EthersT.Contract; } - return modifiedAbi; + return contract; } function linkBytecode(artifact: Artifact, libraries: Link[]): string { diff --git a/packages/hardhat-ethers/src/internal/index.ts b/packages/hardhat-ethers/src/internal/index.ts index a8f46f51e0..9561f44ea6 100644 --- a/packages/hardhat-ethers/src/internal/index.ts +++ b/packages/hardhat-ethers/src/internal/index.ts @@ -1,9 +1,9 @@ import type EthersT from "ethers"; -import type * as ProviderProxyT from "./provider-proxy"; import { extendEnvironment } from "hardhat/config"; import { lazyObject } from "hardhat/plugins"; +import { HardhatEthersProvider } from "./hardhat-ethers-provider"; import { getContractAt, getContractAtFromArtifact, @@ -16,29 +16,19 @@ import { } from "./helpers"; import "./type-extensions"; -const registerCustomInspection = (BigNumber: any) => { - const inspectCustomSymbol = Symbol.for("nodejs.util.inspect.custom"); - - BigNumber.prototype[inspectCustomSymbol] = function () { - return `BigNumber { value: "${this.toString()}" }`; - }; -}; - extendEnvironment((hre) => { hre.ethers = lazyObject(() => { - const { createProviderProxy } = - require("./provider-proxy") as typeof ProviderProxyT; - const { ethers } = require("ethers") as typeof EthersT; - registerCustomInspection(ethers.BigNumber); - - const providerProxy = createProviderProxy(hre.network.provider); + const provider = new HardhatEthersProvider( + hre.network.provider, + hre.network.name + ); return { ...ethers, - provider: providerProxy, + provider, getSigner: (address: string) => getSigner(hre, address), getSigners: () => getSigners(hre), @@ -47,12 +37,11 @@ extendEnvironment((hre) => { // We cast to any here as we hit a limitation of Function#bind and // overloads. See: https://github.com/microsoft/TypeScript/issues/28582 getContractFactory: getContractFactory.bind(null, hre) as any, - getContractFactoryFromArtifact: getContractFactoryFromArtifact.bind( - null, - hre - ), - getContractAt: getContractAt.bind(null, hre), - getContractAtFromArtifact: getContractAtFromArtifact.bind(null, hre), + getContractFactoryFromArtifact: (...args) => + getContractFactoryFromArtifact(hre, ...args), + getContractAt: (...args) => getContractAt(hre, ...args), + getContractAtFromArtifact: (...args) => + getContractAtFromArtifact(hre, ...args), deployContract: deployContract.bind(null, hre) as any, }; }); diff --git a/packages/hardhat-ethers/src/internal/provider-proxy.ts b/packages/hardhat-ethers/src/internal/provider-proxy.ts deleted file mode 100644 index 4a722de9da..0000000000 --- a/packages/hardhat-ethers/src/internal/provider-proxy.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { - HARDHAT_NETWORK_RESET_EVENT, - HARDHAT_NETWORK_REVERT_SNAPSHOT_EVENT, -} from "hardhat/internal/constants"; -import { EthereumProvider } from "hardhat/types"; - -import { EthersProviderWrapper } from "./ethers-provider-wrapper"; -import { createUpdatableTargetProxy } from "./updatable-target-proxy"; - -/** - * This method returns a proxy that uses an underlying provider for everything. - * - * This underlying provider is replaced by a new one after a successful hardhat_reset, - * because ethers providers can have internal state that returns wrong results after - * the network is reset. - */ -export function createProviderProxy( - hardhatProvider: EthereumProvider -): EthersProviderWrapper { - const initialProvider = new EthersProviderWrapper(hardhatProvider); - - const { proxy: providerProxy, setTarget } = - createUpdatableTargetProxy(initialProvider); - - hardhatProvider.on(HARDHAT_NETWORK_RESET_EVENT, () => { - setTarget(new EthersProviderWrapper(hardhatProvider)); - }); - hardhatProvider.on(HARDHAT_NETWORK_REVERT_SNAPSHOT_EVENT, () => { - setTarget(new EthersProviderWrapper(hardhatProvider)); - }); - - return providerProxy; -} diff --git a/packages/hardhat-ethers/src/internal/updatable-target-proxy.ts b/packages/hardhat-ethers/src/internal/updatable-target-proxy.ts deleted file mode 100644 index 8ad55cd3ac..0000000000 --- a/packages/hardhat-ethers/src/internal/updatable-target-proxy.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Returns a read-only proxy that just forwards everything to a target, - * and a function that can be used to change that underlying target - */ -export function createUpdatableTargetProxy( - initialTarget: T -): { - proxy: T; - setTarget: (target: T) => void; -} { - const targetObject = { - target: initialTarget, - }; - - let isExtensible = Object.isExtensible(initialTarget); - - const handler: Required> = { - // these two functions are implemented because of the Required type - apply(_, _thisArg, _argArray) { - throw new Error( - "cannot be implemented because the target is not a function" - ); - }, - - construct(_, _argArray, _newTarget) { - throw new Error( - "cannot be implemented because the target is not a function" - ); - }, - - defineProperty(_, property, _descriptor) { - throw new Error( - `cannot define property ${String(property)} in read-only proxy` - ); - }, - - deleteProperty(_, property) { - throw new Error( - `cannot delete property ${String(property)} in read-only proxy` - ); - }, - - get(_, property, receiver) { - const result = Reflect.get(targetObject.target, property, receiver); - - if (result instanceof Function) { - return result.bind(targetObject.target); - } - - return result; - }, - - getOwnPropertyDescriptor(_, property) { - const descriptor = Reflect.getOwnPropertyDescriptor( - targetObject.target, - property - ); - - if (descriptor !== undefined) { - Object.defineProperty(targetObject.target, property, descriptor); - } - - return descriptor; - }, - - getPrototypeOf(_) { - return Reflect.getPrototypeOf(targetObject.target); - }, - - has(_, property) { - return Reflect.has(targetObject.target, property); - }, - - isExtensible(_) { - // we need to return the extensibility value of the original target - return isExtensible; - }, - - ownKeys(_) { - return Reflect.ownKeys(targetObject.target); - }, - - preventExtensions(_) { - isExtensible = false; - return Reflect.preventExtensions(targetObject.target); - }, - - set(_, property, _value, _receiver) { - throw new Error( - `cannot set property ${String(property)} in read-only proxy` - ); - }, - - setPrototypeOf(_, _prototype) { - throw new Error("cannot change the prototype in read-only proxy"); - }, - }; - - const proxy: T = new Proxy(initialTarget, handler); - - const setTarget = (newTarget: T) => { - targetObject.target = newTarget; - }; - - return { proxy, setTarget }; -} diff --git a/packages/hardhat-ethers/src/signers.ts b/packages/hardhat-ethers/src/signers.ts index 48294f1cb9..e4ef81550b 100644 --- a/packages/hardhat-ethers/src/signers.ts +++ b/packages/hardhat-ethers/src/signers.ts @@ -1,49 +1,328 @@ -import { ethers } from "ethers"; +import type { BlockTag, TransactionRequest } from "ethers"; +import { + assertArgument, + ethers, + getAddress, + hexlify, + resolveAddress, + toUtf8Bytes, + TransactionLike, + TypedDataEncoder, +} from "ethers"; +import { HardhatEthersProvider } from "./internal/hardhat-ethers-provider"; +import { + copyRequest, + getRpcTransaction, + resolveProperties, +} from "./internal/ethers-utils"; +import { HardhatEthersError, NotImplementedError } from "./internal/errors"; -export class SignerWithAddress extends ethers.Signer { - public static async create(signer: ethers.providers.JsonRpcSigner) { - return new SignerWithAddress(await signer.getAddress(), signer); +export class HardhatEthersSigner implements ethers.Signer { + public readonly address: string; + public readonly provider: ethers.JsonRpcProvider | HardhatEthersProvider; + + public static async create(provider: HardhatEthersProvider, address: string) { + const hre = await import("hardhat"); + + // depending on the config, we set a fixed gas limit for all transactions + let gasLimit: number | undefined; + + if (hre.network.name === "hardhat") { + // If we are connected to the in-process hardhat network and the config + // has a fixed number as the gas config, we use that. + // Hardhat core already sets this value to the block gas limit when the + // user doesn't specify a number. + if (hre.network.config.gas !== "auto") { + gasLimit = hre.network.config.gas; + } + } else if (hre.network.name === "localhost") { + const configuredGasLimit = hre.config.networks.localhost.gas; + + if (configuredGasLimit !== "auto") { + // if the resolved gas config is a number, we use that + gasLimit = configuredGasLimit; + } else { + // if the resolved gas config is "auto", we need to check that + // the user config is undefined, because that's the default value; + // otherwise explicitly setting the gas to "auto" would have no effect + if (hre.userConfig.networks?.localhost?.gas === undefined) { + // finally, we check if we are connected to a hardhat network + let isHardhatNetwork = false; + try { + await hre.network.provider.send("hardhat_metadata"); + isHardhatNetwork = true; + } catch {} + + if (isHardhatNetwork) { + // WARNING: this assumes that the hardhat node is being run in the + // same project which might be wrong + gasLimit = hre.config.networks.hardhat.blockGasLimit; + } + } + } + } + + return new HardhatEthersSigner(address, provider, gasLimit); } private constructor( - public readonly address: string, - private readonly _signer: ethers.providers.JsonRpcSigner + address: string, + _provider: ethers.JsonRpcProvider | HardhatEthersProvider, + private readonly _gasLimit?: number ) { - super(); - (this as any).provider = _signer.provider; + this.address = getAddress(address); + this.provider = _provider; } - public async getAddress(): Promise { - return this.address; + public connect( + provider: ethers.JsonRpcProvider | HardhatEthersProvider + ): ethers.Signer { + return new HardhatEthersSigner(this.address, provider); } - public signMessage(message: string | ethers.utils.Bytes): Promise { - return this._signer.signMessage(message); + public getNonce(blockTag?: BlockTag | undefined): Promise { + return this.provider.getTransactionCount(this.address, blockTag); } - public signTransaction( - transaction: ethers.utils.Deferrable - ): Promise { - return this._signer.signTransaction(transaction); + public populateCall( + tx: TransactionRequest + ): Promise> { + return populate(this, tx); + } + + public populateTransaction( + tx: TransactionRequest + ): Promise> { + return this.populateCall(tx); + } + + public async estimateGas(tx: TransactionRequest): Promise { + return this.provider.estimateGas(await this.populateCall(tx)); + } + + public async call(tx: TransactionRequest): Promise { + return this.provider.call(await this.populateCall(tx)); } - public sendTransaction( - transaction: ethers.utils.Deferrable - ): Promise { - return this._signer.sendTransaction(transaction); + public resolveName(name: string): Promise { + return this.provider.resolveName(name); } - public connect(provider: ethers.providers.Provider): SignerWithAddress { - return new SignerWithAddress(this.address, this._signer.connect(provider)); + public async signTransaction(_tx: TransactionRequest): Promise { + // TODO if we split the signer for the in-process and json-rpc networks, + // we can enable this method when using the in-process network or when the + // json-rpc network has a private key + throw new NotImplementedError("HardhatEthersSigner.signTransaction"); } - public _signTypedData( - ...params: Parameters + public async sendTransaction( + tx: TransactionRequest + ): Promise { + // This cannot be mined any earlier than any recent block + const blockNumber = await this.provider.getBlockNumber(); + + // Send the transaction + const hash = await this._sendUncheckedTransaction(tx); + + // Unfortunately, JSON-RPC only provides and opaque transaction hash + // for a response, and we need the actual transaction, so we poll + // for it; it should show up very quickly + + return new Promise((resolve) => { + const timeouts = [1000, 100]; + const checkTx = async () => { + // Try getting the transaction + const txPolled = await this.provider.getTransaction(hash); + if (txPolled !== null) { + resolve(txPolled.replaceableTransaction(blockNumber)); + return; + } + + // Wait another 4 seconds + setTimeout(() => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + checkTx(); + }, timeouts.pop() ?? 4000); + }; + // eslint-disable-next-line @typescript-eslint/no-floating-promises + checkTx(); + }); + } + + public signMessage(message: string | Uint8Array): Promise { + const resolvedMessage = + typeof message === "string" ? toUtf8Bytes(message) : message; + return this.provider.send("personal_sign", [ + hexlify(resolvedMessage), + this.address.toLowerCase(), + ]); + } + + public async signTypedData( + domain: ethers.TypedDataDomain, + types: Record, + value: Record ): Promise { - return this._signer._signTypedData(...params); + const copiedValue = deepCopy(value); + + // Populate any ENS names (in-place) + const populated = await TypedDataEncoder.resolveNames( + domain, + types, + copiedValue, + async (v: string) => { + return v; + } + ); + + return this.provider.send("eth_signTypedData_v4", [ + this.address.toLowerCase(), + JSON.stringify( + TypedDataEncoder.getPayload(populated.domain, types, populated.value), + (_k, v) => { + if (typeof v === "bigint") { + return v.toString(); + } + + return v; + } + ), + ]); + } + + public async getAddress(): Promise { + return this.address; } public toJSON() { return ``; } + + private async _sendUncheckedTransaction( + tx: TransactionRequest + ): Promise { + const resolvedTx = deepCopy(tx); + + const promises: Array> = []; + + // Make sure the from matches the sender + if (resolvedTx.from !== null && resolvedTx.from !== undefined) { + const _from = resolvedTx.from; + promises.push( + (async () => { + const from = await resolveAddress(_from, this.provider); + assertArgument( + from !== null && + from !== undefined && + from.toLowerCase() === this.address.toLowerCase(), + "from address mismatch", + "transaction", + tx + ); + resolvedTx.from = from; + })() + ); + } else { + resolvedTx.from = this.address; + } + + if (resolvedTx.gasLimit === null || resolvedTx.gasLimit === undefined) { + if (this._gasLimit !== undefined) { + resolvedTx.gasLimit = this._gasLimit; + } else { + promises.push( + (async () => { + resolvedTx.gasLimit = await this.provider.estimateGas({ + ...resolvedTx, + from: this.address, + }); + })() + ); + } + } + + // The address may be an ENS name or Addressable + if (resolvedTx.to !== null && resolvedTx.to !== undefined) { + const _to = resolvedTx.to; + promises.push( + (async () => { + resolvedTx.to = await resolveAddress(_to, this.provider); + })() + ); + } + + // Wait until all of our properties are filled in + if (promises.length > 0) { + await Promise.all(promises); + } + + const hexTx = getRpcTransaction(resolvedTx); + + return this.provider.send("eth_sendTransaction", [hexTx]); + } +} + +// exported as an alias to make migration easier +export { HardhatEthersSigner as SignerWithAddress }; + +async function populate( + signer: ethers.Signer, + tx: TransactionRequest +): Promise> { + const pop: any = copyRequest(tx); + + if (pop.to !== null && pop.to !== undefined) { + pop.to = resolveAddress(pop.to, signer); + } + + if (pop.from !== null && pop.from !== undefined) { + const from = pop.from; + pop.from = Promise.all([ + signer.getAddress(), + resolveAddress(from, signer), + ]).then(([address, resolvedFrom]) => { + assertArgument( + address.toLowerCase() === resolvedFrom.toLowerCase(), + "transaction from mismatch", + "tx.from", + resolvedFrom + ); + return address; + }); + } else { + pop.from = signer.getAddress(); + } + + return resolveProperties(pop); +} + +const Primitive = "bigint,boolean,function,number,string,symbol".split(/,/g); +function deepCopy(value: T): T { + if ( + value === null || + value === undefined || + Primitive.indexOf(typeof value) >= 0 + ) { + return value; + } + + // Keep any Addressable + if (typeof (value as any).getAddress === "function") { + return value; + } + + if (Array.isArray(value)) { + return (value as any).map(deepCopy); + } + + if (typeof value === "object") { + return Object.keys(value).reduce((accum, key) => { + accum[key] = (value as any)[key]; + return accum; + }, {} as any); + } + + throw new HardhatEthersError( + `Assertion error: ${value as any} (${typeof value})` + ); } diff --git a/packages/hardhat-ethers/src/types/index.ts b/packages/hardhat-ethers/src/types/index.ts index b39b673ce3..0b2915baa6 100644 --- a/packages/hardhat-ethers/src/types/index.ts +++ b/packages/hardhat-ethers/src/types/index.ts @@ -1,10 +1,10 @@ import type * as ethers from "ethers"; -import type { SignerWithAddress } from "../signers"; - -import { Artifact } from "hardhat/types"; +import type { Artifact } from "hardhat/types"; +import type { HardhatEthersProvider } from "../internal/hardhat-ethers-provider"; +import type { HardhatEthersSigner } from "../signers"; export interface Libraries { - [libraryName: string]: string; + [libraryName: string]: string | ethers.Addressable; } export interface FactoryOptions { @@ -12,38 +12,51 @@ export interface FactoryOptions { libraries?: Libraries; } -export declare function getContractFactory( +export type DeployContractOptions = FactoryOptions & ethers.Overrides; + +export declare function getContractFactory< + A extends any[] = any[], + I = ethers.Contract +>( name: string, signerOrOptions?: ethers.Signer | FactoryOptions -): Promise; -export declare function getContractFactory( +): Promise>; +export declare function getContractFactory< + A extends any[] = any[], + I = ethers.Contract +>( abi: any[], - bytecode: ethers.utils.BytesLike, + bytecode: ethers.BytesLike, signer?: ethers.Signer -): Promise; +): Promise>; export declare function deployContract( name: string, - signerOrOptions?: ethers.Signer | FactoryOptions + signerOrOptions?: ethers.Signer | DeployContractOptions ): Promise; export declare function deployContract( name: string, args: any[], - signerOrOptions?: ethers.Signer | FactoryOptions + signerOrOptions?: ethers.Signer | DeployContractOptions ): Promise; +export declare function getContractFactoryFromArtifact< + A extends any[] = any[], + I = ethers.Contract +>( + artifact: Artifact, + signerOrOptions?: ethers.Signer | FactoryOptions +): Promise>; + export interface HardhatEthersHelpers { - provider: ethers.providers.JsonRpcProvider; + provider: HardhatEthersProvider; getContractFactory: typeof getContractFactory; - getContractFactoryFromArtifact: ( - artifact: Artifact, - signerOrOptions?: ethers.Signer | FactoryOptions - ) => Promise; + getContractFactoryFromArtifact: typeof getContractFactoryFromArtifact; getContractAt: ( nameOrAbi: string | any[], - address: string, + address: string | ethers.Addressable, signer?: ethers.Signer ) => Promise; getContractAtFromArtifact: ( @@ -51,8 +64,8 @@ export interface HardhatEthersHelpers { address: string, signer?: ethers.Signer ) => Promise; - getSigner: (address: string) => Promise; - getSigners: () => Promise; - getImpersonatedSigner: (address: string) => Promise; + getSigner: (address: string) => Promise; + getSigners: () => Promise; + getImpersonatedSigner: (address: string) => Promise; deployContract: typeof deployContract; } diff --git a/packages/hardhat-ethers/test/contracts.ts b/packages/hardhat-ethers/test/contracts.ts new file mode 100644 index 0000000000..3662fcba93 --- /dev/null +++ b/packages/hardhat-ethers/test/contracts.ts @@ -0,0 +1,89 @@ +import { assert, use } from "chai"; +import chaiAsPromised from "chai-as-promised"; + +import { ExampleContract, EXAMPLE_CONTRACT } from "./example-contracts"; +import { useEnvironment } from "./environment"; +import { sleep } from "./helpers"; + +use(chaiAsPromised); + +describe("contracts", function () { + useEnvironment("minimal-project"); + + it("should wait for deployment when automining is enabled", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + + const contract = await factory.deploy(); + + await contract.waitForDeployment(); + }); + + it("should wait for deployment when automining is disabled", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + + await this.env.network.provider.send("evm_setAutomine", [false]); + + const contract = await factory.deploy(); + + let deployed = false; + const waitForDeploymentPromise = contract.waitForDeployment().then(() => { + deployed = true; + }); + + assert.isFalse(deployed); + await sleep(10); + assert.isFalse(deployed); + + await this.env.network.provider.send("hardhat_mine"); + await waitForDeploymentPromise; + assert.isTrue(deployed); + }); + + it("should wait for multiple deployments at the same time", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + + await this.env.network.provider.send("evm_setAutomine", [false]); + + // we set the gas limit so that the 3 txs fit in a block + const contract1 = await factory.deploy({ gasLimit: 1_000_000 }); + const contract2 = await factory.deploy({ gasLimit: 1_000_000 }); + const contract3 = await factory.deploy({ gasLimit: 1_000_000 }); + + const allDeployedPromise = Promise.all([ + contract1.waitForDeployment(), + contract2.waitForDeployment(), + contract3.waitForDeployment(), + ]); + + let deployed = false; + const waitForDeploymentPromise = allDeployedPromise.then(() => { + deployed = true; + }); + + assert.isFalse(deployed); + await sleep(10); + assert.isFalse(deployed); + + await this.env.network.provider.send("hardhat_mine"); + await waitForDeploymentPromise; + assert.isTrue(deployed); + }); +}); diff --git a/packages/hardhat-ethers/test/environment.ts b/packages/hardhat-ethers/test/environment.ts new file mode 100644 index 0000000000..7e5c760d56 --- /dev/null +++ b/packages/hardhat-ethers/test/environment.ts @@ -0,0 +1,165 @@ +import chalk from "chalk"; +import fs from "fs"; +import { HardhatRuntimeEnvironment, HardhatUserConfig } from "hardhat/types"; +import { resetHardhatContext } from "hardhat/plugins-testing"; +import path from "path"; + +// Import this plugin type extensions for the HardhatRuntimeEnvironment +import "../src/internal/type-extensions"; + +declare module "mocha" { + interface Context { + env: HardhatRuntimeEnvironment; + } +} + +/** + * Start a new Hardhat environment for each test + */ +export function useEnvironment( + fixtureProjectName: string, + networkName = "hardhat" +) { + const fixtureProjectPath = path.resolve( + __dirname, + "fixture-projects", + fixtureProjectName + ); + + beforeEach("Loading hardhat environment", function () { + process.chdir(fixtureProjectPath); + process.env.HARDHAT_NETWORK = networkName; + + this.env = require("hardhat"); + }); + + afterEach("Resetting hardhat", function () { + resetHardhatContext(); + }); + + afterEach(function () { + if (this.currentTest?.state === "failed") { + console.log(chalk.red("Failed in fixture project", fixtureProjectPath)); + } + }); +} + +/** + * Like useEnvironment, but re-use the environment for the whole suite + */ +export function usePersistentEnvironment( + fixtureProjectName: string, + networkName = "hardhat" +) { + const fixtureProjectPath = path.resolve( + __dirname, + "fixture-projects", + fixtureProjectName + ); + + before("Loading hardhat environment", function () { + process.chdir(fixtureProjectPath); + process.env.HARDHAT_NETWORK = networkName; + + this.env = require("hardhat"); + }); + + after("Resetting hardhat", function () { + resetHardhatContext(); + }); + + afterEach(function () { + if (this.currentTest?.state === "failed") { + console.log(chalk.red("Failed in fixture project", fixtureProjectPath)); + } + }); +} + +/** + * Generate a fixture project on runtime with the given parameters, + * and start a persistent environment in that project. + */ +export function useGeneratedEnvironment( + hardhatGasLimit: "default" | "auto" | number, + localhostGasLimit: "default" | "auto" | number, + networkName: "hardhat" | "localhost" +) { + const fixtureProjectPath = path.resolve( + __dirname, + "fixture-projects", + "generated-fixtures", + `hardhat-gas-${hardhatGasLimit}-localhost-gas-${localhostGasLimit}` + ); + + before("Loading hardhat environment", function () { + // remove the directory if it exists and create an empty one + try { + fs.unlinkSync(fixtureProjectPath); + } catch {} + fs.mkdirSync(fixtureProjectPath, { recursive: true }); + + // generate and write the hardhat config + const hardhatConfigPath = path.resolve( + fixtureProjectPath, + "hardhat.config.js" + ); + + const hardhatConfig: HardhatUserConfig = { + solidity: "0.8.19", + networks: { + hardhat: {}, + localhost: {}, + }, + }; + if (hardhatGasLimit !== "default") { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + hardhatConfig.networks!.hardhat!.gas = hardhatGasLimit; + } + if (localhostGasLimit !== "default") { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + hardhatConfig.networks!.localhost!.gas = localhostGasLimit; + } + + fs.writeFileSync( + hardhatConfigPath, + ` +require("../../../../src/internal/index"); + +module.exports = ${JSON.stringify(hardhatConfig, null, 2)} +` + ); + + // generate and write the contracts + fs.mkdirSync(path.resolve(fixtureProjectPath, "contracts"), { + recursive: true, + }); + + fs.writeFileSync( + path.resolve(fixtureProjectPath, "contracts", "Example.sol"), + ` +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract Example { + function f() public {} +} +` + ); + + // start the environment + process.chdir(fixtureProjectPath); + process.env.HARDHAT_NETWORK = networkName; + + this.env = require("hardhat"); + }); + + after("Resetting hardhat", function () { + resetHardhatContext(); + }); + + afterEach(function () { + if (this.currentTest?.state === "failed") { + console.log(chalk.red("Failed in fixture project", fixtureProjectPath)); + } + }); +} diff --git a/packages/hardhat-ethers/test/ethers-provider-wrapper.ts b/packages/hardhat-ethers/test/ethers-provider-wrapper.ts deleted file mode 100644 index d1f730195f..0000000000 --- a/packages/hardhat-ethers/test/ethers-provider-wrapper.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { assert } from "chai"; -import { ethers } from "ethers"; - -import { EthersProviderWrapper } from "../src/internal/ethers-provider-wrapper"; - -import { useEnvironment } from "./helpers"; - -describe("Ethers provider wrapper", function () { - let realProvider: ethers.providers.JsonRpcProvider; - let wrapper: EthersProviderWrapper; - - useEnvironment("hardhat-project"); - - beforeEach(function () { - realProvider = new ethers.providers.JsonRpcProvider( - "http://127.0.0.1:8545" - ); - wrapper = new EthersProviderWrapper(this.env.network.provider); - }); - - it("Should return the same as the real provider", async function () { - const response = await realProvider.send("eth_accounts", []); - const response2 = await wrapper.send("eth_accounts", []); - - assert.deepEqual(response, response2); - }); - - it("Should return the same error", async function () { - this.skip(); - // We disable this test for RskJ - // See: https://github.com/rsksmart/rskj/issues/876 - const version: string = await this.env.network.provider.send( - "web3_clientVersion" - ); - if (version.includes("RskJ")) { - this.skip(); - } - - try { - await realProvider.send("error_please", []); - assert.fail("Ethers provider should have failed"); - } catch (err: any) { - try { - await wrapper.send("error_please", []); - assert.fail("Wrapped provider should have failed"); - } catch (err2: any) { - assert.deepEqual(err2.message, err.message); - } - } - }); -}); diff --git a/packages/hardhat-ethers/test/example-contracts.ts b/packages/hardhat-ethers/test/example-contracts.ts new file mode 100644 index 0000000000..7f463fcd38 --- /dev/null +++ b/packages/hardhat-ethers/test/example-contracts.ts @@ -0,0 +1,58 @@ +import type { + BaseContract, + BaseContractMethod, + BigNumberish, + ContractTransactionResponse, +} from "ethers"; + +/* +contract Example { + uint public value; + uint public doubleValue; + event Inc(); + event AnotherEvent(); + + constructor() payable {} + + function inc() public { + value++; + doubleValue = 2 * value; + emit Inc(); + } + + function emitsTwoEvents() public { + emit Inc(); + emit AnotherEvent(); + } +} +*/ +export const EXAMPLE_CONTRACT = { + deploymentBytecode: + "0x6080604052610284806100136000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063371303c0146100515780633fa4f2451461005b578063b190115914610079578063e377818414610083575b600080fd5b6100596100a1565b005b6100636100fb565b604051610070919061017a565b60405180910390f35b610081610101565b005b61008b61015b565b604051610098919061017a565b60405180910390f35b6000808154809291906100b3906101c4565b919050555060005460026100c7919061020c565b6001819055507fccf19ee637b3555bb918b8270dfab3f2b4ec60236d1ab717296aa85d6921224f60405160405180910390a1565b60005481565b7fccf19ee637b3555bb918b8270dfab3f2b4ec60236d1ab717296aa85d6921224f60405160405180910390a17f601d819e31a3cd164f83f7a7cf9cb5042ab1acff87b773c68f63d059c0af2dc060405160405180910390a1565b60015481565b6000819050919050565b61017481610161565b82525050565b600060208201905061018f600083018461016b565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006101cf82610161565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361020157610200610195565b5b600182019050919050565b600061021782610161565b915061022283610161565b925082820261023081610161565b9150828204841483151761024757610246610195565b5b509291505056fea2646970667358221220bedfe038de0cf21194c025de5a282c3415bf29f716ef1af0073bc2c45d803e8164736f6c63430008110033", + abi: [ + "event Inc()", + "event AnotherEvent()", + "function value() public view returns (uint256)", + "function doubleValue() public view returns (uint256)", + "function inc() public", + "function emitsTwoEvents() public", + ], +}; + +export type ExampleContract = BaseContract & { + inc: BaseContractMethod<[], void, ContractTransactionResponse>; + emitsTwoEvents: BaseContractMethod<[], void, ContractTransactionResponse>; +}; + +export type TestContractLib = BaseContract & { + printNumber: BaseContractMethod< + [BigNumberish], + bigint, + ContractTransactionResponse + >; +}; + +export type GreeterContract = BaseContract & { + greet: BaseContractMethod<[], string, string>; + setGreeting: BaseContractMethod<[string], void, ContractTransactionResponse>; +}; diff --git a/packages/hardhat-ethers/test/fixture-projects/.gitignore b/packages/hardhat-ethers/test/fixture-projects/.gitignore new file mode 100644 index 0000000000..4366b69da9 --- /dev/null +++ b/packages/hardhat-ethers/test/fixture-projects/.gitignore @@ -0,0 +1 @@ +generated-fixtures diff --git a/packages/hardhat-ethers/test/fixture-projects/hardhat-project-with-gas-auto/.gitignore b/packages/hardhat-ethers/test/fixture-projects/hardhat-project-with-gas-auto/.gitignore new file mode 100644 index 0000000000..4a4ecc528f --- /dev/null +++ b/packages/hardhat-ethers/test/fixture-projects/hardhat-project-with-gas-auto/.gitignore @@ -0,0 +1,2 @@ +cache/ +artifacts/ diff --git a/packages/hardhat-ethers/test/fixture-projects/hardhat-project-with-gas-auto/hardhat.config.js b/packages/hardhat-ethers/test/fixture-projects/hardhat-project-with-gas-auto/hardhat.config.js new file mode 100644 index 0000000000..18a8d57186 --- /dev/null +++ b/packages/hardhat-ethers/test/fixture-projects/hardhat-project-with-gas-auto/hardhat.config.js @@ -0,0 +1,10 @@ +require("../../../src/internal/index"); + +module.exports = { + solidity: "0.5.15", + networks: { + hardhat: { + gas: "auto", + }, + }, +}; diff --git a/packages/hardhat-ethers/test/fixture-projects/minimal-project/hardhat.config.js b/packages/hardhat-ethers/test/fixture-projects/minimal-project/hardhat.config.js new file mode 100644 index 0000000000..c1f3384738 --- /dev/null +++ b/packages/hardhat-ethers/test/fixture-projects/minimal-project/hardhat.config.js @@ -0,0 +1,5 @@ +require("../../../src/internal/index"); + +module.exports = { + solidity: "0.8.0", +}; diff --git a/packages/hardhat-ethers/test/gas-config.ts b/packages/hardhat-ethers/test/gas-config.ts new file mode 100644 index 0000000000..13af3010cf --- /dev/null +++ b/packages/hardhat-ethers/test/gas-config.ts @@ -0,0 +1,143 @@ +import { assert, use } from "chai"; +import chaiAsPromised from "chai-as-promised"; + +import { useGeneratedEnvironment } from "./environment"; + +use(chaiAsPromised); + +// This test suite generates different gas configuration +// combinations and runs some common tests for all of them. + +type GasLimitValue = "default" | "auto" | number; +type ConnectedNetwork = "hardhat" | "localhost"; + +interface Combination { + hardhatGasLimit: GasLimitValue; + localhostGasLimit: GasLimitValue; + connectedNetwork: ConnectedNetwork; +} + +function generateCombinations(): Combination[] { + const result: Combination[] = []; + + const hardhatGasLimitValues: GasLimitValue[] = ["default", "auto", 1_000_000]; + const localhostGasLimitValues: GasLimitValue[] = [ + "default", + "auto", + 1_000_000, + ]; + const connectedNetworkValues: ConnectedNetwork[] = ["hardhat", "localhost"]; + + for (const hardhatGasLimit of hardhatGasLimitValues) { + for (const localhostGasLimit of localhostGasLimitValues) { + for (const connectedNetwork of connectedNetworkValues) { + result.push({ + hardhatGasLimit, + localhostGasLimit, + connectedNetwork, + }); + } + } + } + + return result; +} + +describe("gas config behavior", function () { + for (const { + hardhatGasLimit, + localhostGasLimit, + connectedNetwork, + } of generateCombinations()) { + describe(`hardhat gas limit: ${hardhatGasLimit} | localhostGasLimit: ${localhostGasLimit} | connectedNetwork: ${connectedNetwork}`, function () { + useGeneratedEnvironment( + hardhatGasLimit, + localhostGasLimit, + connectedNetwork + ); + + // for some combinations there will be a default gas limit that is used + // when no explicit gas limit is set by the user; in those cases, we + // assert that the tx indeed uses that gas limit; if not, then + // the result of an estimateGas call should be used + let defaultGasLimit: bigint | undefined; + if ( + (connectedNetwork === "hardhat" && hardhatGasLimit === 1_000_000) || + (connectedNetwork === "localhost" && localhostGasLimit === 1_000_000) + ) { + defaultGasLimit = 1_000_000n; + } else if ( + (connectedNetwork === "hardhat" && hardhatGasLimit === "default") || + (connectedNetwork === "localhost" && localhostGasLimit === "default") + ) { + // expect the block gas limit to be used as the default gas limit + defaultGasLimit = 30_000_000n; + } + + it("plain transaction, default gas limit", async function () { + const expectedGasLimit = defaultGasLimit ?? 21_001n; + + const [signer] = await this.env.ethers.getSigners(); + const tx = await signer.sendTransaction({ + to: signer, + }); + + assert.strictEqual(tx.gasLimit, expectedGasLimit); + }); + + it("plain transaction, explicit gas limit", async function () { + const [signer] = await this.env.ethers.getSigners(); + + const tx = await signer.sendTransaction({ + to: signer, + gasLimit: 500_000, + }); + + assert.strictEqual(tx.gasLimit, 500_000n); + }); + + it("contract deployment, default gas limit", async function () { + const expectedGasLimit = defaultGasLimit ?? 76_985n; + + await this.env.run("compile", { quiet: true }); + const example: any = await this.env.ethers.deployContract("Example"); + const deploymentTx = await example.deploymentTransaction(); + + assert.strictEqual(deploymentTx.gasLimit, expectedGasLimit); + }); + + it("contract deployment, explicit gas limit", async function () { + await this.env.run("compile", { quiet: true }); + const Example: any = await this.env.ethers.getContractFactory( + "Example" + ); + const example = await Example.deploy({ + gasLimit: 500_000, + }); + const deploymentTx = await example.deploymentTransaction(); + + assert.strictEqual(deploymentTx.gasLimit, 500_000n); + }); + + it("contract call, default gas limit", async function () { + const expectedGasLimit = defaultGasLimit ?? 21_186n; + + await this.env.run("compile", { quiet: true }); + const example: any = await this.env.ethers.deployContract("Example"); + const tx = await example.f(); + + assert.strictEqual(tx.gasLimit, expectedGasLimit); + }); + + it("contract call, explicit gas limit", async function () { + await this.env.run("compile", { quiet: true }); + const example: any = await this.env.ethers.deployContract("Example"); + const tx = await example.f({ + gasLimit: 500_000, + }); + + assert.strictEqual(tx.gasLimit, 500_000n); + }); + }); + } +}); diff --git a/packages/hardhat-ethers/test/gas-price.ts b/packages/hardhat-ethers/test/gas-price.ts new file mode 100644 index 0000000000..6fb5ca44d9 --- /dev/null +++ b/packages/hardhat-ethers/test/gas-price.ts @@ -0,0 +1,184 @@ +import { assert, use } from "chai"; +import chaiAsPromised from "chai-as-promised"; + +import { useEnvironment } from "./environment"; + +use(chaiAsPromised); + +describe("gas price overrides", function () { + describe("in-process hardhat network", function () { + useEnvironment("hardhat-project", "hardhat"); + + runTests(); + }); + + describe("hardhat node", function () { + useEnvironment("hardhat-project", "localhost"); + + runTests(); + }); +}); + +function runTests() { + describe("plain transactions", function () { + it("should use the given gas price if specified", async function () { + const [signer] = await this.env.ethers.getSigners(); + + const tx = await signer.sendTransaction({ + to: signer, + gasPrice: this.env.ethers.parseUnits("10", "gwei"), + }); + + const receipt = await tx.wait(); + + assert.strictEqual(tx.gasPrice, 10n * 10n ** 9n); + assert.strictEqual(receipt?.gasPrice, 10n * 10n ** 9n); + }); + + it("should use EIP-1559 values if maxFeePerGas and maxPriorityFeePerGas are specified, maxFeePerGas = baseFeePerGas", async function () { + const [signer] = await this.env.ethers.getSigners(); + + const baseFeePerGas = this.env.ethers.parseUnits("10", "gwei"); + const maxFeePerGas = baseFeePerGas; + const maxPriorityFeePerGas = this.env.ethers.parseUnits("1", "gwei"); + + await this.env.network.provider.send( + "hardhat_setNextBlockBaseFeePerGas", + [`0x${baseFeePerGas.toString(16)}`] + ); + + const tx = await signer.sendTransaction({ + to: signer, + maxFeePerGas, + maxPriorityFeePerGas, + }); + + const receipt = await tx.wait(); + + assert.strictEqual(tx.maxFeePerGas, maxFeePerGas); + assert.strictEqual(tx.maxPriorityFeePerGas, maxPriorityFeePerGas); + assert.strictEqual(receipt?.gasPrice, maxFeePerGas); + }); + + it("should use EIP-1559 values if maxFeePerGas and maxPriorityFeePerGas are specified, maxFeePerGas > baseFeePerGas", async function () { + const [signer] = await this.env.ethers.getSigners(); + + const baseFeePerGas = this.env.ethers.parseUnits("5", "gwei"); + const maxFeePerGas = this.env.ethers.parseUnits("10", "gwei"); + const maxPriorityFeePerGas = this.env.ethers.parseUnits("1", "gwei"); + + await this.env.network.provider.send( + "hardhat_setNextBlockBaseFeePerGas", + [`0x${baseFeePerGas.toString(16)}`] + ); + + const tx = await signer.sendTransaction({ + to: signer, + maxFeePerGas, + maxPriorityFeePerGas, + }); + + const receipt = await tx.wait(); + + assert.strictEqual(tx.maxFeePerGas, maxFeePerGas); + assert.strictEqual(tx.maxPriorityFeePerGas, maxPriorityFeePerGas); + assert.strictEqual( + receipt?.gasPrice, + baseFeePerGas + maxPriorityFeePerGas + ); + }); + + it("should use a default gas price if no value is specified", async function () { + const [signer] = await this.env.ethers.getSigners(); + + // we don't run any assertions here because the strategy + // used to set the default gas prices might change; we + // just check that the transaction is mined correctly + const tx = await signer.sendTransaction({ + to: signer, + }); + + await tx.wait(); + }); + + it("should use a default value for maxPriorityFeePerGas if maxFeePerGas is the only value specified", async function () { + const [signer] = await this.env.ethers.getSigners(); + + const baseFeePerGas = this.env.ethers.parseUnits("5", "gwei"); + const maxFeePerGas = this.env.ethers.parseUnits("10", "gwei"); + + // make sure that the max fee is enough + await this.env.network.provider.send( + "hardhat_setNextBlockBaseFeePerGas", + [`0x${baseFeePerGas.toString(16)}`] + ); + + const tx = await signer.sendTransaction({ + to: signer, + maxFeePerGas, + }); + + // we just check that the EIP-1559 values are set, because the + // strategy to select a default priority fee might change + assert.exists(tx.maxFeePerGas); + assert.exists(tx.maxPriorityFeePerGas); + }); + + it("should use a default maxFeePerGas if only maxPriorityFeePerGas is specified", async function () { + const [signer] = await this.env.ethers.getSigners(); + + const maxPriorityFeePerGas = this.env.ethers.parseUnits("1", "gwei"); + + const tx = await signer.sendTransaction({ + to: signer, + maxPriorityFeePerGas, + }); + + // we just check that the max fee is set, because the + // strategy to select its value might change + assert.exists(tx.maxFeePerGas); + + assert.strictEqual(tx.maxPriorityFeePerGas, maxPriorityFeePerGas); + }); + + it("should throw if both gasPrice and maxFeePerGas are specified", async function () { + const [signer] = await this.env.ethers.getSigners(); + + await assert.isRejected( + signer.sendTransaction({ + to: signer, + gasPrice: this.env.ethers.parseUnits("10", "gwei"), + maxFeePerGas: this.env.ethers.parseUnits("10", "gwei"), + }), + "Cannot send both gasPrice and maxFeePerGas params" + ); + }); + + it("should throw if both gasPrice and maxPriorityFeePerGas are specified", async function () { + const [signer] = await this.env.ethers.getSigners(); + + await assert.isRejected( + signer.sendTransaction({ + to: signer, + gasPrice: this.env.ethers.parseUnits("10", "gwei"), + maxPriorityFeePerGas: this.env.ethers.parseUnits("10", "gwei"), + }), + "Cannot send both gasPrice and maxPriorityFeePerGas" + ); + }); + + it("should throw if gasPrice, maxFeePerGas and maxPriorityFeePerGas are specified", async function () { + const [signer] = await this.env.ethers.getSigners(); + + await assert.isRejected( + signer.sendTransaction({ + to: signer, + gasPrice: this.env.ethers.parseUnits("10", "gwei"), + maxFeePerGas: this.env.ethers.parseUnits("10", "gwei"), + maxPriorityFeePerGas: this.env.ethers.parseUnits("10", "gwei"), + }), + "Cannot send both gasPrice and maxFeePerGas" + ); + }); + }); +} diff --git a/packages/hardhat-ethers/test/hardhat-ethers-provider.ts b/packages/hardhat-ethers/test/hardhat-ethers-provider.ts new file mode 100644 index 0000000000..3faa40eaba --- /dev/null +++ b/packages/hardhat-ethers/test/hardhat-ethers-provider.ts @@ -0,0 +1,1040 @@ +import { assert, use } from "chai"; +import chaiAsPromised from "chai-as-promised"; + +import { ExampleContract, EXAMPLE_CONTRACT } from "./example-contracts"; +import { usePersistentEnvironment } from "./environment"; +import { assertIsNotNull, assertWithin } from "./helpers"; + +use(chaiAsPromised); + +describe("hardhat ethers provider", function () { + usePersistentEnvironment("minimal-project"); + + it("can access itself through .provider", async function () { + assert.strictEqual( + this.env.ethers.provider, + this.env.ethers.provider.provider + ); + }); + + it("should have a destroy method", async function () { + this.env.ethers.provider.destroy(); + }); + + it("should have a send method for raw JSON-RPC requests", async function () { + const accounts = await this.env.ethers.provider.send("eth_accounts"); + + assert.isArray(accounts); + }); + + describe("getSigner", function () { + it("should get a signer using an index", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + assert.strictEqual( + await signer.getAddress(), + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + ); + }); + + it("should get a signer using an address", async function () { + const signer = await this.env.ethers.provider.getSigner( + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + ); + + assert.strictEqual( + await signer.getAddress(), + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + ); + }); + + it("should get a signer even if the address is all lowercase", async function () { + const signer = await this.env.ethers.provider.getSigner( + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" + ); + + assert.strictEqual( + await signer.getAddress(), + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + ); + }); + + it("should throw if the address checksum is wrong", async function () { + await assert.isRejected( + this.env.ethers.provider.getSigner( + "0XF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266" + ), + "invalid address" + ); + }); + + it("should throw if the index doesn't match an account", async function () { + await assert.isRejected( + this.env.ethers.provider.getSigner(100), + "Tried to get account with index 100 but there are 20 accounts" + ); + }); + + it("should work for impersonated accounts", async function () { + const [s] = await this.env.ethers.getSigners(); + const randomAddress = "0xf965cceab9374d9f961581b6e38942e45e1cfeae"; + + await s.sendTransaction({ + to: randomAddress, + value: this.env.ethers.parseEther("1"), + }); + + await this.env.ethers.provider.send("hardhat_impersonateAccount", [ + randomAddress, + ]); + + const impersonatedSigner = await this.env.ethers.provider.getSigner( + randomAddress + ); + + // shouldn't revert + await impersonatedSigner.sendTransaction({ + to: s.address, + value: this.env.ethers.parseEther("0.1"), + }); + }); + }); + + it("should return the latest block number", async function () { + const latestBlockNumber = await this.env.ethers.provider.getBlockNumber(); + + assert.strictEqual(latestBlockNumber, 0); + + await this.env.ethers.provider.send("hardhat_mine"); + + assert.strictEqual(latestBlockNumber, 0); + }); + + it("should return the network", async function () { + const network = await this.env.ethers.provider.getNetwork(); + + assert.strictEqual(network.name, "hardhat"); + assert.strictEqual(network.chainId, 31337n); + }); + + it("should return fee data", async function () { + const feeData = await this.env.ethers.provider.getFeeData(); + + assert.typeOf(feeData.gasPrice, "bigint"); + assert.typeOf(feeData.maxFeePerGas, "bigint"); + assert.typeOf(feeData.maxPriorityFeePerGas, "bigint"); + }); + + describe("getBalance", function () { + beforeEach(async function () { + await this.env.network.provider.send("hardhat_reset"); + }); + + it("should return the balance of an address", async function () { + const balance = await this.env.ethers.provider.getBalance( + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + ); + + assert.strictEqual(balance, this.env.ethers.parseEther("10000")); + }); + + it("should return the balance of a signer", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const balance = await this.env.ethers.provider.getBalance(signer); + + assert.strictEqual(balance, this.env.ethers.parseEther("10000")); + }); + + it("should accept block numbers", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const gasLimit = 21_000n; + const gasPrice = this.env.ethers.parseUnits("100", "gwei"); + const value = this.env.ethers.parseEther("1"); + + await signer.sendTransaction({ + to: this.env.ethers.ZeroAddress, + value, + gasLimit, + gasPrice, + }); + + const blockNumber = await this.env.ethers.provider.getBlockNumber(); + + const balanceAfter = await this.env.ethers.provider.getBalance( + signer, + "latest" + ); + assert.strictEqual( + balanceAfter, + this.env.ethers.parseEther("10000") - gasLimit * gasPrice - value + ); + + const balanceBefore = await this.env.ethers.provider.getBalance( + signer, + blockNumber - 1 + ); + assert.strictEqual(balanceBefore, this.env.ethers.parseEther("10000")); + }); + + it("should accept block hashes", async function () { + const block = await this.env.ethers.provider.getBlock("latest"); + + assertIsNotNull(block); + assertIsNotNull(block.hash); + + const balance = await this.env.ethers.provider.getBalance( + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + block.hash + ); + + assert.strictEqual(balance, this.env.ethers.parseEther("10000")); + }); + + it("should return the balance of a contract", async function () { + // deploy a contract with some ETH + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract = await factory.deploy({ + value: this.env.ethers.parseEther("0.5"), + }); + + // check the balance of the contract + const balance = await this.env.ethers.provider.getBalance(contract); + + assert.strictEqual(balance, 5n * 10n ** 17n); + }); + }); + + describe("getTransactionCount", function () { + beforeEach(async function () { + await this.env.network.provider.send("hardhat_reset"); + }); + + it("should return the transaction count of an address", async function () { + const balance = await this.env.ethers.provider.getTransactionCount( + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + ); + + assert.strictEqual(balance, 0); + }); + + it("should return the transaction count of a signer", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const balance = await this.env.ethers.provider.getTransactionCount( + signer + ); + + assert.strictEqual(balance, 0); + }); + + it("should accept block numbers", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + await signer.sendTransaction({ + to: this.env.ethers.ZeroAddress, + }); + + const blockNumber = await this.env.ethers.provider.getBlockNumber(); + + const transactionCountAfter = + await this.env.ethers.provider.getTransactionCount(signer, "latest"); + assert.strictEqual(transactionCountAfter, 1); + + const transactionCountBefore = + await this.env.ethers.provider.getTransactionCount( + signer, + blockNumber - 1 + ); + assert.strictEqual(transactionCountBefore, 0); + }); + + it("should accept block hashes", async function () { + const block = await this.env.ethers.provider.getBlock("latest"); + + assertIsNotNull(block); + assertIsNotNull(block.hash); + + const balance = await this.env.ethers.provider.getTransactionCount( + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + block.hash + ); + + assert.strictEqual(balance, 0); + }); + }); + + describe("getCode", function () { + // deploys an empty contract + const deploymentBytecode = + "0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220eeaf807039e8b863535433564733b36afab56700620e89f192795eaf32f272ee64736f6c63430008110033"; + const contractBytecode = + "0x6080604052600080fdfea2646970667358221220eeaf807039e8b863535433564733b36afab56700620e89f192795eaf32f272ee64736f6c63430008110033"; + + let contract: ExampleContract; + beforeEach(async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + [], + deploymentBytecode, + signer + ); + contract = await factory.deploy(); + }); + + it("should return the code of an address", async function () { + const contractAddress = await contract.getAddress(); + + const code = await this.env.ethers.provider.getCode(contractAddress); + + assert.strictEqual(code, contractBytecode); + }); + + it("should return the code of a contract", async function () { + const code = await this.env.ethers.provider.getCode(contract); + + assert.strictEqual(code, contractBytecode); + }); + + it("should accept block numbers", async function () { + const codeAfter = await this.env.ethers.provider.getCode( + contract, + "latest" + ); + assert.strictEqual(codeAfter, contractBytecode); + + const blockNumber = await this.env.ethers.provider.getBlockNumber(); + const codeBefore = await this.env.ethers.provider.getCode( + contract, + blockNumber - 1 + ); + assert.strictEqual(codeBefore, "0x"); + }); + + it("should accept block hashes", async function () { + const block = await this.env.ethers.provider.getBlock("latest"); + + assertIsNotNull(block); + assertIsNotNull(block.hash); + + const code = await this.env.ethers.provider.getCode(contract, block.hash); + assert.strictEqual(code, contractBytecode); + }); + }); + + describe("getStorage", function () { + let contract: ExampleContract; + beforeEach(async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + contract = await factory.deploy(); + }); + + it("should get the storage of an address", async function () { + const contractAddress = await contract.getAddress(); + + await contract.inc(); + + const value = await this.env.ethers.provider.getStorage( + contractAddress, + 0 + ); + const doubleValue = await this.env.ethers.provider.getStorage( + contractAddress, + 1 + ); + + assert.strictEqual( + value, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + assert.strictEqual( + doubleValue, + "0x0000000000000000000000000000000000000000000000000000000000000002" + ); + }); + + it("should get the storage of a contract", async function () { + await contract.inc(); + + const value = await this.env.ethers.provider.getStorage(contract, 0); + const doubleValue = await this.env.ethers.provider.getStorage( + contract, + 1 + ); + + assert.strictEqual( + value, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + assert.strictEqual( + doubleValue, + "0x0000000000000000000000000000000000000000000000000000000000000002" + ); + }); + + it("should accept block numbers", async function () { + await contract.inc(); + + const storageValueAfter = await this.env.ethers.provider.getStorage( + contract, + 0, + "latest" + ); + assert.strictEqual( + storageValueAfter, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + + const blockNumber = await this.env.ethers.provider.getBlockNumber(); + const storageValueBefore = await this.env.ethers.provider.getStorage( + contract, + 0, + blockNumber - 1 + ); + assert.strictEqual( + storageValueBefore, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); + }); + + it("should accept block hashes", async function () { + await contract.inc(); + + const block = await this.env.ethers.provider.getBlock("latest"); + + assertIsNotNull(block); + assertIsNotNull(block.hash); + + const storageValue = await this.env.ethers.provider.getStorage( + contract, + 0, + block.hash + ); + + assert.strictEqual( + storageValue, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + }); + + it("should accept short hex encode strings as the storage position", async function () { + await contract.inc(); + + const value = await this.env.ethers.provider.getStorage(contract, "0x0"); + const doubleValue = await this.env.ethers.provider.getStorage( + contract, + "0x1" + ); + assert.strictEqual( + value, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + assert.strictEqual( + doubleValue, + "0x0000000000000000000000000000000000000000000000000000000000000002" + ); + }); + + it("should accept long hex encode strings as the storage position", async function () { + await contract.inc(); + + const value = await this.env.ethers.provider.getStorage( + contract, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); + const doubleValue = await this.env.ethers.provider.getStorage( + contract, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + assert.strictEqual( + value, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + assert.strictEqual( + doubleValue, + "0x0000000000000000000000000000000000000000000000000000000000000002" + ); + }); + + it("should accept bigints as the storage position", async function () { + await contract.inc(); + + const value = await this.env.ethers.provider.getStorage(contract, 0n); + const doubleValue = await this.env.ethers.provider.getStorage( + contract, + 1n + ); + assert.strictEqual( + value, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + assert.strictEqual( + doubleValue, + "0x0000000000000000000000000000000000000000000000000000000000000002" + ); + }); + }); + + describe("estimateGas", function () { + it("should estimate gas for a value transaction", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const gasEstimation = await this.env.ethers.provider.estimateGas({ + from: signer.address, + to: signer.address, + }); + + assert.strictEqual(Number(gasEstimation), 21_001); + }); + + it("should estimate gas for a contract call", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract = await factory.deploy(); + + const gasEstimation = await this.env.ethers.provider.estimateGas({ + from: signer.address, + to: await contract.getAddress(), + data: "0x371303c0", // inc() + }); + + assertWithin(Number(gasEstimation), 65_000, 70_000); + }); + + it("should accept a block number", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract = await factory.deploy(); + + await contract.inc(); + const blockNumber = await this.env.ethers.provider.getBlockNumber(); + + const gasEstimationAfter = await this.env.ethers.provider.estimateGas({ + from: signer.address, + to: await contract.getAddress(), + data: "0x371303c0", // inc() + blockTag: "latest", + }); + + assertWithin(Number(gasEstimationAfter), 30_000, 35_000); + + const gasEstimationBefore = await this.env.ethers.provider.estimateGas({ + from: signer.address, + to: await contract.getAddress(), + data: "0x371303c0", // inc() + blockTag: blockNumber - 1, + }); + + assertWithin(Number(gasEstimationBefore), 65_000, 70_000); + }); + + it("should accept a block hash", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract = await factory.deploy(); + + const block = await this.env.ethers.provider.getBlock("latest"); + + assertIsNotNull(block); + assertIsNotNull(block.hash); + + const gasEstimation = await this.env.ethers.provider.estimateGas({ + from: signer.address, + to: await contract.getAddress(), + data: "0x371303c0", // inc() + blockTag: block.hash, + }); + + assertWithin(Number(gasEstimation), 65_000, 70_000); + }); + + it("should use the pending block by default", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract = await factory.deploy(); + + // this estimates the cost of increasing the value from 0 to 1 + const gasEstimationFirstInc = await this.env.ethers.provider.estimateGas({ + from: signer.address, + to: await contract.getAddress(), + data: "0x371303c0", // inc() + }); + + await this.env.ethers.provider.send("evm_setAutomine", [false]); + await contract.inc(); + + // if the pending block is used, this should estimate the cost of + // increasing the value from 1 to 2, and this should be cheaper than + // increasing it from 0 to 1 + const gasEstimationSecondInc = await this.env.ethers.provider.estimateGas( + { + from: signer.address, + to: await contract.getAddress(), + data: "0x371303c0", // inc() + } + ); + + assert.isTrue( + gasEstimationSecondInc < gasEstimationFirstInc, + "Expected second gas estimation to be lower" + ); + + await this.env.ethers.provider.send("evm_setAutomine", [true]); + }); + }); + + describe("call", function () { + it("should make a contract call using an address", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract = await factory.deploy(); + await contract.inc(); + + const result = await this.env.ethers.provider.call({ + from: signer.address, + to: await contract.getAddress(), + data: "0x3fa4f245", // value() + }); + + assert.strictEqual( + result, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + }); + + it("should make a contract call using a contract", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract = await factory.deploy(); + await contract.inc(); + + const result = await this.env.ethers.provider.call({ + from: signer.address, + to: contract, + data: "0x3fa4f245", // value() + }); + + assert.strictEqual( + result, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + }); + + it("should accept a block number", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract = await factory.deploy(); + await contract.inc(); + + const blockNumber = await this.env.ethers.provider.getBlockNumber(); + + const resultAfter = await this.env.ethers.provider.call({ + from: signer.address, + to: contract, + data: "0x3fa4f245", // value() + blockTag: "latest", + }); + + assert.strictEqual( + resultAfter, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + + const resultBefore = await this.env.ethers.provider.call({ + from: signer.address, + to: contract, + data: "0x3fa4f245", // value() + blockTag: blockNumber - 1, + }); + + assert.strictEqual( + resultBefore, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); + }); + + it("should accept a block hash", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract = await factory.deploy(); + await contract.inc(); + + const block = await this.env.ethers.provider.getBlock("latest"); + + assertIsNotNull(block); + assertIsNotNull(block.hash); + + const result = await this.env.ethers.provider.call({ + from: signer.address, + to: contract, + data: "0x3fa4f245", // value() + blockTag: block.hash, + }); + + assert.strictEqual( + result, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + }); + }); + + describe("broadcastTransaction", function () { + it("should send a raw transaction", async function () { + await this.env.ethers.provider.send("hardhat_reset"); + // private key of the first unlocked account + const wallet = new this.env.ethers.Wallet( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + this.env.ethers.provider + ); + const rawTx = await wallet.signTransaction({ + to: this.env.ethers.ZeroAddress, + chainId: 31337, + gasPrice: 100n * 10n ** 9n, + gasLimit: 21_000, + }); + + const tx = await this.env.ethers.provider.broadcastTransaction(rawTx); + + assert.strictEqual(tx.from, wallet.address); + assert.strictEqual(tx.to, this.env.ethers.ZeroAddress); + assert.strictEqual(tx.gasLimit, 21_000n); + }); + }); + + describe("getBlock", function () { + it("should accept latest and earliest block tags", async function () { + await this.env.ethers.provider.send("hardhat_reset"); + await this.env.ethers.provider.send("hardhat_mine"); + await this.env.ethers.provider.send("hardhat_mine"); + await this.env.ethers.provider.send("hardhat_mine"); + + const latestBlock = await this.env.ethers.provider.getBlock("latest"); + assertIsNotNull(latestBlock); + assert.strictEqual(latestBlock.number, 3); + + const earliestBlock = await this.env.ethers.provider.getBlock("earliest"); + assertIsNotNull(earliestBlock); + assert.strictEqual(earliestBlock.number, 0); + }); + + it("should accept numbers", async function () { + await this.env.ethers.provider.send("hardhat_reset"); + await this.env.ethers.provider.send("hardhat_mine"); + await this.env.ethers.provider.send("hardhat_mine"); + await this.env.ethers.provider.send("hardhat_mine"); + + const latestBlock = await this.env.ethers.provider.getBlock(3); + assertIsNotNull(latestBlock); + assert.strictEqual(latestBlock.number, 3); + + const earliestBlock = await this.env.ethers.provider.getBlock(1); + assertIsNotNull(earliestBlock); + assert.strictEqual(earliestBlock.number, 1); + }); + + it("should accept block hashes", async function () { + await this.env.ethers.provider.send("hardhat_reset"); + + const blockByNumber = await this.env.ethers.provider.getBlock(0); + assertIsNotNull(blockByNumber); + assertIsNotNull(blockByNumber.hash); + + const blockByHash = await this.env.ethers.provider.getBlock( + blockByNumber.hash + ); + assertIsNotNull(blockByHash); + + assert.strictEqual(blockByNumber.number, blockByHash.number); + assert.strictEqual(blockByNumber.hash, blockByHash.hash); + }); + + it("shouldn't prefetch transactions by default", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const tx = await signer.sendTransaction({ to: signer.address }); + + const block = await this.env.ethers.provider.getBlock("latest"); + assertIsNotNull(block); + + assert.lengthOf(block.transactions, 1); + assert.strictEqual(block.transactions[0], tx.hash); + + assert.throws(() => block.prefetchedTransactions); + }); + + it("shouldn't prefetch transactions if false is passed", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const tx = await signer.sendTransaction({ to: signer.address }); + + const block = await this.env.ethers.provider.getBlock("latest", false); + assertIsNotNull(block); + + assert.lengthOf(block.transactions, 1); + assert.strictEqual(block.transactions[0], tx.hash); + + assert.throws(() => block.prefetchedTransactions); + }); + + it("should prefetch transactions if true is passed", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const tx = await signer.sendTransaction({ to: signer.address }); + + const block = await this.env.ethers.provider.getBlock("latest", true); + assertIsNotNull(block); + + assert.lengthOf(block.transactions, 1); + assert.strictEqual(block.transactions[0], tx.hash); + + assert.lengthOf(block.prefetchedTransactions, 1); + assert.strictEqual(block.prefetchedTransactions[0].hash, tx.hash); + assert.strictEqual(block.prefetchedTransactions[0].from, signer.address); + }); + }); + + describe("getTransaction", function () { + it("should get a transaction by its hash", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const sentTx = await signer.sendTransaction({ to: signer.address }); + + const fetchedTx = await this.env.ethers.provider.getTransaction( + sentTx.hash + ); + + assertIsNotNull(fetchedTx); + assert.strictEqual(fetchedTx.hash, sentTx.hash); + }); + + it("should return null if the transaction doesn't exist", async function () { + const tx = await this.env.ethers.provider.getTransaction( + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); + + assert.isNull(tx); + }); + }); + + describe("getTransactionReceipt", function () { + it("should get a receipt by the transaction hash", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const tx = await signer.sendTransaction({ to: signer.address }); + + const receipt = await this.env.ethers.provider.getTransactionReceipt( + tx.hash + ); + + assertIsNotNull(receipt); + assert.strictEqual(receipt.hash, tx.hash); + assert.strictEqual(receipt.status, 1); + }); + + it("should return null if the transaction doesn't exist", async function () { + const receipt = await this.env.ethers.provider.getTransactionReceipt( + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); + + assert.isNull(receipt); + }); + }); + + describe("getLogs", function () { + // keccak("Inc()") + const INC_EVENT_TOPIC = + "0xccf19ee637b3555bb918b8270dfab3f2b4ec60236d1ab717296aa85d6921224f"; + // keccak("AnotherEvent()") + const ANOTHER_EVENT_TOPIC = + "0x601d819e31a3cd164f83f7a7cf9cb5042ab1acff87b773c68f63d059c0af2dc0"; + + it("should get the logs from the latest block by default", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract = await factory.deploy(); + + await contract.inc(); + + const logs = await this.env.ethers.provider.getLogs({}); + + assert.lengthOf(logs, 1); + + const log = logs[0]; + assert.strictEqual(log.address, await contract.getAddress()); + assert.lengthOf(log.topics, 1); + assert.strictEqual(log.topics[0], INC_EVENT_TOPIC); + }); + + it("should get the logs by block number", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract = await factory.deploy(); + + await contract.inc(); + await this.env.ethers.provider.send("hardhat_mine"); + const blockNumber = await this.env.ethers.provider.getBlockNumber(); + + // latest block shouldn't have logs + const latestBlockLogs = await this.env.ethers.provider.getLogs({ + fromBlock: blockNumber, + toBlock: blockNumber, + }); + assert.lengthOf(latestBlockLogs, 0); + + const logs = await this.env.ethers.provider.getLogs({ + fromBlock: blockNumber - 1, + toBlock: blockNumber - 1, + }); + + assert.lengthOf(logs, 1); + + const log = logs[0]; + assert.strictEqual(log.address, await contract.getAddress()); + assert.lengthOf(log.topics, 1); + assert.strictEqual(log.topics[0], INC_EVENT_TOPIC); + }); + + it("should get the logs by address", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract1 = await factory.deploy(); + const contract2 = await factory.deploy(); + + await contract1.inc(); + await contract2.inc(); + const blockNumber = await this.env.ethers.provider.getBlockNumber(); + + const logs = await this.env.ethers.provider.getLogs({ + fromBlock: blockNumber - 1, + toBlock: blockNumber, + }); + + assert.lengthOf(logs, 2); + + const logsByAddress = await this.env.ethers.provider.getLogs({ + address: await contract1.getAddress(), + fromBlock: blockNumber - 1, + toBlock: blockNumber, + }); + + assert.lengthOf(logsByAddress, 1); + assert.strictEqual( + logsByAddress[0].address, + await contract1.getAddress() + ); + }); + + it("should get the logs by an array of addresses", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract1 = await factory.deploy(); + const contract2 = await factory.deploy(); + const contract3 = await factory.deploy(); + + await contract1.inc(); + await contract2.inc(); + await contract3.inc(); + const blockNumber = await this.env.ethers.provider.getBlockNumber(); + + const logs = await this.env.ethers.provider.getLogs({ + fromBlock: blockNumber - 2, + toBlock: blockNumber, + }); + + assert.lengthOf(logs, 3); + + const contract1Address = await contract1.getAddress(); + const contract2Address = await contract2.getAddress(); + const logsByAddress = await this.env.ethers.provider.getLogs({ + address: [contract1Address, contract2Address], + fromBlock: blockNumber - 2, + toBlock: blockNumber, + }); + + assert.lengthOf(logsByAddress, 2); + assert.strictEqual(logsByAddress[0].address, contract1Address); + assert.strictEqual(logsByAddress[1].address, contract2Address); + }); + + it("should get the logs by topic", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory<[], ExampleContract>( + EXAMPLE_CONTRACT.abi, + EXAMPLE_CONTRACT.deploymentBytecode, + signer + ); + const contract = await factory.deploy(); + + await contract.emitsTwoEvents(); + + const logs = await this.env.ethers.provider.getLogs({}); + assert.lengthOf(logs, 2); + + const incEventLogs = await this.env.ethers.provider.getLogs({ + topics: [INC_EVENT_TOPIC], + }); + + assert.lengthOf(incEventLogs, 1); + assert.lengthOf(incEventLogs[0].topics, 1); + assert.strictEqual(incEventLogs[0].topics[0], INC_EVENT_TOPIC); + + const anotherEventLogs = await this.env.ethers.provider.getLogs({ + topics: [ANOTHER_EVENT_TOPIC], + }); + + assert.lengthOf(anotherEventLogs, 1); + assert.lengthOf(anotherEventLogs[0].topics, 1); + assert.strictEqual(anotherEventLogs[0].topics[0], ANOTHER_EVENT_TOPIC); + }); + }); +}); diff --git a/packages/hardhat-ethers/test/hardhat-ethers-signer.ts b/packages/hardhat-ethers/test/hardhat-ethers-signer.ts new file mode 100644 index 0000000000..10ca35870b --- /dev/null +++ b/packages/hardhat-ethers/test/hardhat-ethers-signer.ts @@ -0,0 +1,296 @@ +import { assert } from "chai"; + +import { usePersistentEnvironment } from "./environment"; +import { ExampleContract, EXAMPLE_CONTRACT } from "./example-contracts"; +import { assertIsNotNull, assertWithin } from "./helpers"; + +describe("hardhat ethers signer", function () { + describe("minimal project", function () { + usePersistentEnvironment("minimal-project"); + + it("has an address field that matches the address", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + assert.isString(signer.address); + assert.strictEqual(signer.address, await signer.getAddress()); + }); + + it("can be connected to a provider", async function () { + if ( + process.env.INFURA_URL === undefined || + process.env.INFURA_URL === "" + ) { + this.skip(); + } + + const signerConnectedToHardhat = await this.env.ethers.provider.getSigner( + 0 + ); + + const nonceInHardhat = await signerConnectedToHardhat.getNonce(); + + const mainnetProvider = new this.env.ethers.JsonRpcProvider( + process.env.INFURA_URL + ); + + const signerConnectedToMainnet = + signerConnectedToHardhat.connect(mainnetProvider); + + const nonceInMainnet = await signerConnectedToMainnet.getNonce(); + + assert.strictEqual(nonceInHardhat, 0); + assert.isAbove(nonceInMainnet, 0); + }); + + it("can get the nonce of the signer", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + assert.strictEqual(await signer.getNonce(), 0); + + await signer.sendTransaction({ to: signer }); + assert.strictEqual(await signer.getNonce(), 1); + }); + + it("should populate a call/tx", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + const populatedCall = await signer.populateCall({ + to: signer, + }); + + assert.strictEqual(populatedCall.from, signer.address); + + // populateTransaction does exactly the same + const populatedTx = await signer.populateCall({ + to: signer, + }); + + assert.strictEqual(populatedTx.from, signer.address); + }); + + describe("estimateGas", function () { + it("should estimate gas for a value transaction", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const gasEstimation = await signer.estimateGas({ + to: signer, + }); + + assert.strictEqual(Number(gasEstimation), 21_001); + }); + + it("should estimate gas for a contract call", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory< + [], + ExampleContract + >(EXAMPLE_CONTRACT.abi, EXAMPLE_CONTRACT.deploymentBytecode, signer); + const contract = await factory.deploy(); + + const gasEstimation = await signer.estimateGas({ + to: contract, + data: "0x371303c0", // inc() + }); + + assertWithin(Number(gasEstimation), 65_000, 70_000); + }); + }); + + describe("call", function () { + it("should make a contract call", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + const factory = new this.env.ethers.ContractFactory< + [], + ExampleContract + >(EXAMPLE_CONTRACT.abi, EXAMPLE_CONTRACT.deploymentBytecode, signer); + const contract = await factory.deploy(); + await contract.inc(); + + const result = await signer.call({ + to: contract, + data: "0x3fa4f245", // value() + }); + + assert.strictEqual( + result, + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + }); + }); + + describe("sendTransaction", function () { + it("should send a transaction", async function () { + const sender = await this.env.ethers.provider.getSigner(0); + const receiver = await this.env.ethers.provider.getSigner(1); + + const balanceBefore = await this.env.ethers.provider.getBalance( + receiver + ); + + await sender.sendTransaction({ + to: receiver, + value: this.env.ethers.parseEther("1"), + }); + + const balanceAfter = await this.env.ethers.provider.getBalance( + receiver + ); + + const balanceDifference = balanceAfter - balanceBefore; + + assert.strictEqual(balanceDifference, 10n ** 18n); + }); + }); + + describe("signMessage", function () { + it("should sign a message", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + const signedMessage = await signer.signMessage("hello"); + + assert.strictEqual( + signedMessage, + "0xf16ea9a3478698f695fd1401bfe27e9e4a7e8e3da94aa72b021125e31fa899cc573c48ea3fe1d4ab61a9db10c19032026e3ed2dbccba5a178235ac27f94504311c" + ); + }); + }); + + describe("signTypedData", function () { + const types = { + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" }, + ], + Mail: [ + { name: "from", type: "Person" }, + { name: "to", type: "Person" }, + { name: "contents", type: "string" }, + ], + }; + + const data = { + from: { + name: "John", + wallet: "0x0000000000000000000000000000000000000001", + }, + to: { + name: "Mark", + wallet: "0x0000000000000000000000000000000000000002", + }, + contents: "something", + }; + + it("should sign data", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + const signedData = await signer.signTypedData( + { + chainId: 31337, + }, + types, + data + ); + + assert.strictEqual( + signedData, + "0xbea20009786d1f69327eea384d6b8082f2d35b41212d1acbbd490516f0ae776748e93d4603df49033f89ce6a97afba4523d753d35e962ea431cc706642ad713f1b" + ); + }); + + it("should use the chain id", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + const signedData = await signer.signTypedData( + { + chainId: 10101, + }, + types, + data + ); + + // we get a different value from the different test because we changed the + // chainId + assert.strictEqual( + signedData, + "0x8a6a6aeca0cf03dbffd6d7b15207c0dcf5c7daa432e510b5de1ebecff8de6cd457e2eaa9fe96c11474a7344584f4b128c773153836142647c426b5f2c3eb6c701b" + ); + }); + }); + + describe("default gas limit", function () { + it("should use the block gas limit for the in-process hardhat network", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + const tx = await signer.sendTransaction({ to: signer }); + + if (!("blockGasLimit" in this.env.network.config)) { + assert.fail("test should be run in the hardhat network"); + } + + const blockGasLimit = this.env.network.config.blockGasLimit; + assert.strictEqual(Number(tx.gasLimit), blockGasLimit); + }); + + it("should use custom gas limit, if provided", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + const tx = await signer.sendTransaction({ + to: signer, + gasLimit: 30_000, + }); + + assert.strictEqual(tx.gasLimit, 30_000n); + }); + }); + + describe("nonce management", function () { + it("should send a second transaction with the right nonce if the first one wasn't mined", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + await this.env.ethers.provider.send("evm_setAutomine", [false]); + + const tx1 = await signer.sendTransaction({ + to: signer, + gasLimit: 30_000, + }); + const tx2 = await signer.sendTransaction({ + to: signer, + gasLimit: 30_000, + }); + + assert.notEqual(tx1.nonce, tx2.nonce); + assert.strictEqual(tx2.nonce, tx1.nonce + 1); + + await this.env.ethers.provider.send("hardhat_mine", []); + + const latestBlock = await this.env.ethers.provider.getBlock("latest"); + + assertIsNotNull(latestBlock); + + assert.lengthOf(latestBlock.transactions, 2); + }); + }); + }); + + describe('project with gas set to "auto"', function () { + usePersistentEnvironment("hardhat-project-with-gas-auto"); + + it("should estimate the gas of the transaction", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + const tx = await signer.sendTransaction({ to: signer }); + + assert.strictEqual(tx.gasLimit, 21_001n); + }); + + it("should use custom gas limit, if provided", async function () { + const signer = await this.env.ethers.provider.getSigner(0); + + const tx = await signer.sendTransaction({ + to: signer, + gasLimit: 30_000, + }); + + assert.strictEqual(tx.gasLimit, 30_000n); + }); + }); +}); diff --git a/packages/hardhat-ethers/test/helpers.ts b/packages/hardhat-ethers/test/helpers.ts index 17642ecd83..779244c08e 100644 --- a/packages/hardhat-ethers/test/helpers.ts +++ b/packages/hardhat-ethers/test/helpers.ts @@ -1,28 +1,29 @@ -import { resetHardhatContext } from "hardhat/plugins-testing"; -import { HardhatRuntimeEnvironment } from "hardhat/types"; -import path from "path"; +import { assert } from "chai"; +import { ContractRunner, Signer } from "ethers"; -// Import this plugin type extensions for the HardhatRuntimeEnvironment -import "../src/internal/type-extensions"; - -declare module "mocha" { - interface Context { - env: HardhatRuntimeEnvironment; +export function assertWithin( + value: number | bigint, + min: number | bigint, + max: number | bigint +) { + if (value < min || value > max) { + assert.fail(`Expected ${value} to be between ${min} and ${max}`); } } -export function useEnvironment( - fixtureProjectName: string, - networkName = "localhost" -) { - beforeEach("Loading hardhat environment", function () { - process.chdir(path.join(__dirname, "fixture-projects", fixtureProjectName)); - process.env.HARDHAT_NETWORK = networkName; - - this.env = require("hardhat"); - }); +export function assertIsNotNull( + value: T +): asserts value is Exclude { + assert.isNotNull(value); +} - afterEach("Resetting hardhat", function () { - resetHardhatContext(); - }); +export function assertIsSigner( + value: ContractRunner | null +): asserts value is Signer { + assertIsNotNull(value); + assert.isTrue("getAddress" in value); + assert.isTrue("signTransaction" in value); } + +export const sleep = (timeout: number) => + new Promise((resolve) => setTimeout(resolve, timeout)); diff --git a/packages/hardhat-ethers/test/index.ts b/packages/hardhat-ethers/test/index.ts index 50bd7757d8..0f417dc746 100644 --- a/packages/hardhat-ethers/test/index.ts +++ b/packages/hardhat-ethers/test/index.ts @@ -1,19 +1,22 @@ +import type { ethers as EthersT } from "ethers"; import chai, { assert } from "chai"; import chaiAsPromised from "chai-as-promised"; -import { ethers, Signer } from "ethers"; +import { ethers } from "ethers"; import { NomicLabsHardhatPluginError } from "hardhat/plugins"; import { Artifact } from "hardhat/types"; -import util from "util"; -import { EthersProviderWrapper } from "../src/internal/ethers-provider-wrapper"; +import { HardhatEthersSigner } from "../src/signers"; -import { useEnvironment } from "./helpers"; +import { useEnvironment } from "./environment"; +import { GreeterContract, TestContractLib } from "./example-contracts"; +import { assertIsNotNull, assertIsSigner } from "./helpers"; chai.use(chaiAsPromised); describe("Ethers plugin", function () { - describe("ganache", function () { - useEnvironment("hardhat-project"); + describe("hardhat node", function () { + useEnvironment("hardhat-project", "localhost"); + describe("HRE extensions", function () { it("should extend hardhat runtime environment", function () { assert.isDefined(this.env.ethers); @@ -26,84 +29,6 @@ describe("Ethers plugin", function () { ...Object.keys(ethers), ]); }); - - describe("Custom formatters", function () { - const assertBigNumberFormat = function ( - BigNumber: any, - value: string | number, - expected: string - ) { - assert.equal(util.format("%o", BigNumber.from(value)), expected); - }; - - describe("BigNumber", function () { - it("should format zero unaltered", function () { - assertBigNumberFormat( - this.env.ethers.BigNumber, - 0, - 'BigNumber { value: "0" }' - ); - }); - - it("should provide human readable versions of positive integers", function () { - const BigNumber = this.env.ethers.BigNumber; - - assertBigNumberFormat(BigNumber, 1, 'BigNumber { value: "1" }'); - assertBigNumberFormat(BigNumber, 999, 'BigNumber { value: "999" }'); - assertBigNumberFormat( - BigNumber, - 1000, - 'BigNumber { value: "1000" }' - ); - assertBigNumberFormat( - BigNumber, - 999999, - 'BigNumber { value: "999999" }' - ); - assertBigNumberFormat( - BigNumber, - 1000000, - 'BigNumber { value: "1000000" }' - ); - assertBigNumberFormat( - BigNumber, - "999999999999999999292", - 'BigNumber { value: "999999999999999999292" }' - ); - }); - - it("should provide human readable versions of negative integers", function () { - const BigNumber = this.env.ethers.BigNumber; - - assertBigNumberFormat(BigNumber, -1, 'BigNumber { value: "-1" }'); - assertBigNumberFormat( - BigNumber, - -999, - 'BigNumber { value: "-999" }' - ); - assertBigNumberFormat( - BigNumber, - -1000, - 'BigNumber { value: "-1000" }' - ); - assertBigNumberFormat( - BigNumber, - -999999, - 'BigNumber { value: "-999999" }' - ); - assertBigNumberFormat( - BigNumber, - -1000000, - 'BigNumber { value: "-1000000" }' - ); - assertBigNumberFormat( - BigNumber, - "-999999999999999999292", - 'BigNumber { value: "-999999999999999999292" }' - ); - }); - }); - }); }); describe("Provider", function () { @@ -112,12 +37,15 @@ describe("Ethers plugin", function () { "eth_accounts", [] ); - assert.equal(accounts[0], "0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1"); + assert.strictEqual( + accounts[0], + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" + ); }); }); describe("Signers and contracts helpers", function () { - let signers: ethers.Signer[]; + let signers: HardhatEthersSigner[]; let greeterArtifact: Artifact; let iGreeterArtifact: Artifact; @@ -132,48 +60,56 @@ describe("Ethers plugin", function () { describe("getSigners", function () { it("should return the signers", async function () { const sigs = await this.env.ethers.getSigners(); - assert.equal( + assert.strictEqual( await sigs[0].getAddress(), - "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1" + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" ); }); it("should expose the address synchronously", async function () { const sigs = await this.env.ethers.getSigners(); - assert.equal( + assert.strictEqual( sigs[0].address, - "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1" + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" ); }); }); describe("getImpersonatedSigner", function () { - it("should invoke hardhat_impersonateAccount", async function () { + it("should return the working impersonated signer", async function () { + const [signer] = await this.env.ethers.getSigners(); const address = `0x${"ff".repeat(20)}`; - // TODO: We are testing this plugin against Ganache, so this fails. - // We should test it using Hardhat Network instead. - await assert.isRejected( - this.env.ethers.getImpersonatedSigner(address), - "Method hardhat_impersonateAccount not supported" + const impersonatedSigner = + await this.env.ethers.getImpersonatedSigner(address); + + assert.strictEqual( + impersonatedSigner.address, + "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF" ); + + // fund impersonated account + await signer.sendTransaction({ + to: impersonatedSigner, + value: 10n ** 18n, + }); + + // send a tx from impersonated account + await impersonatedSigner.sendTransaction({ + to: this.env.ethers.ZeroAddress, + value: 10n ** 17n, + }); }); - it("should return the working impersonated signer", async function () {}); }); describe("signer", function () { - /** - * this test has been skipped pending the removal of ganache from this - * test suite, which is being tracked at - * https://github.com/NomicFoundation/hardhat/issues/3447 - */ - it.skip("should sign a message", async function () { + it("should sign a message", async function () { const [sig] = await this.env.ethers.getSigners(); const result = await sig.signMessage("hello"); - assert.equal( + assert.strictEqual( result, - "0x1845faa75f53acb0c3e7247dcf294ce045c139722418dc9638709b54bafffa093591aeaaa195e7dc53f7e774c80e9a7f1371f0647a100d1c9e81db83d8ddd47801" + "0xf16ea9a3478698f695fd1401bfe27e9e4a7e8e3da94aa72b021125e31fa899cc573c48ea3fe1d4ab61a9db10c19032026e3ed2dbccba5a178235ac27f94504311c" ); }); @@ -181,40 +117,56 @@ describe("Ethers plugin", function () { const [sig] = await this.env.ethers.getSigners(); const Greeter = await this.env.ethers.getContractFactory("Greeter"); - const tx = Greeter.getDeployTransaction(); + const tx = await Greeter.getDeployTransaction(); - assert.throws(() => sig.signTransaction(tx)); + await assert.isRejected(sig.signTransaction(tx)); }); - it("should return the balance of the account", async function () { + // `signer.getBalance` is not present in ethers v6; we should re-enable + // this test when/if it's added back + it.skip("should return the balance of the account", async function () { const [sig] = await this.env.ethers.getSigners(); - assert.equal( + assert.strictEqual( + // @ts-expect-error (await sig.getBalance()).toString(), "100000000000000000000" ); }); + it("should return the balance of the account", async function () { + // we use the second signer because the first one is used in previous tests + const [, secondSigner] = await this.env.ethers.getSigners(); + assert.strictEqual( + await this.env.ethers.provider.getBalance(secondSigner), + 10_000n * 10n ** 18n + ); + }); + it("should return the transaction count of the account", async function () { - const [sig] = await this.env.ethers.getSigners(); - assert.equal((await sig.getTransactionCount()).toString(), "0"); + // we use the second signer because the first one is used in previous tests + const [, secondSigner] = await this.env.ethers.getSigners(); + assert.strictEqual( + await this.env.ethers.provider.getTransactionCount(secondSigner), + 0 + ); }); it("should allow to use the estimateGas method", async function () { const [sig] = await this.env.ethers.getSigners(); const Greeter = await this.env.ethers.getContractFactory("Greeter"); - const tx = Greeter.getDeployTransaction(); + const tx = await Greeter.getDeployTransaction(); const result = await sig.estimateGas(tx); - assert.isTrue(result.gt(0)); + assert.isTrue(result > 0n); }); it("should allow to use the call method", async function () { const [sig] = await this.env.ethers.getSigners(); const Greeter = await this.env.ethers.getContractFactory("Greeter"); - const tx = Greeter.getDeployTransaction(); + const tx = await Greeter.getDeployTransaction(); const result = await sig.call(tx); @@ -225,46 +177,41 @@ describe("Ethers plugin", function () { const [sig] = await this.env.ethers.getSigners(); const Greeter = await this.env.ethers.getContractFactory("Greeter"); - const tx = Greeter.getDeployTransaction(); + const tx = await Greeter.getDeployTransaction(); const response = await sig.sendTransaction(tx); const receipt = await response.wait(); - assert.equal(receipt.status, 1); + if (receipt === null) { + assert.fail("receipt shoudn't be null"); + } + assert.strictEqual(receipt.status, 1); }); it("should get the chainId", async function () { - const [sig] = await this.env.ethers.getSigners(); - - const chainId = await sig.getChainId(); + const { chainId } = await this.env.ethers.provider.getNetwork(); - assert.equal(chainId, 1337); + assert.strictEqual(chainId, 31337n); }); it("should get the gas price", async function () { - const [sig] = await this.env.ethers.getSigners(); - - const gasPrice = await sig.getGasPrice(); + const feeData: EthersT.FeeData = + await this.env.ethers.provider.getFeeData(); - assert.equal(gasPrice.toString(), "20000000000"); + assertIsNotNull(feeData.gasPrice); + assert.isTrue(feeData.gasPrice > 0); }); - it("should check and populate a transaction", async function () { + it("should populate a transaction", async function () { const [sig] = await this.env.ethers.getSigners(); const Greeter = await this.env.ethers.getContractFactory("Greeter"); - const tx = Greeter.getDeployTransaction(); + const tx = await Greeter.getDeployTransaction(); - const checkedTransaction = sig.checkTransaction(tx); + const populatedTransaction = await sig.populateTransaction(tx); - assert.equal(await checkedTransaction.from, sig.address); - - const populatedTransaction = await sig.populateTransaction( - checkedTransaction - ); - - assert.equal(populatedTransaction.from, sig.address); + assert.strictEqual(populatedTransaction.from, sig.address); }); }); @@ -276,13 +223,15 @@ describe("Ethers plugin", function () { "Greeter" ); - assert.containsAllKeys(contract.interface.functions, [ - "setGreeting(string)", - "greet()", - ]); + assert.isNotNull(contract.interface.getFunction("greet")); + assert.isNotNull(contract.interface.getFunction("setGreeting")); + + // non-existent functions should be null + assert.isNull(contract.interface.getFunction("doesntExist")); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[0].getAddress() ); }); @@ -315,19 +264,22 @@ describe("Ethers plugin", function () { ); const library = await libraryFactory.deploy(); - const contractFactory = await this.env.ethers.getContractFactory( - "TestContractLib", - { libraries: { TestLibrary: library.address } } - ); - assert.equal( - await contractFactory.signer.getAddress(), + const contractFactory = await this.env.ethers.getContractFactory< + [], + TestContractLib + >("TestContractLib", { + libraries: { TestLibrary: library.target }, + }); + assertIsSigner(contractFactory.runner); + assert.strictEqual( + await contractFactory.runner.getAddress(), await signers[0].getAddress() ); const numberPrinter = await contractFactory.deploy(); - const someNumber = 50; - assert.equal( - await numberPrinter.callStatic.printNumber(someNumber), - someNumber * 2 + const someNumber = 50n; + assert.strictEqual( + await numberPrinter.printNumber.staticCall(someNumber), + someNumber * 2n ); }); @@ -340,8 +292,9 @@ describe("Ethers plugin", function () { try { await this.env.ethers.getContractFactory("TestContractLib", { libraries: { - TestLibrary: library.address, - "contracts/TestContractLib.sol:TestLibrary": library.address, + TestLibrary: await library.getAddress(), + "contracts/TestContractLib.sol:TestLibrary": + await library.getAddress(), }, }); } catch (reason: any) { @@ -379,10 +332,11 @@ describe("Ethers plugin", function () { const contractFactory = await this.env.ethers.getContractFactory( "TestNonUniqueLib", - { libraries: { NonUniqueLibrary: library.address } } + { libraries: { NonUniqueLibrary: await library.getAddress() } } ); - assert.equal( - await contractFactory.signer.getAddress(), + assertIsSigner(contractFactory.runner); + assert.strictEqual( + await contractFactory.runner.getAddress(), await signers[0].getAddress() ); }); @@ -400,9 +354,9 @@ describe("Ethers plugin", function () { try { await this.env.ethers.getContractFactory("TestAmbiguousLib", { libraries: { - AmbiguousLibrary: library.address, + AmbiguousLibrary: await library.getAddress(), "contracts/AmbiguousLibrary2.sol:AmbiguousLibrary": - library2.address, + await library2.getAddress(), }, }); } catch (reason: any) { @@ -490,66 +444,43 @@ describe("Ethers plugin", function () { ); }); - it("should fail to create a contract factory when incorrectly linking a library with an ethers.Contract", async function () { + it("should contract instances as libraries", async function () { const libraryFactory = await this.env.ethers.getContractFactory( "TestLibrary" ); const library = await libraryFactory.deploy(); - try { - await this.env.ethers.getContractFactory("TestContractLib", { - libraries: { TestLibrary: library as any }, - }); - } catch (reason: any) { - assert.instanceOf( - reason, - NomicLabsHardhatPluginError, - "getContractFactory should fail with a hardhat plugin error" - ); - assert.isTrue( - reason.message.includes( - "invalid address", - "getContractFactory should report the invalid address as the cause" - ) - ); - // This assert is here just to make sure we don't end up printing an enormous object - // in the error message. This may happen if the argument received is particularly complex. - assert.isTrue( - reason.message.length <= 400, - "getContractFactory should fail with an error message that isn't too large" - ); - return; - } - - assert.fail( - "getContractFactory should fail to create a contract factory if there is an invalid address" - ); + await this.env.ethers.getContractFactory("TestContractLib", { + libraries: { TestLibrary: library }, + }); }); it("Should be able to send txs and make calls", async function () { - const Greeter = await this.env.ethers.getContractFactory("Greeter"); + const Greeter = await this.env.ethers.getContractFactory< + [], + GreeterContract + >("Greeter"); const greeter = await Greeter.deploy(); - assert.equal(await greeter.functions.greet(), "Hi"); - await greeter.functions.setGreeting("Hola"); - assert.equal(await greeter.functions.greet(), "Hola"); + assert.strictEqual(await greeter.greet(), "Hi"); + await greeter.setGreeting("Hola"); + assert.strictEqual(await greeter.greet(), "Hola"); }); - describe("with custom signer", function () { - it("should return a contract factory connected to the custom signer", async function () { + describe("with hardhat's signer", function () { + it("should return a contract factory connected to the hardhat's signer", async function () { // It's already compiled in artifacts/ const contract = await this.env.ethers.getContractFactory( "Greeter", signers[1] ); - assert.containsAllKeys(contract.interface.functions, [ - "setGreeting(string)", - "greet()", - ]); + assert.isNotNull(contract.interface.getFunction("greet")); + assert.isNotNull(contract.interface.getFunction("setGreeting")); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[1].getAddress() ); }); @@ -564,13 +495,12 @@ describe("Ethers plugin", function () { greeterArtifact.bytecode ); - assert.containsAllKeys(contract.interface.functions, [ - "setGreeting(string)", - "greet()", - ]); + assert.isNotNull(contract.interface.getFunction("greet")); + assert.isNotNull(contract.interface.getFunction("setGreeting")); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[0].getAddress() ); }); @@ -580,29 +510,30 @@ describe("Ethers plugin", function () { iGreeterArtifact.abi, iGreeterArtifact.bytecode ); - assert.equal(contract.bytecode, "0x"); - assert.containsAllKeys(contract.interface.functions, ["greet()"]); + assert.strictEqual(contract.bytecode, "0x"); + assert.isNotNull(contract.interface.getFunction("greet")); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[0].getAddress() ); }); it("Should be able to send txs and make calls", async function () { - const Greeter = await this.env.ethers.getContractFactory( - greeterArtifact.abi, - greeterArtifact.bytecode - ); + const Greeter = await this.env.ethers.getContractFactory< + [], + GreeterContract + >(greeterArtifact.abi, greeterArtifact.bytecode); const greeter = await Greeter.deploy(); - assert.equal(await greeter.functions.greet(), "Hi"); - await greeter.functions.setGreeting("Hola"); - assert.equal(await greeter.functions.greet(), "Hola"); + assert.strictEqual(await greeter.greet(), "Hi"); + await greeter.setGreeting("Hola"); + assert.strictEqual(await greeter.greet(), "Hola"); }); - describe("with custom signer", function () { - it("should return a contract factory connected to the custom signer", async function () { + describe("with hardhat's signer", function () { + it("should return a contract factory connected to the hardhat's signer", async function () { // It's already compiled in artifacts/ const contract = await this.env.ethers.getContractFactory( greeterArtifact.abi, @@ -610,13 +541,12 @@ describe("Ethers plugin", function () { signers[1] ); - assert.containsAllKeys(contract.interface.functions, [ - "setGreeting(string)", - "greet()", - ]); + assert.isNotNull(contract.interface.getFunction("greet")); + assert.isNotNull(contract.interface.getFunction("setGreeting")); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[1].getAddress() ); }); @@ -630,13 +560,12 @@ describe("Ethers plugin", function () { greeterArtifact ); - assert.containsAllKeys(contract.interface.functions, [ - "setGreeting(string)", - "greet()", - ]); + assert.isNotNull(contract.interface.getFunction("greet")); + assert.isNotNull(contract.interface.getFunction("setGreeting")); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[0].getAddress() ); }); @@ -652,49 +581,53 @@ describe("Ethers plugin", function () { ); const contractFactory = - await this.env.ethers.getContractFactoryFromArtifact( - testContractLibArtifact, - { libraries: { TestLibrary: library.address } } - ); + await this.env.ethers.getContractFactoryFromArtifact< + [], + TestContractLib + >(testContractLibArtifact, { + libraries: { TestLibrary: await library.getAddress() }, + }); + assertIsSigner(contractFactory.runner); - assert.equal( - await contractFactory.signer.getAddress(), + assert.strictEqual( + await contractFactory.runner.getAddress(), await signers[0].getAddress() ); + const numberPrinter = await contractFactory.deploy(); - const someNumber = 50; - assert.equal( - await numberPrinter.callStatic.printNumber(someNumber), - someNumber * 2 + const someNumber = 50n; + assert.strictEqual( + await numberPrinter.printNumber.staticCall(someNumber), + someNumber * 2n ); }); it("Should be able to send txs and make calls", async function () { - const Greeter = await this.env.ethers.getContractFactoryFromArtifact( - greeterArtifact - ); + const Greeter = await this.env.ethers.getContractFactoryFromArtifact< + [], + GreeterContract + >(greeterArtifact); const greeter = await Greeter.deploy(); - assert.equal(await greeter.functions.greet(), "Hi"); - await greeter.functions.setGreeting("Hola"); - assert.equal(await greeter.functions.greet(), "Hola"); + assert.strictEqual(await greeter.greet(), "Hi"); + await greeter.setGreeting("Hola"); + assert.strictEqual(await greeter.greet(), "Hola"); }); - describe("with custom signer", function () { - it("should return a contract factory connected to the custom signer", async function () { + describe("with hardhat's signer", function () { + it("should return a contract factory connected to the hardhat's signer", async function () { const contract = await this.env.ethers.getContractFactoryFromArtifact( greeterArtifact, signers[1] ); - assert.containsAllKeys(contract.interface.functions, [ - "setGreeting(string)", - "greet()", - ]); + assert.isNotNull(contract.interface.getFunction("greet")); + assert.isNotNull(contract.interface.getFunction("setGreeting")); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[1].getAddress() ); }); @@ -702,10 +635,13 @@ describe("Ethers plugin", function () { }); describe("getContractAt", function () { - let deployedGreeter: ethers.Contract; + let deployedGreeter: GreeterContract; beforeEach(async function () { - const Greeter = await this.env.ethers.getContractFactory("Greeter"); + const Greeter = await this.env.ethers.getContractFactory< + [], + GreeterContract + >("Greeter"); deployedGreeter = await Greeter.deploy(); }); @@ -719,16 +655,15 @@ describe("Ethers plugin", function () { it("Should return an instance of a contract", async function () { const contract = await this.env.ethers.getContractAt( "Greeter", - deployedGreeter.address + deployedGreeter.target ); - assert.containsAllKeys(contract.functions, [ - "setGreeting(string)", - "greet()", - ]); + assert.exists(contract.setGreeting); + assert.exists(contract.greet); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[0].getAddress() ); }); @@ -736,13 +671,14 @@ describe("Ethers plugin", function () { it("Should return an instance of an interface", async function () { const contract = await this.env.ethers.getContractAt( "IGreeter", - deployedGreeter.address + deployedGreeter.target ); - assert.containsAllKeys(contract.functions, ["greet()"]); + assert.isNotNull(contract.interface.getFunction("greet")); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[0].getAddress() ); }); @@ -750,24 +686,24 @@ describe("Ethers plugin", function () { it("Should be able to send txs and make calls", async function () { const greeter = await this.env.ethers.getContractAt( "Greeter", - deployedGreeter.address + deployedGreeter.target ); - assert.equal(await greeter.functions.greet(), "Hi"); - await greeter.functions.setGreeting("Hola"); - assert.equal(await greeter.functions.greet(), "Hola"); + assert.strictEqual(await greeter.greet(), "Hi"); + await greeter.setGreeting("Hola"); + assert.strictEqual(await greeter.greet(), "Hola"); }); - describe("with custom signer", function () { - it("Should return an instance of a contract associated to a custom signer", async function () { + describe("with hardhat's signer", function () { + it("Should return an instance of a contract associated to a hardhat's signer", async function () { const contract = await this.env.ethers.getContractAt( "Greeter", - deployedGreeter.address, + deployedGreeter.target, signers[1] ); - - assert.equal( - await contract.signer.getAddress(), + assertIsSigner(contract.runner); + assert.strictEqual( + await contract.runner.getAddress(), await signers[1].getAddress() ); }); @@ -778,16 +714,15 @@ describe("Ethers plugin", function () { it("Should return an instance of a contract", async function () { const contract = await this.env.ethers.getContractAt( greeterArtifact.abi, - deployedGreeter.address + deployedGreeter.target ); - assert.containsAllKeys(contract.functions, [ - "setGreeting(string)", - "greet()", - ]); + assert.isNotNull(contract.interface.getFunction("greet")); + assert.isNotNull(contract.interface.getFunction("setGreeting")); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[0].getAddress() ); }); @@ -795,13 +730,14 @@ describe("Ethers plugin", function () { it("Should return an instance of an interface", async function () { const contract = await this.env.ethers.getContractAt( iGreeterArtifact.abi, - deployedGreeter.address + deployedGreeter.target ); - assert.containsAllKeys(contract.functions, ["greet()"]); + assert.isNotNull(contract.interface.getFunction("greet")); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[0].getAddress() ); }); @@ -809,52 +745,52 @@ describe("Ethers plugin", function () { it("Should be able to send txs and make calls", async function () { const greeter = await this.env.ethers.getContractAt( greeterArtifact.abi, - deployedGreeter.address + deployedGreeter.target ); - assert.equal(await greeter.functions.greet(), "Hi"); - await greeter.functions.setGreeting("Hola"); - assert.equal(await greeter.functions.greet(), "Hola"); + assert.strictEqual(await greeter.greet(), "Hi"); + await greeter.setGreeting("Hola"); + assert.strictEqual(await greeter.greet(), "Hola"); }); - it("Should be able to detect events", async function () { - const greeter = await this.env.ethers.getContractAt( - greeterArtifact.abi, - deployedGreeter.address - ); - - // at the time of this writing, ethers' default polling interval is - // 4000 ms. here we turn it down in order to speed up this test. - // see also - // https://github.com/ethers-io/ethers.js/issues/615#issuecomment-848991047 - const provider = greeter.provider as EthersProviderWrapper; - provider.pollingInterval = 100; - - let eventEmitted = false; - greeter.on("GreetingUpdated", () => { - eventEmitted = true; - }); - - await greeter.functions.setGreeting("Hola"); - - // wait for 1.5 polling intervals for the event to fire - await new Promise((resolve) => - setTimeout(resolve, provider.pollingInterval * 2) - ); - - assert.equal(eventEmitted, true); - }); - - describe("with custom signer", function () { - it("Should return an instance of a contract associated to a custom signer", async function () { + // TODO re-enable when we make .on("event") work + // it("Should be able to detect events", async function () { + // const greeter = await this.env.ethers.getContractAt( + // greeterArtifact.abi, + // deployedGreeter.target + // ); + // + // // at the time of this writing, ethers' default polling interval is + // // 4000 ms. here we turn it down in order to speed up this test. + // // see also + // // https://github.com/ethers-io/ethers.js/issues/615#issuecomment-848991047 + // // const provider = greeter.provider as any; + // // provider.pollingInterval = 100; + // + // let eventEmitted = false; + // await greeter.on("GreetingUpdated", () => { + // eventEmitted = true; + // }); + // + // await greeter.setGreeting("Hola"); + // + // // wait for 1.5 polling intervals for the event to fire + // await new Promise((resolve) => setTimeout(resolve, 10_000)); + // + // assert.strictEqual(eventEmitted, true); + // }); + + describe("with hardhat's signer", function () { + it("Should return an instance of a contract associated to a hardhat's signer", async function () { const contract = await this.env.ethers.getContractAt( greeterArtifact.abi, - deployedGreeter.address, + deployedGreeter.target, signers[1] ); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[1].getAddress() ); }); @@ -866,31 +802,36 @@ describe("Ethers plugin", function () { ); const library = await libraryFactory.deploy(); - const contractFactory = await this.env.ethers.getContractFactory( - "TestContractLib", - { libraries: { TestLibrary: library.address } } - ); + const contractFactory = await this.env.ethers.getContractFactory< + [], + TestContractLib + >("TestContractLib", { + libraries: { TestLibrary: library.target }, + }); const numberPrinter = await contractFactory.deploy(); const numberPrinterAtAddress = await this.env.ethers.getContractAt( "TestContractLib", - numberPrinter.address + numberPrinter.target ); - const someNumber = 50; - assert.equal( - await numberPrinterAtAddress.callStatic.printNumber(someNumber), - someNumber * 2 + const someNumber = 50n; + assert.strictEqual( + await numberPrinterAtAddress.printNumber.staticCall(someNumber), + someNumber * 2n ); }); }); }); describe("getContractAtFromArtifact", function () { - let deployedGreeter: ethers.Contract; + let deployedGreeter: GreeterContract; beforeEach(async function () { - const Greeter = await this.env.ethers.getContractFactory("Greeter"); + const Greeter = await this.env.ethers.getContractFactory< + [], + GreeterContract + >("Greeter"); deployedGreeter = await Greeter.deploy(); }); @@ -898,16 +839,15 @@ describe("Ethers plugin", function () { it("Should return an instance of a contract", async function () { const contract = await this.env.ethers.getContractAtFromArtifact( greeterArtifact, - deployedGreeter.address + await deployedGreeter.getAddress() ); - assert.containsAllKeys(contract.functions, [ - "setGreeting(string)", - "greet()", - ]); + assert.isNotNull(contract.interface.getFunction("greet")); + assert.isNotNull(contract.interface.getFunction("setGreeting")); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[0].getAddress() ); }); @@ -915,24 +855,25 @@ describe("Ethers plugin", function () { it("Should be able to send txs and make calls", async function () { const greeter = await this.env.ethers.getContractAtFromArtifact( greeterArtifact, - deployedGreeter.address + await deployedGreeter.getAddress() ); - assert.equal(await greeter.functions.greet(), "Hi"); - await greeter.functions.setGreeting("Hola"); - assert.equal(await greeter.functions.greet(), "Hola"); + assert.strictEqual(await greeter.greet(), "Hi"); + await greeter.setGreeting("Hola"); + assert.strictEqual(await greeter.greet(), "Hola"); }); - describe("with custom signer", function () { - it("Should return an instance of a contract associated to a custom signer", async function () { + describe("with hardhat's signer", function () { + it("Should return an instance of a contract associated to a hardhat's signer", async function () { const contract = await this.env.ethers.getContractAtFromArtifact( greeterArtifact, - deployedGreeter.address, + await deployedGreeter.getAddress(), signers[1] ); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signers[1].getAddress() ); }); @@ -947,7 +888,7 @@ describe("Ethers plugin", function () { await assertContract(contract, signers[0]); }); - it("should deploy and return a contract with custom signer passed directly", async function () { + it("should deploy and return a contract with hardhat's signer passed directly", async function () { const contract = await this.env.ethers.deployContract( "Greeter", signers[1] @@ -956,7 +897,7 @@ describe("Ethers plugin", function () { await assertContract(contract, signers[1]); }); - it("should deploy and return a contract with custom signer passed as an option", async function () { + it("should deploy and return a contract with hardhat's signer passed as an option", async function () { const contract = await this.env.ethers.deployContract("Greeter", { signer: signers[1], }); @@ -974,7 +915,7 @@ describe("Ethers plugin", function () { assert(await contract.greet(), "Hello"); }); - it("should deploy with args and return a contract with custom signer", async function () { + it("should deploy with args and return a contract with hardhat's signer", async function () { const contract = await this.env.ethers.deployContract( "GreeterWithConstructorArg", ["Hello"], @@ -985,7 +926,7 @@ describe("Ethers plugin", function () { assert(await contract.greet(), "Hello"); }); - it("should deploy with args and return a contract with custom signer as an option", async function () { + it("should deploy with args and return a contract with hardhat's signer as an option", async function () { const contract = await this.env.ethers.deployContract( "GreeterWithConstructorArg", ["Hello"], @@ -996,17 +937,50 @@ describe("Ethers plugin", function () { assert(await contract.greet(), "Hello"); }); + it("should accept overrides for the deployment transaction", async function () { + const contract = await this.env.ethers.deployContract("Greeter", { + gasLimit: 1_000_000, + }); + + await assertContract(contract, signers[0]); + + const deploymentTx = contract.deploymentTransaction(); + if (deploymentTx === null) { + assert.fail("Deployment transaction shouldn't be null"); + } + + assert.equal(deploymentTx.gasLimit, 1_000_000n); + }); + + it("should accept overrides for the deployment transaction when there are constructor args", async function () { + const contract = await this.env.ethers.deployContract( + "GreeterWithConstructorArg", + ["Hello"], + { + gasLimit: 1_000_000, + } + ); + + await assertContract(contract, signers[0]); + + const deploymentTx = contract.deploymentTransaction(); + if (deploymentTx === null) { + assert.fail("Deployment transaction shouldn't be null"); + } + + assert.equal(deploymentTx.gasLimit, 1_000_000n); + }); + async function assertContract( - contract: ethers.Contract, - signer: Signer + contract: EthersT.Contract, + signer: HardhatEthersSigner ) { - assert.containsAllKeys(contract.interface.functions, [ - "setGreeting(string)", - "greet()", - ]); + assert.isNotNull(contract.interface.getFunction("greet")); + assert.isNotNull(contract.interface.getFunction("setGreeting")); + assertIsSigner(contract.runner); - assert.equal( - await contract.signer.getAddress(), + assert.strictEqual( + await contract.runner.getAddress(), await signer.getAddress() ); } @@ -1018,46 +992,45 @@ describe("Ethers plugin", function () { useEnvironment("hardhat-project", "hardhat"); describe("contract events", function () { - it("should be detected", async function () { - const Greeter = await this.env.ethers.getContractFactory("Greeter"); - const deployedGreeter: ethers.Contract = await Greeter.deploy(); - - // at the time of this writing, ethers' default polling interval is - // 4000 ms. here we turn it down in order to speed up this test. - // see also - // https://github.com/ethers-io/ethers.js/issues/615#issuecomment-848991047 - const provider = deployedGreeter.provider as EthersProviderWrapper; - provider.pollingInterval = 200; - - let eventEmitted = false; - deployedGreeter.on("GreetingUpdated", () => { - eventEmitted = true; - }); - - await deployedGreeter.functions.setGreeting("Hola"); - - // wait for 1.5 polling intervals for the event to fire - await new Promise((resolve) => - setTimeout(resolve, provider.pollingInterval * 2) - ); - - assert.equal(eventEmitted, true); - }); + // TODO re-enable when we make .on("event") work + // it("should be detected", async function () { + // const Greeter = await this.env.ethers.getContractFactory("Greeter"); + // const deployedGreeter: any = await Greeter.deploy(); + // + // // at the time of this writing, ethers' default polling interval is + // // 4000 ms. here we turn it down in order to speed up this test. + // // see also + // // https://github.com/ethers-io/ethers.js/issues/615#issuecomment-848991047 + // // const provider = deployedGreeter.provider as EthersProviderWrapper; + // // provider.pollingInterval = 200; + // + // let eventEmitted = false; + // deployedGreeter.on("GreetingUpdated", () => { + // eventEmitted = true; + // }); + // + // await deployedGreeter.setGreeting("Hola"); + // + // // wait for 1.5 polling intervals for the event to fire + // await new Promise((resolve) => setTimeout(resolve, 200 * 2)); + // + // assert.strictEqual(eventEmitted, true); + // }); }); describe("hardhat_reset", function () { it("should return the correct block number after a hardhat_reset", async function () { let blockNumber = await this.env.ethers.provider.getBlockNumber(); - assert.equal(blockNumber.toString(), "0"); + assert.strictEqual(blockNumber.toString(), "0"); await this.env.ethers.provider.send("evm_mine", []); await this.env.ethers.provider.send("evm_mine", []); blockNumber = await this.env.ethers.provider.getBlockNumber(); - assert.equal(blockNumber.toString(), "2"); + assert.strictEqual(blockNumber.toString(), "2"); await this.env.ethers.provider.send("hardhat_reset", []); blockNumber = await this.env.ethers.provider.getBlockNumber(); - assert.equal(blockNumber.toString(), "0"); + assert.strictEqual(blockNumber.toString(), "0"); }); it("should return the correct block after a hardhat_reset", async function () { @@ -1083,21 +1056,21 @@ describe("Ethers plugin", function () { sig.address ); - assert.equal(nonce, 0); + assert.strictEqual(nonce, 0); const response = await sig.sendTransaction({ from: sig.address, - to: this.env.ethers.constants.AddressZero, + to: this.env.ethers.ZeroAddress, value: "0x1", }); await response.wait(); nonce = await this.env.ethers.provider.getTransactionCount(sig.address); - assert.equal(nonce, 1); + assert.strictEqual(nonce, 1); await this.env.ethers.provider.send("hardhat_reset", []); nonce = await this.env.ethers.provider.getTransactionCount(sig.address); - assert.equal(nonce, 0); + assert.strictEqual(nonce, 0); }); it("should return the correct balance after a hardhat_reset", async function () { @@ -1105,32 +1078,38 @@ describe("Ethers plugin", function () { let balance = await this.env.ethers.provider.getBalance(sig.address); - assert.equal(balance.toString(), "10000000000000000000000"); + assert.strictEqual(balance.toString(), "10000000000000000000000"); const response = await sig.sendTransaction({ from: sig.address, - to: this.env.ethers.constants.AddressZero, + to: this.env.ethers.ZeroAddress, gasPrice: 8e9, }); await response.wait(); balance = await this.env.ethers.provider.getBalance(sig.address); - assert.equal(balance.toString(), "9999999832000000000000"); + assert.strictEqual(balance.toString(), "9999999832000000000000"); await this.env.ethers.provider.send("hardhat_reset", []); balance = await this.env.ethers.provider.getBalance(sig.address); - assert.equal(balance.toString(), "10000000000000000000000"); + assert.strictEqual(balance.toString(), "10000000000000000000000"); }); it("should return the correct code after a hardhat_reset", async function () { const [sig] = await this.env.ethers.getSigners(); const Greeter = await this.env.ethers.getContractFactory("Greeter"); - const tx = Greeter.getDeployTransaction(); + const tx = await Greeter.getDeployTransaction(); const response = await sig.sendTransaction(tx); const receipt = await response.wait(); + if (receipt === null) { + assert.fail("receipt shoudn't be null"); + } + if (receipt.contractAddress === null) { + assert.fail("receipt.contractAddress shoudn't be null"); + } let code = await this.env.ethers.provider.getCode( receipt.contractAddress @@ -1151,16 +1130,16 @@ describe("Ethers plugin", function () { [] ); let blockNumber = await this.env.ethers.provider.getBlockNumber(); - assert.equal(blockNumber.toString(), "0"); + assert.strictEqual(blockNumber.toString(), "0"); await this.env.ethers.provider.send("evm_mine", []); await this.env.ethers.provider.send("evm_mine", []); blockNumber = await this.env.ethers.provider.getBlockNumber(); - assert.equal(blockNumber.toString(), "2"); + assert.strictEqual(blockNumber.toString(), "2"); await this.env.ethers.provider.send("evm_revert", [snapshotId]); blockNumber = await this.env.ethers.provider.getBlockNumber(); - assert.equal(blockNumber.toString(), "0"); + assert.strictEqual(blockNumber.toString(), "0"); }); it("should return the correct block after a evm_revert", async function () { @@ -1194,21 +1173,21 @@ describe("Ethers plugin", function () { sig.address ); - assert.equal(nonce, 0); + assert.strictEqual(nonce, 0); const response = await sig.sendTransaction({ from: sig.address, - to: this.env.ethers.constants.AddressZero, + to: this.env.ethers.ZeroAddress, value: "0x1", }); await response.wait(); nonce = await this.env.ethers.provider.getTransactionCount(sig.address); - assert.equal(nonce, 1); + assert.strictEqual(nonce, 1); await this.env.ethers.provider.send("evm_revert", [snapshotId]); nonce = await this.env.ethers.provider.getTransactionCount(sig.address); - assert.equal(nonce, 0); + assert.strictEqual(nonce, 0); }); it("should return the correct balance after a evm_revert", async function () { @@ -1220,21 +1199,21 @@ describe("Ethers plugin", function () { let balance = await this.env.ethers.provider.getBalance(sig.address); - assert.equal(balance.toString(), "10000000000000000000000"); + assert.strictEqual(balance.toString(), "10000000000000000000000"); const response = await sig.sendTransaction({ from: sig.address, - to: this.env.ethers.constants.AddressZero, + to: this.env.ethers.ZeroAddress, gasPrice: 8e9, }); await response.wait(); balance = await this.env.ethers.provider.getBalance(sig.address); - assert.equal(balance.toString(), "9999999832000000000000"); + assert.strictEqual(balance.toString(), "9999999832000000000000"); await this.env.ethers.provider.send("evm_revert", [snapshotId]); balance = await this.env.ethers.provider.getBalance(sig.address); - assert.equal(balance.toString(), "10000000000000000000000"); + assert.strictEqual(balance.toString(), "10000000000000000000000"); }); it("should return the correct code after a evm_revert", async function () { @@ -1245,12 +1224,19 @@ describe("Ethers plugin", function () { const [sig] = await this.env.ethers.getSigners(); const Greeter = await this.env.ethers.getContractFactory("Greeter"); - const tx = Greeter.getDeployTransaction(); + const tx = await Greeter.getDeployTransaction(); const response = await sig.sendTransaction(tx); const receipt = await response.wait(); + if (receipt === null) { + assert.fail("receipt shoudn't be null"); + } + if (receipt.contractAddress === null) { + assert.fail("receipt.contractAddress shoudn't be null"); + } + let code = await this.env.ethers.provider.getCode( receipt.contractAddress ); @@ -1263,7 +1249,7 @@ describe("Ethers plugin", function () { }); }); - it("_signTypedData integration test", async function () { + it("signTypedData integration test", async function () { // See https://eips.ethereum.org/EIPS/eip-712#parameters // There's a json schema and an explanation for each field. const typedMessage = { @@ -1304,7 +1290,7 @@ describe("Ethers plugin", function () { }; const [signer] = await this.env.ethers.getSigners(); - const signature = await signer._signTypedData( + const signature = await signer.signTypedData( typedMessage.domain, typedMessage.types, typedMessage.message @@ -1316,28 +1302,30 @@ describe("Ethers plugin", function () { assert.lengthOf(signature, signatureSizeInBytes * byteToHex + hexPrefix); }); }); - describe("ganache via WebSocket", function () { - useEnvironment("hardhat-project"); - it("should be able to detect events", async function () { - await this.env.run("compile", { quiet: true }); - - const Greeter = await this.env.ethers.getContractFactory("Greeter"); - const deployedGreeter: ethers.Contract = await Greeter.deploy(); - - const readonlyContract = deployedGreeter.connect( - new ethers.providers.WebSocketProvider("ws://127.0.0.1:8545") - ); - let emitted = false; - readonlyContract.on("GreetingUpdated", () => { - emitted = true; - }); - - await deployedGreeter.functions.setGreeting("Hola"); - // wait for the event to fire - await new Promise((resolve) => setTimeout(resolve, 100)); - - assert.equal(emitted, true); - }); + describe("hardhat node via WebSocket", function () { + useEnvironment("hardhat-project", "localhost"); + // TODO re-enable when we make .on("event") work + // it("should be able to detect events", async function () { + // await this.env.run("compile", { quiet: true }); + // + // const Greeter = await this.env.ethers.getContractFactory("Greeter"); + // const deployedGreeter: any = await Greeter.deploy(); + // + // const readonlyContract = deployedGreeter.connect( + // new ethers.WebSocketProvider("ws://127.0.0.1:8545") + // ); + // let emitted = false; + // await readonlyContract.on("GreetingUpdated", () => { + // emitted = true; + // }); + // + // await deployedGreeter.setGreeting("Hola"); + // + // // wait for the event to fire + // await new Promise((resolve) => setTimeout(resolve, 100)); + // + // assert.strictEqual(emitted, true); + // }); }); }); diff --git a/packages/hardhat-ethers/test/no-accounts.ts b/packages/hardhat-ethers/test/no-accounts.ts index b068088a98..4a7f8657a3 100644 --- a/packages/hardhat-ethers/test/no-accounts.ts +++ b/packages/hardhat-ethers/test/no-accounts.ts @@ -2,9 +2,9 @@ import { assert } from "chai"; import { TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { SignerWithAddress } from "../src/signers"; +import { HardhatEthersSigner } from "../src/signers"; -import { useEnvironment } from "./helpers"; +import { useEnvironment } from "./environment"; describe("hardhat-ethers plugin", function () { describe("hardhat network with no accounts", function () { @@ -33,23 +33,36 @@ describe("hardhat-ethers plugin", function () { it("Should return an instance of a contract with a read-only provider", async function () { const receipt = await deployGreeter(this.env, signerAddress); + if (receipt === null) { + assert.fail("receipt shoudn't be null"); + } + if (receipt.contractAddress === null) { + assert.fail("receipt.contractAddress shoudn't be null"); + } + const contract = await this.env.ethers.getContractAt( "Greeter", receipt.contractAddress ); - assert.isDefined(contract.provider); - assert.isNotNull(contract.provider); + assert.isDefined(contract.runner); + assert.isNotNull(contract.runner); - const greeting = await contract.functions.greet(); + const greeting = await contract.greet(); - assert.equal(greeting, "Hi"); + assert.strictEqual(greeting, "Hi"); }); }); describe("with the abi and address", function () { it("Should return an instance of a contract with a read-only provider", async function () { const receipt = await deployGreeter(this.env, signerAddress); + if (receipt === null) { + assert.fail("receipt shoudn't be null"); + } + if (receipt.contractAddress === null) { + assert.fail("receipt.contractAddress shoudn't be null"); + } const signers = await this.env.ethers.getSigners(); assert.isEmpty(signers); @@ -63,12 +76,12 @@ describe("hardhat-ethers plugin", function () { receipt.contractAddress ); - assert.isDefined(contract.provider); - assert.isNotNull(contract.provider); + assert.isDefined(contract.runner); + assert.isNotNull(contract.runner); - const greeting = await contract.functions.greet(); + const greeting = await contract.greet(); - assert.equal(greeting, "Hi"); + assert.strictEqual(greeting, "Hi"); }); }); }); @@ -81,8 +94,8 @@ describe("hardhat-ethers plugin", function () { assert.isTrue(signers.every((aSigner) => aSigner.address !== address)); const signer = await this.env.ethers.getSigner(address); - assert.instanceOf(signer, SignerWithAddress); - assert.equal(signer.address, address); + assert.instanceOf(signer, HardhatEthersSigner); + assert.strictEqual(signer.address, address); }); }); }); @@ -93,7 +106,7 @@ async function deployGreeter( signerAddress: string ) { const Greeter = await hre.ethers.getContractFactory("Greeter"); - const tx = Greeter.getDeployTransaction(); + const tx = await Greeter.getDeployTransaction(); tx.from = signerAddress; await hre.network.provider.request({ @@ -111,7 +124,10 @@ async function deployGreeter( }); assert.isDefined(hre.ethers.provider); const receipt = await hre.ethers.provider.getTransactionReceipt(txHash); - assert.equal(receipt.status, 1, "The deployment transaction failed."); + if (receipt === null) { + assert.fail("receipt shoudn't be null"); + } + assert.strictEqual(receipt.status, 1, "The deployment transaction failed."); return receipt; } diff --git a/packages/hardhat-ethers/test/type-tests.ts b/packages/hardhat-ethers/test/type-tests.ts new file mode 100644 index 0000000000..0fe9ce754d --- /dev/null +++ b/packages/hardhat-ethers/test/type-tests.ts @@ -0,0 +1,13 @@ +import { FactoryOptions } from "../src/types"; + +// FactoryOptions shouldn't have mandatory properties +const _factoryOptions: FactoryOptions = {}; + +// FactoryOptions only has these two properties. +// If new ones are added, then the deployContract +// implementation should be updated to also delete +// those new extra properties +const _factoryOptionsRequired: Required = { + signer: null as any, + libraries: null as any, +}; diff --git a/packages/hardhat-ethers/test/updatable-target-proxy.ts b/packages/hardhat-ethers/test/updatable-target-proxy.ts deleted file mode 100644 index fac06e0e60..0000000000 --- a/packages/hardhat-ethers/test/updatable-target-proxy.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { assert } from "chai"; - -import { createUpdatableTargetProxy } from "../src/internal/updatable-target-proxy"; - -describe("updatable target proxy", function () { - it("should proxy properties", function () { - const o: any = { - a: 1, - getA() { - return this.a; - }, - b: {}, - getB() { - return this.b; - }, - }; - - const { proxy } = createUpdatableTargetProxy(o); - - assert.equal(proxy.a, 1); - assert.equal(proxy.getA(), 1); - assert.equal(proxy.b, o.b); - assert.equal(proxy.getB(), o.b); - }); - - it("should let set a new target", function () { - const o1: any = { - a: 1, - getA() { - return this.a; - }, - b: {}, - getB() { - return this.b; - }, - }; - - const o2: any = { - a: 2, - getA() { - return this.a; - }, - b: {}, - getB() { - return this.b; - }, - }; - - const { proxy, setTarget } = createUpdatableTargetProxy(o1); - - assert.equal(proxy.a, 1); - - setTarget(o2); - - assert.equal(proxy.a, 2); - assert.equal(proxy.getA(), 2); - assert.equal(proxy.b, o2.b); - assert.equal(proxy.getB(), o2.b); - }); - - it("shouldn't let you modify the proxied object", function () { - const o: any = { - a: 1, - }; - - const { proxy } = createUpdatableTargetProxy(o); - - assert.throws(() => { - proxy.a = 2; - }); - assert.throws(() => { - delete proxy.a; - }); - assert.throws(() => { - Object.defineProperty(proxy, "b", {}); - }); - assert.throws(() => { - Object.setPrototypeOf(proxy, {}); - }); - }); - - it("should let you call methods that modify the object", function () { - const o = { - a: 1, - inc() { - this.a++; - }, - }; - - const { proxy } = createUpdatableTargetProxy(o); - - assert.equal(proxy.a, 1); - proxy.inc(); - assert.equal(proxy.a, 2); - }); - - it("should trap getOwnPropertyDescriptor correctly", () => { - const o = { a: 1 }; - const { proxy, setTarget } = createUpdatableTargetProxy(o); - - assert.deepEqual(Object.getOwnPropertyDescriptor(proxy, "a"), { - value: 1, - writable: true, - enumerable: true, - configurable: true, - }); - - const o2 = { a: 2, b: 3 }; - setTarget(o2); - - assert.deepEqual(Object.getOwnPropertyDescriptor(proxy, "a"), { - value: 2, - writable: true, - enumerable: true, - configurable: true, - }); - assert.deepEqual(Object.getOwnPropertyDescriptor(proxy, "b"), { - value: 3, - writable: true, - enumerable: true, - configurable: true, - }); - }); - - it("should trap getPrototypeOf correctly", () => { - const proto = {}; - const o = Object.create(proto); - - const { proxy, setTarget } = createUpdatableTargetProxy(o); - - assert.equal(Object.getPrototypeOf(proxy), proto); - - const proto2 = {}; - const o2 = Object.create(proto2); - - setTarget(o2); - assert.equal(Object.getPrototypeOf(proxy), proto2); - }); - - it("should trap has correctly", () => { - const proto = { a: 1 }; - const o = Object.create(proto); - o.b = 2; - - const { proxy, setTarget } = createUpdatableTargetProxy(o); - - assert.isTrue("a" in proxy); - assert.isTrue("b" in proxy); - assert.isFalse("c" in proxy); - - const proto2 = { a: 2 }; - const o2 = Object.create(proto2); - o2.b = 4; - o2.c = 6; - - setTarget(o2); - assert.isTrue("a" in proxy); - assert.isTrue("b" in proxy); - assert.isTrue("c" in proxy); - assert.isFalse("d" in proxy); - }); - - it("should return isExtensible correctly", () => { - const o: any = {}; - Object.preventExtensions(o); - - const { proxy, setTarget } = createUpdatableTargetProxy(o); - - assert.isFalse(Object.isExtensible(proxy)); - - // if the proxy is initially not extensible, then it can't be made - // extensible afterwards - setTarget({}); - assert.isFalse(Object.isExtensible(proxy)); - }); - - it("should trap ownKeys correctly", () => { - const proto = { a: 1 }; - const o: any = Object.create(proto); - o.b = 1; - - const { proxy, setTarget } = createUpdatableTargetProxy(o); - assert.deepEqual(Object.getOwnPropertyNames(proxy), ["b"]); - - const proto2 = { c: 1 }; - const o2: any = Object.create(proto2); - o2.d = 1; - setTarget(o2); - assert.deepEqual(Object.getOwnPropertyNames(proxy), ["d"]); - }); - - it("should trap preventExtensions correctly", () => { - const o: any = {}; - - const { proxy } = createUpdatableTargetProxy(o); - assert.isTrue(Object.isExtensible(proxy)); - - Object.preventExtensions(proxy); - assert.isFalse(Object.isExtensible(proxy)); - }); -}); diff --git a/packages/hardhat-network-helpers/LICENSE b/packages/hardhat-network-helpers/LICENSE index 3b8858c555..3b7e8c7eab 100644 --- a/packages/hardhat-network-helpers/LICENSE +++ b/packages/hardhat-network-helpers/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-network-helpers/package.json b/packages/hardhat-network-helpers/package.json index 18d28fe319..6aaac35109 100644 --- a/packages/hardhat-network-helpers/package.json +++ b/packages/hardhat-network-helpers/package.json @@ -52,7 +52,7 @@ "eslint-plugin-import": "2.24.1", "eslint-plugin-no-only-tests": "3.0.0", "eslint-plugin-prettier": "3.4.0", - "ethers": "^5.0.0", + "ethers-v5": "npm:ethers@5", "hardhat": "^2.9.5", "mocha": "^10.0.0", "prettier": "2.4.1", diff --git a/packages/hardhat-network-helpers/test/helpers/getStorageAt.ts b/packages/hardhat-network-helpers/test/helpers/getStorageAt.ts index 46de0a1624..06236298dc 100644 --- a/packages/hardhat-network-helpers/test/helpers/getStorageAt.ts +++ b/packages/hardhat-network-helpers/test/helpers/getStorageAt.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { BN } from "ethereumjs-util"; -import { ethers } from "ethers"; +import { ethers } from "ethers-v5"; import * as hh from "../../src"; import { BlockTag, NumberLike } from "../../src/types"; diff --git a/packages/hardhat-network-helpers/test/helpers/mine.ts b/packages/hardhat-network-helpers/test/helpers/mine.ts index 97945826d1..8cf7674389 100644 --- a/packages/hardhat-network-helpers/test/helpers/mine.ts +++ b/packages/hardhat-network-helpers/test/helpers/mine.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { BN } from "ethereumjs-util"; -import { ethers } from "ethers"; +import { ethers } from "ethers-v5"; import * as hh from "../../src"; import { NumberLike } from "../../src/types"; diff --git a/packages/hardhat-network-helpers/test/helpers/mineUpTo.ts b/packages/hardhat-network-helpers/test/helpers/mineUpTo.ts index bce852065a..3010630e27 100644 --- a/packages/hardhat-network-helpers/test/helpers/mineUpTo.ts +++ b/packages/hardhat-network-helpers/test/helpers/mineUpTo.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { BN } from "ethereumjs-util"; -import { ethers } from "ethers"; +import { ethers } from "ethers-v5"; import * as hh from "../../src"; import { useEnvironment } from "../test-utils"; diff --git a/packages/hardhat-network-helpers/test/helpers/setBalance.ts b/packages/hardhat-network-helpers/test/helpers/setBalance.ts index 040a26a770..1af4b3c88d 100644 --- a/packages/hardhat-network-helpers/test/helpers/setBalance.ts +++ b/packages/hardhat-network-helpers/test/helpers/setBalance.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { BN } from "ethereumjs-util"; -import { ethers } from "ethers"; +import { ethers } from "ethers-v5"; import * as hh from "../../src"; import { NumberLike } from "../../src/types"; diff --git a/packages/hardhat-network-helpers/test/helpers/setBlockGasLimit.ts b/packages/hardhat-network-helpers/test/helpers/setBlockGasLimit.ts index 5604bf0f48..0a717d924c 100644 --- a/packages/hardhat-network-helpers/test/helpers/setBlockGasLimit.ts +++ b/packages/hardhat-network-helpers/test/helpers/setBlockGasLimit.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { BN } from "ethereumjs-util"; -import { ethers } from "ethers"; +import { ethers } from "ethers-v5"; import * as hh from "../../src"; import { NumberLike } from "../../src/types"; diff --git a/packages/hardhat-network-helpers/test/helpers/setNextBlockBaseFeePerGas.ts b/packages/hardhat-network-helpers/test/helpers/setNextBlockBaseFeePerGas.ts index 85b078a25a..e9404cb891 100644 --- a/packages/hardhat-network-helpers/test/helpers/setNextBlockBaseFeePerGas.ts +++ b/packages/hardhat-network-helpers/test/helpers/setNextBlockBaseFeePerGas.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { BN } from "ethereumjs-util"; -import { ethers } from "ethers"; +import { ethers } from "ethers-v5"; import * as hh from "../../src"; import { NumberLike } from "../../src/types"; diff --git a/packages/hardhat-network-helpers/test/helpers/setNonce.ts b/packages/hardhat-network-helpers/test/helpers/setNonce.ts index b1f5ea7a45..2e619a7237 100644 --- a/packages/hardhat-network-helpers/test/helpers/setNonce.ts +++ b/packages/hardhat-network-helpers/test/helpers/setNonce.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { BN } from "ethereumjs-util"; -import { ethers } from "ethers"; +import { ethers } from "ethers-v5"; import * as hh from "../../src"; import { NumberLike } from "../../src/types"; diff --git a/packages/hardhat-network-helpers/test/helpers/setPrevRandao.ts b/packages/hardhat-network-helpers/test/helpers/setPrevRandao.ts index a42db5b6ce..18f1a68516 100644 --- a/packages/hardhat-network-helpers/test/helpers/setPrevRandao.ts +++ b/packages/hardhat-network-helpers/test/helpers/setPrevRandao.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { BN } from "ethereumjs-util"; -import { ethers } from "ethers"; +import { ethers } from "ethers-v5"; import * as hh from "../../src"; import { NumberLike } from "../../src/types"; diff --git a/packages/hardhat-network-helpers/test/helpers/setStorageAt.ts b/packages/hardhat-network-helpers/test/helpers/setStorageAt.ts index 18aac66834..62de311629 100644 --- a/packages/hardhat-network-helpers/test/helpers/setStorageAt.ts +++ b/packages/hardhat-network-helpers/test/helpers/setStorageAt.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { BN } from "ethereumjs-util"; -import { ethers } from "ethers"; +import { ethers } from "ethers-v5"; import * as hh from "../../src"; import { toPaddedRpcQuantity } from "../../src/utils"; diff --git a/packages/hardhat-network-helpers/test/helpers/time/advanceBlock.ts b/packages/hardhat-network-helpers/test/helpers/time/advanceBlock.ts index 352462d271..ea7ffebbc6 100644 --- a/packages/hardhat-network-helpers/test/helpers/time/advanceBlock.ts +++ b/packages/hardhat-network-helpers/test/helpers/time/advanceBlock.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { BN } from "ethereumjs-util"; -import { ethers } from "ethers"; +import { ethers } from "ethers-v5"; import * as hh from "../../../src"; import { NumberLike } from "../../../src/types"; diff --git a/packages/hardhat-network-helpers/test/helpers/time/advanceBlockTo.ts b/packages/hardhat-network-helpers/test/helpers/time/advanceBlockTo.ts index 44c4031bee..6d6d6315e4 100644 --- a/packages/hardhat-network-helpers/test/helpers/time/advanceBlockTo.ts +++ b/packages/hardhat-network-helpers/test/helpers/time/advanceBlockTo.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { BN } from "ethereumjs-util"; -import { ethers } from "ethers"; +import { ethers } from "ethers-v5"; import * as hh from "../../../src"; import { useEnvironment } from "../../test-utils"; diff --git a/packages/hardhat-network-helpers/test/helpers/time/increase.ts b/packages/hardhat-network-helpers/test/helpers/time/increase.ts index 719fa169a9..ce14bc809c 100644 --- a/packages/hardhat-network-helpers/test/helpers/time/increase.ts +++ b/packages/hardhat-network-helpers/test/helpers/time/increase.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { BN } from "ethereumjs-util"; -import { ethers } from "ethers"; +import { ethers } from "ethers-v5"; import * as hh from "../../../src"; import { NumberLike } from "../../../src/types"; diff --git a/packages/hardhat-network-helpers/test/helpers/time/increaseTo.ts b/packages/hardhat-network-helpers/test/helpers/time/increaseTo.ts index 9e4301cd79..d0bbb1b4f6 100644 --- a/packages/hardhat-network-helpers/test/helpers/time/increaseTo.ts +++ b/packages/hardhat-network-helpers/test/helpers/time/increaseTo.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; import { BN } from "ethereumjs-util"; -import { ethers } from "ethers"; +import { ethers } from "ethers-v5"; import * as hh from "../../../src"; import { useEnvironment } from "../../test-utils"; diff --git a/packages/hardhat-shorthand/LICENSE b/packages/hardhat-shorthand/LICENSE index 3b8858c555..3b7e8c7eab 100644 --- a/packages/hardhat-shorthand/LICENSE +++ b/packages/hardhat-shorthand/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-solhint/LICENSE b/packages/hardhat-solhint/LICENSE index 3b8858c555..3b7e8c7eab 100644 --- a/packages/hardhat-solhint/LICENSE +++ b/packages/hardhat-solhint/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-solpp/LICENSE b/packages/hardhat-solpp/LICENSE index 3b8858c555..3b7e8c7eab 100644 --- a/packages/hardhat-solpp/LICENSE +++ b/packages/hardhat-solpp/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-toolbox/.eslintrc.js b/packages/hardhat-toolbox/.eslintrc.js index 889740f226..44ed8ed6d5 100644 --- a/packages/hardhat-toolbox/.eslintrc.js +++ b/packages/hardhat-toolbox/.eslintrc.js @@ -1,7 +1,7 @@ module.exports = { extends: [`${__dirname}/../../config/eslint/eslintrc.js`], parserOptions: { - project: `${__dirname}/tsconfig.json`, + project: `${__dirname}/src/tsconfig.json`, sourceType: "module", }, }; diff --git a/packages/hardhat-toolbox/.gitignore b/packages/hardhat-toolbox/.gitignore index c00d7e7296..88c7f760ad 100644 --- a/packages/hardhat-toolbox/.gitignore +++ b/packages/hardhat-toolbox/.gitignore @@ -4,6 +4,10 @@ # Compilation output /build-test/ /dist +/*.js +/*.js.map +/*.d.ts +/*.d.ts.map # Code coverage artifacts /coverage diff --git a/packages/hardhat-toolbox/.prettierignore b/packages/hardhat-toolbox/.prettierignore index 12cbe6bee7..d9f2c83183 100644 --- a/packages/hardhat-toolbox/.prettierignore +++ b/packages/hardhat-toolbox/.prettierignore @@ -3,4 +3,9 @@ /test/fixture-projects/**/artifacts /test/fixture-projects/**/artifacts-dir /test/fixture-projects/**/cache +/*.d.ts +/*.d.ts.map +/*.js +/*.js.map +/build-test CHANGELOG.md diff --git a/packages/hardhat-toolbox/CHANGELOG.md b/packages/hardhat-toolbox/CHANGELOG.md index f0e56c3a1c..569d3f5984 100644 --- a/packages/hardhat-toolbox/CHANGELOG.md +++ b/packages/hardhat-toolbox/CHANGELOG.md @@ -1,5 +1,11 @@ # @nomicfoundation/hardhat-toolbox +## 3.0.0 + +### Major Changes + +- 399347f40: The Toolbox and the plugins that it includes are now based on ethers v6 + ## 2.0.2 ### Patch Changes diff --git a/packages/hardhat-toolbox/LICENSE b/packages/hardhat-toolbox/LICENSE index d5cab88817..3b7e8c7eab 100644 --- a/packages/hardhat-toolbox/LICENSE +++ b/packages/hardhat-toolbox/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-toolbox/README.md b/packages/hardhat-toolbox/README.md index 0ed50f6b6b..eb8bd4d0fe 100644 --- a/packages/hardhat-toolbox/README.md +++ b/packages/hardhat-toolbox/README.md @@ -6,10 +6,10 @@ The `@nomicfoundation/hardhat-toolbox` plugin bundles all the commonly used pack When you use this plugin, you'll be able to: -- Deploy and interact with your contracts using [ethers.js](https://docs.ethers.io/v5/) and the [`hardhat-ethers`](https://hardhat.org/hardhat-runner/plugins/nomiclabs-hardhat-ethers) plugin. +- Deploy and interact with your contracts using [ethers.js](https://docs.ethers.org/v6/) and the [`hardhat-ethers`](https://hardhat.org/hardhat-runner/plugins/nomiclabs-hardhat-ethers) plugin. - Test your contracts with [Mocha](https://mochajs.org/), [Chai](https://chaijs.com/) and our own [Hardhat Chai Matchers](https://hardhat.org/hardhat-chai-matchers) plugin. - Interact with Hardhat Network with our [Hardhat Network Helpers](https://hardhat.org/hardhat-network-helpers). -- Verify the source code of your contracts with the [hardhat-etherscan](https://hardhat.org/hardhat-runner/plugins/nomiclabs-hardhat-etherscan) plugin. +- Verify the source code of your contracts with the [hardhat-verify](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify) plugin. - Get metrics on the gas used by your contracts with the [hardhat-gas-reporter](https://github.com/cgewecke/hardhat-gas-reporter) plugin. - Measure your tests coverage with [solidity-coverage](https://github.com/sc-forks/solidity-coverage). - And, if you are using TypeScript, get type bindings for your contracts with [Typechain](https://github.com/dethcrypto/TypeChain/). @@ -19,3 +19,11 @@ When you use this plugin, you'll be able to: To create a new project that uses the Toolbox, check our [Setting up a project guide](https://hardhat.org/hardhat-runner/docs/guides/project-setup). If you want to migrate an existing Hardhat project to use the Toolbox, read [our migration guide](https://hardhat.org/hardhat-runner/docs/guides/migrating-from-hardhat-waffle). + +### Network Helpers + +When the Toolbox is installed using npm 7 or later, its peer dependencies are automatically installed. However, these dependencies won't be listed in the `package.json`. As a result, directly importing the Network Helpers can be problematic for certain tools or IDEs. To address this issue, the Toolbox re-exports the Hardhat Network Helpers. You can use them like this: + +```ts +import helpers from "@nomicfoundation/hardhat-toolbox/network-helpers"; +``` diff --git a/packages/hardhat-toolbox/package.json b/packages/hardhat-toolbox/package.json index 756773bb13..e10a7908c8 100644 --- a/packages/hardhat-toolbox/package.json +++ b/packages/hardhat-toolbox/package.json @@ -1,6 +1,6 @@ { "name": "@nomicfoundation/hardhat-toolbox", - "version": "2.0.2", + "version": "3.0.0", "description": "Nomic Foundation's recommended bundle of Hardhat plugins", "repository": "github:nomicfoundation/hardhat", "homepage": "https://github.com/nomicfoundation/hardhat/tree/main/packages/hardhat-toolbox", @@ -9,8 +9,8 @@ "Nomic Foundation" ], "license": "MIT", - "main": "dist/src/index.js", - "types": "dist/src/index.d.ts", + "main": "index.js", + "types": "index.d.ts", "keywords": [ "ethereum", "smart-contracts", @@ -25,24 +25,25 @@ "test": "mocha --recursive \"test/**/*.ts\" --exit", "build": "tsc --build .", "prepublishOnly": "yarn build", - "clean": "rimraf dist" + "clean": "rimraf dist *.{d.ts,js}{,.map} build-test tsconfig.tsbuildinfo" }, "files": [ - "dist/src/", "src/", + "*.d.ts", + "*.d.ts.map", + "*.js", + "*.js.map", "LICENSE", "README.md" ], "dependencies": {}, "devDependencies": { - "@ethersproject/abi": "^5.4.7", - "@ethersproject/providers": "^5.4.7", "@nomicfoundation/hardhat-network-helpers": "^1.0.0", - "@nomicfoundation/hardhat-chai-matchers": "^1.0.0", - "@nomiclabs/hardhat-ethers": "^2.0.0", - "@nomiclabs/hardhat-etherscan": "^3.0.0", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", + "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@nomicfoundation/hardhat-verify": "^1.0.0", + "@typechain/ethers-v6": "^0.4.0", + "@typechain/hardhat": "^8.0.0", "@types/chai": "^4.2.0", "@types/mocha": ">=9.1.0", "@types/node": "^14.0.0", @@ -54,7 +55,7 @@ "eslint-plugin-import": "2.24.1", "eslint-plugin-no-only-tests": "3.0.0", "eslint-plugin-prettier": "3.4.0", - "ethers": "^5.4.7", + "ethers": "^6.4.0", "hardhat": "^2.11.0", "hardhat-gas-reporter": "^1.0.8", "mocha": "^10.0.0", @@ -62,28 +63,26 @@ "rimraf": "^3.0.2", "solidity-coverage": "^0.8.1", "ts-node": "^10.8.0", - "typechain": "^8.1.0", + "typechain": "^8.2.0", "typescript": "~4.7.4" }, "peerDependencies": { - "@ethersproject/abi": "^5.4.7", - "@ethersproject/providers": "^5.4.7", "@nomicfoundation/hardhat-network-helpers": "^1.0.0", - "@nomicfoundation/hardhat-chai-matchers": "^1.0.0", - "@nomiclabs/hardhat-ethers": "^2.0.0", - "@nomiclabs/hardhat-etherscan": "^3.0.0", + "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@nomicfoundation/hardhat-verify": "^1.0.0", "@types/chai": "^4.2.0", "@types/mocha": ">=9.1.0", "@types/node": ">=12.0.0", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", + "@typechain/ethers-v6": "^0.4.0", + "@typechain/hardhat": "^8.0.0", "chai": "^4.2.0", - "ethers": "^5.4.7", + "ethers": "^6.4.0", "hardhat": "^2.11.0", "hardhat-gas-reporter": "^1.0.8", "solidity-coverage": "^0.8.1", "ts-node": ">=8.0.0", - "typechain": "^8.1.0", + "typechain": "^8.2.0", "typescript": ">=4.5.0" }, "bugs": { diff --git a/packages/hardhat-toolbox/src/index.ts b/packages/hardhat-toolbox/src/index.ts index 8efa0d2902..0a58d631e4 100644 --- a/packages/hardhat-toolbox/src/index.ts +++ b/packages/hardhat-toolbox/src/index.ts @@ -1,6 +1,6 @@ import "@nomicfoundation/hardhat-chai-matchers"; -import "@nomiclabs/hardhat-ethers"; -import "@nomiclabs/hardhat-etherscan"; +import "@nomicfoundation/hardhat-ethers"; +import "@nomicfoundation/hardhat-verify"; import "@typechain/hardhat"; import "hardhat-gas-reporter"; import "solidity-coverage"; diff --git a/packages/hardhat-toolbox/src/network-helpers.ts b/packages/hardhat-toolbox/src/network-helpers.ts new file mode 100644 index 0000000000..71747baddc --- /dev/null +++ b/packages/hardhat-toolbox/src/network-helpers.ts @@ -0,0 +1 @@ +export * from "@nomicfoundation/hardhat-network-helpers"; diff --git a/packages/hardhat-toolbox/src/tsconfig.json b/packages/hardhat-toolbox/src/tsconfig.json new file mode 100644 index 0000000000..3c04a1ea5c --- /dev/null +++ b/packages/hardhat-toolbox/src/tsconfig.json @@ -0,0 +1,27 @@ +{ + "extends": "../../../config/typescript/tsconfig.json", + "compilerOptions": { + "outDir": "../", + "rootDirs": ["."], + "composite": true + }, + "include": ["./**/*.ts"], + "exclude": [], + "references": [ + { + "path": "../../hardhat-core/src" + }, + { + "path": "../../hardhat-chai-matchers" + }, + { + "path": "../../hardhat-network-helpers" + }, + { + "path": "../../hardhat-ethers/src" + }, + { + "path": "../../hardhat-verify" + } + ] +} diff --git a/packages/hardhat-toolbox/test/.eslintrc.js b/packages/hardhat-toolbox/test/.eslintrc.js new file mode 100644 index 0000000000..757fe8a3ca --- /dev/null +++ b/packages/hardhat-toolbox/test/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + extends: [`${__dirname}/../.eslintrc.js`], + parserOptions: { + project: `${__dirname}/../tsconfig.json`, + sourceType: "module", + }, + rules: { + "import/no-extraneous-dependencies": [ + "error", + { + devDependencies: true, + }, + ], + }, +}; diff --git a/packages/hardhat-toolbox/test/helpers.ts b/packages/hardhat-toolbox/test/helpers.ts index e1b159efe3..4fc372b905 100644 --- a/packages/hardhat-toolbox/test/helpers.ts +++ b/packages/hardhat-toolbox/test/helpers.ts @@ -1,3 +1,6 @@ +// load the environment type extensions from the plugins +import type {} from "../src/index"; + import { resetHardhatContext } from "hardhat/plugins-testing"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import path from "path"; diff --git a/packages/hardhat-toolbox/tsconfig.json b/packages/hardhat-toolbox/tsconfig.json index fd844c410b..65ecfc33b9 100644 --- a/packages/hardhat-toolbox/tsconfig.json +++ b/packages/hardhat-toolbox/tsconfig.json @@ -1,24 +1,15 @@ { "extends": "../../config/typescript/tsconfig.json", "compilerOptions": { - "outDir": "./dist" + "outDir": "./build-test", + "rootDirs": ["./test"], + "composite": true }, - "exclude": ["./dist", "./node_modules", "./test/**/hardhat.config.ts"], + "include": ["./test/**/*.ts"], + "exclude": ["./node_modules", "./test/**/hardhat.config.ts"], "references": [ { - "path": "../hardhat-core/src" - }, - { - "path": "../hardhat-chai-matchers" - }, - { - "path": "../hardhat-network-helpers" - }, - { - "path": "../hardhat-ethers/src" - }, - { - "path": "../hardhat-verify" + "path": "./src" } ] } diff --git a/packages/hardhat-truffle4/LICENSE b/packages/hardhat-truffle4/LICENSE index 3b8858c555..3b7e8c7eab 100644 --- a/packages/hardhat-truffle4/LICENSE +++ b/packages/hardhat-truffle4/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-truffle5/LICENSE b/packages/hardhat-truffle5/LICENSE index 3b8858c555..3b7e8c7eab 100644 --- a/packages/hardhat-truffle5/LICENSE +++ b/packages/hardhat-truffle5/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-truffle5/README.md b/packages/hardhat-truffle5/README.md index f0ba4df798..7797a3913e 100644 --- a/packages/hardhat-truffle5/README.md +++ b/packages/hardhat-truffle5/README.md @@ -15,7 +15,7 @@ This plugin requires [hardhat-web3](https://github.com/nomiclabs/hardhat/tree/ma ## Installation ```bash -npm install --save-dev @nomiclabs/hardhat-truffle5 @nomiclabs/hardhat-web3 web3 +npm install --save-dev @nomiclabs/hardhat-truffle5 @nomiclabs/hardhat-web3 'web3@^1.0.0-beta.36' ``` And add the following statement to your `hardhat.config.js`: diff --git a/packages/hardhat-truffle5/test/tests.ts b/packages/hardhat-truffle5/test/tests.ts index 74d4d43cc7..1c4ee06c2a 100644 --- a/packages/hardhat-truffle5/test/tests.ts +++ b/packages/hardhat-truffle5/test/tests.ts @@ -244,7 +244,10 @@ describe("Gas multiplier", function () { const gasLimit = tx.gas; - assert.equal(gasLimit, Math.floor(web3Estimation * multiplier)); + assert.equal( + gasLimit, + String(Math.floor(Number(web3Estimation) * multiplier)) + ); } async function assertItWorksForFunctions( @@ -280,7 +283,10 @@ describe("Gas multiplier", function () { const tx = await env.web3.eth.getTransaction(txResult.tx); const gasLimit = tx.gas; - assert.equal(gasLimit, Math.floor(web3Estimation * multiplier)); + assert.equal( + gasLimit, + String(Math.floor(Number(web3Estimation) * multiplier)) + ); } describe("When it's set in the network", function () { diff --git a/packages/hardhat-verify/CHANGELOG.md b/packages/hardhat-verify/CHANGELOG.md new file mode 100644 index 0000000000..b504fcae5e --- /dev/null +++ b/packages/hardhat-verify/CHANGELOG.md @@ -0,0 +1,7 @@ +# @nomicfoundation/hardhat-verify + +## 1.0.1 + +### Patch Changes + +- 40b371bca: Removed the compilation step from the verify task, and removed the noCompile flag diff --git a/packages/hardhat-verify/LICENSE b/packages/hardhat-verify/LICENSE index 3b8858c555..3b7e8c7eab 100644 --- a/packages/hardhat-verify/LICENSE +++ b/packages/hardhat-verify/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-verify/README.md b/packages/hardhat-verify/README.md index 350c3049d9..4572cd66d6 100644 --- a/packages/hardhat-verify/README.md +++ b/packages/hardhat-verify/README.md @@ -68,7 +68,7 @@ npx hardhat verify --network mainnet DEPLOYED_CONTRACT_ADDRESS "Constructor argu ### Complex arguments -When the constructor has a complex argument list, you'll need to write a javascript module that exports the argument list. The expected format is the same as a constructor list for an [ethers contract](https://docs.ethers.io/v5/api/contract/). For example, if you have a contract like this: +When the constructor has a complex argument list, you'll need to write a javascript module that exports the argument list. The expected format is the same as a constructor list for an [ethers contract](https://docs.ethers.org/v6/single-page/#api_contract__Contract). For example, if you have a contract like this: ```solidity struct Point { diff --git a/packages/hardhat-verify/package.json b/packages/hardhat-verify/package.json index 029218e3e3..9b4dff4f75 100644 --- a/packages/hardhat-verify/package.json +++ b/packages/hardhat-verify/package.json @@ -1,6 +1,6 @@ { "name": "@nomicfoundation/hardhat-verify", - "version": "1.0.0", + "version": "1.0.1", "description": "Hardhat plugin for verifying contracts", "homepage": "https://github.com/nomicfoundation/hardhat/tree/main/packages/hardhat-verify", "repository": "github:nomicfoundation/hardhat", @@ -44,7 +44,7 @@ "undici": "^5.14.0" }, "devDependencies": { - "@nomiclabs/hardhat-ethers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", "@types/chai": "^4.2.0", "@types/chai-as-promised": "^7.1.3", "@types/lodash.clonedeep": "^4.5.7", diff --git a/packages/hardhat-verify/src/chain-config.ts b/packages/hardhat-verify/src/chain-config.ts index 434b2a00ca..e6ac9cea5f 100644 --- a/packages/hardhat-verify/src/chain-config.ts +++ b/packages/hardhat-verify/src/chain-config.ts @@ -258,7 +258,7 @@ export const builtinChains: ChainConfig[] = [ chainId: 1313161554, urls: { apiURL: "https://explorer.mainnet.aurora.dev/api", - browserURL: "https://aurorascan.dev/", + browserURL: "https://explorer.mainnet.aurora.dev", }, }, { @@ -266,7 +266,7 @@ export const builtinChains: ChainConfig[] = [ chainId: 1313161555, urls: { apiURL: "https://explorer.testnet.aurora.dev/api", - browserURL: "https://testnet.aurorascan.dev", + browserURL: "https://explorer.testnet.aurora.dev", }, }, { diff --git a/packages/hardhat-verify/src/errors.ts b/packages/hardhat-verify/src/errors.ts index 54c0cd7fed..bb6a727135 100644 --- a/packages/hardhat-verify/src/errors.ts +++ b/packages/hardhat-verify/src/errors.ts @@ -6,13 +6,13 @@ import { } from "./abi-validation-extras"; import { TASK_VERIFY_VERIFY } from "./task-names"; -export class HardhatEtherscanError extends NomicLabsHardhatPluginError { +export class HardhatVerifyError extends NomicLabsHardhatPluginError { constructor(message: string, parent?: Error) { super("@nomicfoundation/hardhat-verify", message, parent); } } -export class MissingAddressError extends HardhatEtherscanError { +export class MissingAddressError extends HardhatVerifyError { constructor() { super( "You didn’t provide any address. Please re-run the 'verify' task with the address of the contract you want to verify." @@ -20,20 +20,20 @@ export class MissingAddressError extends HardhatEtherscanError { } } -export class InvalidAddressError extends HardhatEtherscanError { +export class InvalidAddressError extends HardhatVerifyError { constructor(address: string) { super(`${address} is an invalid address.`); } } -export class InvalidContractNameError extends HardhatEtherscanError { +export class InvalidContractNameError extends HardhatVerifyError { constructor(contractName: string) { super(`A valid fully qualified name was expected. Fully qualified names look like this: "contracts/AContract.sol:TheContract" Instead, this name was received: ${contractName}`); } } -export class MissingApiKeyError extends HardhatEtherscanError { +export class MissingApiKeyError extends HardhatVerifyError { constructor(network: string) { super(`You are trying to verify a contract in '${network}', but no API token was found for this network. Please provide one in your hardhat config. For example: @@ -50,7 +50,7 @@ See https://etherscan.io/apis`); } } -export class InvalidConstructorArgumentsError extends HardhatEtherscanError { +export class InvalidConstructorArgumentsError extends HardhatVerifyError { constructor() { super(`The constructorArguments parameter should be an array. If your constructor has no arguments pass an empty array. E.g: @@ -62,7 +62,7 @@ If your constructor has no arguments pass an empty array. E.g: } } -export class ExclusiveConstructorArgumentsError extends HardhatEtherscanError { +export class ExclusiveConstructorArgumentsError extends HardhatVerifyError { constructor() { super( "The parameters constructorArgsParams and constructorArgsModule are exclusive. Please provide only one of them." @@ -70,7 +70,7 @@ export class ExclusiveConstructorArgumentsError extends HardhatEtherscanError { } } -export class InvalidConstructorArgumentsModuleError extends HardhatEtherscanError { +export class InvalidConstructorArgumentsModuleError extends HardhatVerifyError { constructor(constructorArgsModulePath: string) { super(`The module ${constructorArgsModulePath} doesn't export a list. The module should look like this: @@ -78,7 +78,7 @@ module.exports = [ arg1, arg2, ... ];`); } } -export class InvalidLibrariesError extends HardhatEtherscanError { +export class InvalidLibrariesError extends HardhatVerifyError { constructor() { super(`The libraries parameter should be a dictionary. If your contract does not have undetectable libraries pass an empty object or omit the argument. E.g: @@ -90,7 +90,7 @@ If your contract does not have undetectable libraries pass an empty object or om } } -export class InvalidLibrariesModuleError extends HardhatEtherscanError { +export class InvalidLibrariesModuleError extends HardhatVerifyError { constructor(librariesModulePath: string) { super(`The module ${librariesModulePath} doesn't export a dictionary. The module should look like this: @@ -98,7 +98,7 @@ module.exports = { lib1: "0x...", lib2: "0x...", ... };`); } } -export class ImportingModuleError extends HardhatEtherscanError { +export class ImportingModuleError extends HardhatVerifyError { constructor(module: string, parent: Error) { super( `Importing the module for the ${module} failed. @@ -108,7 +108,7 @@ Reason: ${parent.message}`, } } -export class NetworkNotSupportedError extends HardhatEtherscanError { +export class NetworkNotSupportedError extends HardhatVerifyError { constructor(network: string) { super( `The selected network is ${network}. Please select a network supported by Etherscan.` @@ -116,7 +116,7 @@ export class NetworkNotSupportedError extends HardhatEtherscanError { } } -export class ChainConfigNotFoundError extends HardhatEtherscanError { +export class ChainConfigNotFoundError extends HardhatVerifyError { constructor(chainId: number) { super(`Trying to verify a contract in a network with chain id ${chainId}, but the plugin doesn't recognize it as a supported chain. @@ -128,7 +128,7 @@ To see the list of supported networks, run this command: } } -export class ContractVerificationRequestError extends HardhatEtherscanError { +export class ContractVerificationRequestError extends HardhatVerifyError { constructor(url: string, parent: Error) { super( `Failed to send contract verification request. @@ -139,7 +139,7 @@ Reason: ${parent.message}`, } } -export class ContractVerificationInvalidStatusCodeError extends HardhatEtherscanError { +export class ContractVerificationInvalidStatusCodeError extends HardhatVerifyError { constructor(url: string, statusCode: number, responseText: string) { super(`Failed to send contract verification request. Endpoint URL: ${url} @@ -147,7 +147,7 @@ The HTTP server response is not ok. Status code: ${statusCode} Response text: ${ } } -export class ContractVerificationMissingBytecodeError extends HardhatEtherscanError { +export class ContractVerificationMissingBytecodeError extends HardhatVerifyError { constructor(url: string, contractAddress: string) { super(`Failed to send contract verification request. Endpoint URL: ${url} @@ -158,7 +158,7 @@ try to wait for five confirmations of your contract deployment transaction befor } } -export class ContractStatusPollingError extends HardhatEtherscanError { +export class ContractStatusPollingError extends HardhatVerifyError { constructor(url: string, parent: Error) { super( `Failure during etherscan status polling. The verification may still succeed but @@ -170,7 +170,7 @@ Reason: ${parent.message}`, } } -export class ContractStatusPollingInvalidStatusCodeError extends HardhatEtherscanError { +export class ContractStatusPollingInvalidStatusCodeError extends HardhatVerifyError { constructor(statusCode: number, responseText: string) { super( `The HTTP server response is not ok. Status code: ${statusCode} Response text: ${responseText}` @@ -178,7 +178,7 @@ export class ContractStatusPollingInvalidStatusCodeError extends HardhatEthersca } } -export class ContractStatusPollingResponseNotOkError extends HardhatEtherscanError { +export class ContractStatusPollingResponseNotOkError extends HardhatVerifyError { constructor(message: string) { super(`The Etherscan API responded with a failure status. The verification may still succeed but should be checked manually. @@ -186,21 +186,21 @@ Reason: ${message}`); } } -export class EtherscanVersionNotSupportedError extends HardhatEtherscanError { +export class EtherscanVersionNotSupportedError extends HardhatVerifyError { constructor() { super(`Etherscan only supports compiler versions 0.4.11 and higher. See https://etherscan.io/solcversions for more information.`); } } -export class DeployedBytecodeNotFoundError extends HardhatEtherscanError { +export class DeployedBytecodeNotFoundError extends HardhatVerifyError { constructor(address: string, network: string) { super(`The address ${address} has no bytecode. Is the contract deployed to this network? The selected network is ${network}.`); } } -export class CompilerVersionsMismatchError extends HardhatEtherscanError { +export class CompilerVersionsMismatchError extends HardhatVerifyError { constructor( configCompilerVersions: string[], inferredCompilerVersion: string, @@ -221,20 +221,20 @@ Possible causes are: } } -export class ContractNotFoundError extends HardhatEtherscanError { +export class ContractNotFoundError extends HardhatVerifyError { constructor(contractFQN: string) { super(`The contract ${contractFQN} is not present in your project.`); } } -export class BuildInfoNotFoundError extends HardhatEtherscanError { +export class BuildInfoNotFoundError extends HardhatVerifyError { constructor(contractFQN: string) { super(`The contract ${contractFQN} is present in your project, but we couldn't find its sources. Please make sure that it has been compiled by Hardhat and that it is written in Solidity.`); } } -export class BuildInfoCompilerVersionMismatchError extends HardhatEtherscanError { +export class BuildInfoCompilerVersionMismatchError extends HardhatVerifyError { constructor( contractFQN: string, compilerVersion: string, @@ -256,20 +256,24 @@ Possible causes are: } } -export class DeployedBytecodeMismatchError extends HardhatEtherscanError { - constructor(network: string) { - super(`The address provided as argument contains a contract, but its bytecode doesn't match any of your local contracts. +export class DeployedBytecodeMismatchError extends HardhatVerifyError { + constructor(network: string, contractFQN?: string) { + const contractDetails = + typeof contractFQN === "string" + ? `the contract ${contractFQN}.` + : `any of your local contracts.`; + super(`The address provided as argument contains a contract, but its bytecode doesn't match ${contractDetails} Possible causes are: - - Contract code changed after the deployment was executed. This includes code for seemingly unrelated contracts. - - A solidity file was added, moved, deleted or renamed after the deployment was executed. This includes files for seemingly unrelated contracts. - - Solidity compiler settings were modified after the deployment was executed (like the optimizer, target EVM, etc.). + - The artifact for that contract is outdated or missing. You can try compiling the project again with the --force flag before re-running the verification. + - The contract's code changed after the deployment was executed. Sometimes this happens by changes in seemingly unrelated contracts. + - The solidity compiler settings were modified after the deployment was executed (like the optimizer, target EVM, etc.) - The given address is wrong. - The selected network (${network}) is wrong.`); } } -export class DeployedBytecodeMultipleMatchesError extends HardhatEtherscanError { +export class DeployedBytecodeMultipleMatchesError extends HardhatVerifyError { constructor(fqnMatches: string[]) { super(`More than one contract was found to match the deployed bytecode. Please use the contract parameter with one of the following contracts: @@ -288,20 +292,7 @@ contract: "contracts/Example.sol:ExampleContract" } } -export class DeployedBytecodeDoesNotMatchFQNError extends HardhatEtherscanError { - constructor(contractFQN: string, network: string) { - super(`The address provided as argument contains a contract, but its bytecode doesn't match the contract ${contractFQN}. - -Possible causes are: - - Contract code changed after the deployment was executed. This includes code for seemingly unrelated contracts. - - A solidity file was added, moved, deleted or renamed after the deployment was executed. This includes files for seemingly unrelated contracts. - - Solidity compiler settings were modified after the deployment was executed (like the optimizer, target EVM, etc.). - - The given address is wrong. - - The selected network (${network}) is wrong.`); - } -} - -export class InvalidLibraryAddressError extends HardhatEtherscanError { +export class InvalidLibraryAddressError extends HardhatVerifyError { constructor( contractName: string, libraryName: string, @@ -313,7 +304,7 @@ export class InvalidLibraryAddressError extends HardhatEtherscanError { } } -export class DuplicatedLibraryError extends HardhatEtherscanError { +export class DuplicatedLibraryError extends HardhatVerifyError { constructor(libraryName: string, libraryFQN: string) { super( `The library names ${libraryName} and ${libraryFQN} refer to the same library and were given as two entries in the libraries dictionary. @@ -322,7 +313,7 @@ Remove one of them and review your libraries dictionary before proceeding.` } } -export class LibraryNotFoundError extends HardhatEtherscanError { +export class LibraryNotFoundError extends HardhatVerifyError { constructor( contractName: string, libraryName: string, @@ -348,7 +339,7 @@ ${ } } -export class LibraryMultipleMatchesError extends HardhatEtherscanError { +export class LibraryMultipleMatchesError extends HardhatVerifyError { constructor(contractName: string, libraryName: string, fqnMatches: string[]) { super(`The library name ${libraryName} is ambiguous for the contract ${contractName}. It may resolve to one of the following libraries: @@ -358,7 +349,7 @@ To fix this, choose one of these fully qualified library names and replace it in } } -export class MissingLibrariesError extends HardhatEtherscanError { +export class MissingLibrariesError extends HardhatVerifyError { constructor( contractName: string, allLibraries: string[], @@ -375,13 +366,13 @@ ${missingLibraries.map((x) => ` * ${x}`).join("\n")} ${ missingLibraries.length === undetectableLibraries.length - ? "Visit https://hardhat.org/hardhat-runner/plugins/nomiclabs-hardhat-etherscan#libraries-with-undetectable-addresses to learn how to solve this." + ? "Visit https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#libraries-with-undetectable-addresses to learn how to solve this." : "To solve this, you can add them to your --libraries dictionary with their corresponding addresses." }`); } } -export class LibraryAddressesMismatchError extends HardhatEtherscanError { +export class LibraryAddressesMismatchError extends HardhatVerifyError { constructor( conflicts: Array<{ library: string; @@ -403,7 +394,7 @@ You can either fix these addresses in your libraries dictionary or simply remove } } -export class UnexpectedNumberOfFilesError extends HardhatEtherscanError { +export class UnexpectedNumberOfFilesError extends HardhatVerifyError { constructor() { super( "The plugin found an unexpected number of files for this contract. Please report this issue to the Hardhat team." @@ -411,7 +402,7 @@ export class UnexpectedNumberOfFilesError extends HardhatEtherscanError { } } -export class ABIArgumentLengthError extends HardhatEtherscanError { +export class ABIArgumentLengthError extends HardhatVerifyError { constructor( sourceName: string, contractName: string, @@ -426,7 +417,7 @@ but ${providedArgs} arguments were provided instead.`, } } -export class ABIArgumentTypeError extends HardhatEtherscanError { +export class ABIArgumentTypeError extends HardhatVerifyError { constructor(error: ABIArgumentTypeErrorType) { const { value: argValue, argument: argName, reason } = error; super( @@ -437,7 +428,7 @@ Encoder error reason: ${reason}`, } } -export class ABIArgumentOverflowError extends HardhatEtherscanError { +export class ABIArgumentOverflowError extends HardhatVerifyError { constructor(error: ABIArgumentOverflowErrorType) { const { value: argValue, fault: reason, operation } = error; super( @@ -449,7 +440,7 @@ Encoder error reason: ${reason} fault in ${operation}`, } } -export class VerificationAPIUnexpectedMessageError extends HardhatEtherscanError { +export class VerificationAPIUnexpectedMessageError extends HardhatVerifyError { constructor(message: string) { super(`The API responded with an unexpected message. Please report this issue to the Hardhat team. @@ -458,7 +449,7 @@ Message: ${message}`); } } -export class ContractVerificationFailedError extends HardhatEtherscanError { +export class ContractVerificationFailedError extends HardhatVerifyError { constructor(message: string, undetectableLibraries: string[]) { super(`The contract verification failed. Reason: ${message} diff --git a/packages/hardhat-verify/src/etherscan.ts b/packages/hardhat-verify/src/etherscan.ts index 17cdd8b1c5..93b79d481d 100644 --- a/packages/hardhat-verify/src/etherscan.ts +++ b/packages/hardhat-verify/src/etherscan.ts @@ -6,7 +6,7 @@ import { ContractVerificationRequestError, ContractVerificationMissingBytecodeError, ContractVerificationInvalidStatusCodeError, - HardhatEtherscanError, + HardhatVerifyError, MissingApiKeyError, ContractStatusPollingResponseNotOkError, } from "./errors"; @@ -109,7 +109,7 @@ export class Etherscan { } if (!etherscanResponse.isOk()) { - throw new HardhatEtherscanError(etherscanResponse.message); + throw new HardhatVerifyError(etherscanResponse.message); } return etherscanResponse; diff --git a/packages/hardhat-verify/src/index.ts b/packages/hardhat-verify/src/index.ts index 5c78cb59ca..bbe8d9c147 100644 --- a/packages/hardhat-verify/src/index.ts +++ b/packages/hardhat-verify/src/index.ts @@ -8,7 +8,6 @@ import type { import { extendConfig, subtask, task, types } from "hardhat/config"; import { isFullyQualifiedName } from "hardhat/utils/contract-names"; import { - TASK_COMPILE, TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE, TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT, TASK_COMPILE_SOLIDITY_GET_DEPENDENCY_GRAPH, @@ -35,7 +34,7 @@ import { ContractNotFoundError, BuildInfoNotFoundError, BuildInfoCompilerVersionMismatchError, - DeployedBytecodeDoesNotMatchFQNError, + DeployedBytecodeMismatchError, UnexpectedNumberOfFilesError, VerificationAPIUnexpectedMessageError, ContractVerificationFailedError, @@ -69,7 +68,6 @@ interface VerifyTaskArgs { libraries?: string; contract?: string; listNetworks: boolean; - noCompile: boolean; } // verify:verify subtask args @@ -78,7 +76,6 @@ interface VerifySubtaskArgs { constructorArguments: string[]; libraries: LibraryToAddress; contract?: string; - noCompile: boolean; } // parsed verification args @@ -88,7 +85,6 @@ interface VerificationArgs { libraries: LibraryToAddress; contractFQN?: string; listNetworks: boolean; - noCompile: boolean; } interface GetContractInformationArgs { @@ -150,7 +146,6 @@ task(TASK_VERIFY, "Verifies a contract on Etherscan") "Use if the deployed bytecode matches more than one contract in your project" ) .addFlag("listNetworks", "Print the list of supported networks") - .addFlag("noCompile", "Don't compile before running the task") .setAction(async (taskArgs: VerifyTaskArgs, { run }) => { const verificationArgs: VerificationArgs = await run( TASK_VERIFY_RESOLVE_ARGUMENTS, @@ -173,7 +168,6 @@ subtask(TASK_VERIFY_RESOLVE_ARGUMENTS) .addOptionalParam("libraries", undefined, undefined, types.inputFile) .addOptionalParam("contract") .addFlag("listNetworks") - .addFlag("noCompile") .setAction( async ({ address, @@ -182,7 +176,6 @@ subtask(TASK_VERIFY_RESOLVE_ARGUMENTS) contract, libraries: librariesModule, listNetworks, - noCompile, }: VerifyTaskArgs): Promise => { if (address === undefined) { throw new MissingAddressError(); @@ -210,7 +203,6 @@ subtask(TASK_VERIFY_RESOLVE_ARGUMENTS) libraries, contractFQN: contract, listNetworks, - noCompile, }; } ); @@ -234,7 +226,6 @@ subtask(TASK_VERIFY_ETHERSCAN) .addParam("libraries", undefined, undefined, types.any) .addOptionalParam("contractFQN") .addFlag("listNetworks") - .addFlag("noCompile") .setAction( async ( { @@ -243,7 +234,6 @@ subtask(TASK_VERIFY_ETHERSCAN) libraries, contractFQN, listNetworks, - noCompile, }: VerificationArgs, { config, network, run } ) => { @@ -286,11 +276,6 @@ ${contractURL}`); ); } - // Make sure that contract artifacts are up-to-date - if (!noCompile) { - await run(TASK_COMPILE, { quiet: true }); - } - const contractInformation: ExtendedContractInformation = await run( TASK_VERIFY_ETHERSCAN_GET_CONTRACT_INFORMATION, { @@ -414,10 +399,7 @@ subtask(TASK_VERIFY_ETHERSCAN_GET_CONTRACT_INFORMATION) ); if (contractInformation === null) { - throw new DeployedBytecodeDoesNotMatchFQNError( - contractFQN, - network.name - ); + throw new DeployedBytecodeMismatchError(network.name, contractFQN); } } else { contractInformation = await extractInferredContractInformation( @@ -542,16 +524,9 @@ subtask(TASK_VERIFY_VERIFY) .addOptionalParam("constructorArguments", undefined, [], types.any) .addOptionalParam("libraries", undefined, {}, types.any) .addOptionalParam("contract") - .addFlag("noCompile") .setAction( async ( - { - address, - constructorArguments, - libraries, - contract, - noCompile, - }: VerifySubtaskArgs, + { address, constructorArguments, libraries, contract }: VerifySubtaskArgs, { run } ) => { if (address === undefined) { @@ -581,7 +556,6 @@ subtask(TASK_VERIFY_VERIFY) constructorArgs: constructorArguments, libraries, contractFQN: contract, - noCompile, }); } ); diff --git a/packages/hardhat-verify/test/fixture-projects/hardhat-project/hardhat.config.js b/packages/hardhat-verify/test/fixture-projects/hardhat-project/hardhat.config.js index 1904ad4739..a04064c1fd 100644 --- a/packages/hardhat-verify/test/fixture-projects/hardhat-project/hardhat.config.js +++ b/packages/hardhat-verify/test/fixture-projects/hardhat-project/hardhat.config.js @@ -1,4 +1,4 @@ -require("@nomiclabs/hardhat-ethers"); +require("@nomicfoundation/hardhat-ethers"); require("../../../src/index"); diff --git a/packages/hardhat-verify/test/helpers.ts b/packages/hardhat-verify/test/helpers.ts index 34b0a46032..5754ce1b5b 100644 --- a/packages/hardhat-verify/test/helpers.ts +++ b/packages/hardhat-verify/test/helpers.ts @@ -1,7 +1,7 @@ import path from "path"; import { resetHardhatContext } from "hardhat/plugins-testing"; -import type {} from "@nomiclabs/hardhat-ethers"; +import type {} from "@nomicfoundation/hardhat-ethers"; import { FactoryOptions, HardhatRuntimeEnvironment } from "hardhat/types"; declare module "mocha" { @@ -34,9 +34,10 @@ export const deployContract = async ( ): Promise => { const factory = await ethers.getContractFactory(contractName, options); const contract = await factory.deploy(...constructorArguments); - await contract.deployTransaction.wait(confirmations); - console.log(`Deployed ${contractName} at ${contract.address}`); - return contract.address; + await contract.deploymentTransaction()?.wait(confirmations); + const contractAddress = await contract.getAddress(); + console.log(`Deployed ${contractName} at ${contractAddress}`); + return contractAddress; }; export const getRandomAddress = (hre: HardhatRuntimeEnvironment): string => diff --git a/packages/hardhat-verify/test/integration/index.ts b/packages/hardhat-verify/test/integration/index.ts index 1ff457aa67..0a899507d5 100644 --- a/packages/hardhat-verify/test/integration/index.ts +++ b/packages/hardhat-verify/test/integration/index.ts @@ -168,7 +168,7 @@ https://hardhat.etherscan.io/address/${address}#code` }); describe("with deleted artifacts", () => { - it("should not compile the project when the noCompile is provided", async function () { + it("should throw if the artifacts are missing", async function () { await this.hre.run(TASK_CLEAN); // task will fail since we deleted all the artifacts @@ -176,7 +176,6 @@ https://hardhat.etherscan.io/address/${address}#code` this.hre.run(TASK_VERIFY, { address: simpleContractAddress, constructorArgsParams: [], - noCompile: true, }) ).to.be.rejectedWith( /The address provided as argument contains a contract, but its bytecode doesn't match any of your local contracts./ @@ -589,7 +588,6 @@ for verification on the block explorer. Waiting for verification result... NormalLib: normalLibAddress, ConstructorLib: constructorLibAddress, }, - // noCompile: true, }); assert.equal(logStub.callCount, 2); diff --git a/packages/hardhat-verify/test/unit/index.ts b/packages/hardhat-verify/test/unit/index.ts index 46ec2fe913..cc3dd49783 100644 --- a/packages/hardhat-verify/test/unit/index.ts +++ b/packages/hardhat-verify/test/unit/index.ts @@ -61,7 +61,6 @@ describe("verify task", () => { }, contractFQN: "contracts/TestContract.sol:TestContract", listNetworks: true, - noCompile: true, }; const proccesedArgs = await this.hre.run(TASK_VERIFY_RESOLVE_ARGUMENTS, { address, @@ -70,7 +69,6 @@ describe("verify task", () => { libraries: "libraries.js", contract: "contracts/TestContract.sol:TestContract", listNetworks: true, - noCompile: true, }); assert.deepEqual(proccesedArgs, expectedArgs); diff --git a/packages/hardhat-vyper/LICENSE b/packages/hardhat-vyper/LICENSE index 3b8858c555..3b7e8c7eab 100644 --- a/packages/hardhat-vyper/LICENSE +++ b/packages/hardhat-vyper/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-web3-legacy/LICENSE b/packages/hardhat-web3-legacy/LICENSE index 3b8858c555..3b7e8c7eab 100644 --- a/packages/hardhat-web3-legacy/LICENSE +++ b/packages/hardhat-web3-legacy/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-web3/.gitignore b/packages/hardhat-web3/.gitignore index c00d7e7296..bc1256a4fe 100644 --- a/packages/hardhat-web3/.gitignore +++ b/packages/hardhat-web3/.gitignore @@ -93,3 +93,5 @@ typings/ # DynamoDB Local files .dynamodb/ +artifacts +cache \ No newline at end of file diff --git a/packages/hardhat-web3/.mocharc.json b/packages/hardhat-web3/.mocharc.json index 775e35460d..7ee95ee997 100644 --- a/packages/hardhat-web3/.mocharc.json +++ b/packages/hardhat-web3/.mocharc.json @@ -1,6 +1,6 @@ { "require": "ts-node/register/files", - "file": "../common/run-with-ganache", + "file": "../common/run-with-ganache.js", "ignore": ["test/fixture-projects/**/*"], "timeout": 10000 } diff --git a/packages/hardhat-web3/LICENSE b/packages/hardhat-web3/LICENSE index 3b8858c555..3b7e8c7eab 100644 --- a/packages/hardhat-web3/LICENSE +++ b/packages/hardhat-web3/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Nomic Labs LLC +Copyright (c) 2023 Nomic Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/hardhat-web3/README.md b/packages/hardhat-web3/README.md index 364e495058..3112587936 100644 --- a/packages/hardhat-web3/README.md +++ b/packages/hardhat-web3/README.md @@ -11,7 +11,7 @@ This plugin brings to Hardhat the Web3 module and an initialized instance of Web # Installation ```bash -npm install --save-dev @nomiclabs/hardhat-web3 web3 +npm install --save-dev @nomiclabs/hardhat-web3 'web3@^1.0.0-beta.36' ``` And add the following statement to your `hardhat.config.js`: diff --git a/packages/hardhat-web3/package.json b/packages/hardhat-web3/package.json index 99ffc8bf24..490a5ed38a 100644 --- a/packages/hardhat-web3/package.json +++ b/packages/hardhat-web3/package.json @@ -39,6 +39,7 @@ "@typescript-eslint/eslint-plugin": "5.53.0", "@typescript-eslint/parser": "5.53.0", "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", "eslint": "^7.29.0", "eslint-config-prettier": "8.3.0", "eslint-plugin-import": "2.24.1", @@ -50,13 +51,10 @@ "rimraf": "^3.0.2", "ts-node": "^10.8.0", "typescript": "~4.7.4", - "web3": "^1.0.0-beta.36" + "web3": "^4.0.1" }, "peerDependencies": { "hardhat": "^2.0.0", - "web3": "^1.0.0-beta.36" - }, - "dependencies": { - "@types/bignumber.js": "^5.0.0" + "web3": ">=4.0.1" } } diff --git a/packages/hardhat-web3/src/index.ts b/packages/hardhat-web3/src/index.ts index 66519fe9f8..4b27f5bb17 100644 --- a/packages/hardhat-web3/src/index.ts +++ b/packages/hardhat-web3/src/index.ts @@ -2,12 +2,11 @@ import { extendEnvironment } from "hardhat/config"; import { lazyFunction, lazyObject } from "hardhat/plugins"; import "./type-extensions"; -import { Web3HTTPProviderAdapter } from "./web3-provider-adapter"; extendEnvironment((env) => { - env.Web3 = lazyFunction(() => require("web3")); + env.Web3 = lazyFunction(() => require("web3").Web3); env.web3 = lazyObject(() => { - const Web3 = require("web3"); - return new Web3(new Web3HTTPProviderAdapter(env.network.provider)); + const Web3 = require("web3").Web3; + return new Web3(env.network.provider); }); }); diff --git a/packages/hardhat-web3/src/type-extensions.ts b/packages/hardhat-web3/src/type-extensions.ts index af8c9558bb..4353372f1a 100644 --- a/packages/hardhat-web3/src/type-extensions.ts +++ b/packages/hardhat-web3/src/type-extensions.ts @@ -1,10 +1,10 @@ -import type Web3 from "web3"; +import type * as Web3 from "web3"; import "hardhat/types/runtime"; declare module "hardhat/types/runtime" { interface HardhatRuntimeEnvironment { Web3: typeof Web3; - web3: Web3; + web3: Web3.Web3; } } diff --git a/packages/hardhat-web3/src/web3-provider-adapter.ts b/packages/hardhat-web3/src/web3-provider-adapter.ts deleted file mode 100644 index d76c9abbd2..0000000000 --- a/packages/hardhat-web3/src/web3-provider-adapter.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { EthereumProvider } from "hardhat/types"; -import util from "util"; - -export interface JsonRpcRequest { - jsonrpc: string; - method: string; - params: any[]; - id: number; -} - -export interface JsonRpcResponse { - jsonrpc: string; - id: number; - result?: any; - error?: { - code: number; - message: string; - data?: any; - }; -} - -export class Web3HTTPProviderAdapter { - private readonly _provider: EthereumProvider; - - constructor(provider: EthereumProvider) { - this._provider = provider; - // We bind everything here because some test suits break otherwise - this.send = this.send.bind(this) as any; - this.isConnected = this.isConnected.bind(this) as any; - this._sendJsonRpcRequest = this._sendJsonRpcRequest.bind(this) as any; - } - - public send( - payload: JsonRpcRequest, - callback: (error: Error | null, response?: JsonRpcResponse) => void - ): void; - public send( - payload: JsonRpcRequest[], - callback: (error: Error | null, response?: JsonRpcResponse[]) => void - ): void; - public send( - payload: JsonRpcRequest | JsonRpcRequest[], - callback: (error: Error | null, response?: any) => void - ): void { - if (!Array.isArray(payload)) { - util.callbackify(() => this._sendJsonRpcRequest(payload))(callback); - return; - } - - util.callbackify(async () => { - const responses: JsonRpcResponse[] = []; - - for (const request of payload) { - const response = await this._sendJsonRpcRequest(request); - - responses.push(response); - - if (response.error !== undefined) { - break; - } - } - - return responses; - })(callback); - } - - public isConnected(): boolean { - return true; - } - - private async _sendJsonRpcRequest( - request: JsonRpcRequest - ): Promise { - const response: JsonRpcResponse = { - id: request.id, - jsonrpc: "2.0", - }; - - try { - const result = await this._provider.send(request.method, request.params); - response.result = result; - } catch (error: any) { - if (error.code === undefined) { - throw error; - } - - response.error = { - // This might be a mistake, but I'm leaving it as is just in case, - // because it's not obvious what we should do here. - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - code: error.code ? +error.code : 404, - message: error.message, - data: { - stack: error.stack, - name: error.name, - }, - }; - } - - return response; - } -} diff --git a/packages/hardhat-web3/test/fixture-projects/hardhat-project/contracts/Greeter.sol b/packages/hardhat-web3/test/fixture-projects/hardhat-project/contracts/Greeter.sol new file mode 100644 index 0000000000..d7c9b9490c --- /dev/null +++ b/packages/hardhat-web3/test/fixture-projects/hardhat-project/contracts/Greeter.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.5.1; + +contract Greeter { + + string greeting; + + event GreetingUpdated(string greeting); + + constructor() public { + greeting = "Hi"; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + emit GreetingUpdated(_greeting); + } + + function greet() public view returns (string memory) { + return greeting; + } + +} diff --git a/packages/hardhat-web3/test/helpers.ts b/packages/hardhat-web3/test/helpers.ts index 394785e400..c3781481ee 100644 --- a/packages/hardhat-web3/test/helpers.ts +++ b/packages/hardhat-web3/test/helpers.ts @@ -1,5 +1,6 @@ import { resetHardhatContext } from "hardhat/plugins-testing"; import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; import path from "path"; declare module "mocha" { @@ -12,11 +13,12 @@ export function useEnvironment( fixtureProjectName: string, networkName = "localhost" ) { - beforeEach("Loading hardhat environment", function () { + beforeEach("Loading hardhat environment", async function () { process.chdir(path.join(__dirname, "fixture-projects", fixtureProjectName)); process.env.HARDHAT_NETWORK = networkName; this.env = require("hardhat"); + await this.env.run(TASK_COMPILE); }); afterEach("Resetting hardhat", function () { diff --git a/packages/hardhat-web3/test/index.ts b/packages/hardhat-web3/test/index.ts new file mode 100644 index 0000000000..6488c6ddb2 --- /dev/null +++ b/packages/hardhat-web3/test/index.ts @@ -0,0 +1,47 @@ +import chai from "chai"; +import chaiAsPromised from "chai-as-promised"; + +import { Contract } from "web3"; +import { useEnvironment } from "./helpers"; + +chai.use(chaiAsPromised); + +describe("Web3 plugin", function () { + describe("ganache", function () { + useEnvironment("hardhat-project", "localhost"); + + describe("contracts", function () { + it("should deploy", async function () { + const artifact = this.env.artifacts.readArtifactSync("Greeter"); + const Greeter = new Contract(artifact.abi, this.env.web3); + const response = Greeter.deploy({ + data: artifact.bytecode, + }).send({ + from: (await this.env.web3.eth.getAccounts())[0], + }); + await new Promise((resolve) => + response.on("receipt", () => resolve()) + ); + }); + }); + }); + describe("hardhat", function () { + useEnvironment("hardhat-project", "hardhat"); + + describe("contract", function () { + it("should deploy", async function () { + const artifact = this.env.artifacts.readArtifactSync("Greeter"); + const Greeter = new Contract(artifact.abi, this.env.web3); + const from = (await this.env.web3.eth.getAccounts())[0]; + const response = Greeter.deploy({ + data: artifact.bytecode, + }).send({ + from, + }); + await new Promise((resolve) => + response.on("receipt", () => resolve()) + ); + }); + }); + }); +}); diff --git a/packages/hardhat-web3/test/web3-provider-adapter.ts b/packages/hardhat-web3/test/web3-provider-adapter.ts deleted file mode 100644 index 044142d49b..0000000000 --- a/packages/hardhat-web3/test/web3-provider-adapter.ts +++ /dev/null @@ -1,145 +0,0 @@ -/* eslint-disable @typescript-eslint/strict-boolean-expressions */ -import { assert } from "chai"; -import Web3 from "web3"; - -import { - JsonRpcRequest, - JsonRpcResponse, - Web3HTTPProviderAdapter, -} from "../src/web3-provider-adapter"; - -import { useEnvironment } from "./helpers"; - -let nextId = 1; - -function createJsonRpcRequest( - method: string, - params: any[] = [] -): JsonRpcRequest { - return { - id: nextId++, - jsonrpc: "2.0", - method, - params, - }; -} - -describe("Web3 provider adapter", function () { - let realWeb3Provider: any; - let adaptedProvider: Web3HTTPProviderAdapter; - - useEnvironment("hardhat-project"); - - beforeEach(function () { - realWeb3Provider = new Web3.providers.HttpProvider("http://127.0.0.1:8545"); - adaptedProvider = new Web3HTTPProviderAdapter(this.env.network.provider); - }); - - it("Should always return true when isConnected is called", function () { - assert.isTrue(adaptedProvider.isConnected()); - }); - - it("Should return the same as the real provider for sigle requests", function (done) { - const request = createJsonRpcRequest("eth_accounts"); - realWeb3Provider.send( - request, - (error: Error | null, response?: JsonRpcResponse) => { - adaptedProvider.send(request, (error2, response2) => { - assert.deepEqual(error2, error); - assert.deepEqual(response2, response); - done(); - }); - } - ); - }); - - it("Should return the same as the real provider for batched requests", function (done) { - const requests = [ - createJsonRpcRequest("eth_accounts"), - createJsonRpcRequest("net_version"), - createJsonRpcRequest("eth_accounts"), - ]; - - realWeb3Provider.send( - requests, - (error: Error | null, response?: JsonRpcResponse[]) => { - adaptedProvider.send(requests, (error2, response2) => { - assert.deepEqual(error2, error); - assert.deepEqual(response2, response); - done(); - }); - } - ); - }); - - it("Should return the same on error", function (done) { - // We disable this test for RskJ - // See: https://github.com/rsksmart/rskj/issues/876 - this.env.network.provider - .send("web3_clientVersion") - .then((version) => { - if (version.includes("RskJ")) { - done(); - return; - } - - const request = createJsonRpcRequest("error_please"); - - return realWeb3Provider.send( - request, - (error: Error | null, response?: JsonRpcResponse) => { - adaptedProvider.send(request, (error2, response2) => { - assert.deepEqual(error2, error); - assert.equal(response2!.error!.message, response!.error!.message); - done(); - }); - } - ); - }) - .then( - () => {}, - () => {} - ); - }); - - it("Should let all requests complete, even if one of them fails", function (done) { - const requests = [ - createJsonRpcRequest("eth_accounts"), - createJsonRpcRequest("error_please"), - createJsonRpcRequest("eth_accounts"), - ]; - - realWeb3Provider.send( - requests, - (error: Error | null, response?: JsonRpcResponse[]) => { - adaptedProvider.send(requests, (error2, response2) => { - assert.deepEqual(error2, error); - assert.deepEqual(response2![0], response![0]); - assert.equal( - response2![1].error!.message, - response![1].error!.message - ); - - // Ganache doesn't return a value for requests after the failing one, - // so we don't either. Otherwise, this should be tested. - // assert.lengthOf(response2!, response!.length); - // assert.isUndefined(responseFromAdapted![2]);![2]); - - // We disable this test for RskJ - // See: https://github.com/rsksmart/rskj/issues/876 - this.env.network.provider - .send("web3_clientVersion") - .then((version) => { - if (version.includes("RskJ")) { - assert.equal( - response2![1].error!.message, - response![1].error!.message - ); - } - }) - .then(done, done); - }); - } - ); - }); -}); diff --git a/packages/hardhat-web3/web3-lazy-object-tests/when-accessing-web3-class.js b/packages/hardhat-web3/web3-lazy-object-tests/when-accessing-web3-class.js index 08adc79cd1..83656c3640 100644 --- a/packages/hardhat-web3/web3-lazy-object-tests/when-accessing-web3-class.js +++ b/packages/hardhat-web3/web3-lazy-object-tests/when-accessing-web3-class.js @@ -1,6 +1,6 @@ const { lazyFunction, lazyObject } = require("hardhat/plugins"); -global.Web3 = lazyFunction(() => require("web3")); +global.Web3 = lazyFunction(() => require("web3").Web3); global.web3 = lazyObject(() => new global.Web3()); console.log(Web3.version); diff --git a/packages/hardhat-web3/web3-lazy-object-tests/when-accessing-web3-object.js b/packages/hardhat-web3/web3-lazy-object-tests/when-accessing-web3-object.js index 6160b1fced..82d7f4a09f 100644 --- a/packages/hardhat-web3/web3-lazy-object-tests/when-accessing-web3-object.js +++ b/packages/hardhat-web3/web3-lazy-object-tests/when-accessing-web3-object.js @@ -1,6 +1,6 @@ const { lazyFunction, lazyObject } = require("hardhat/plugins"); -global.Web3 = lazyFunction(() => require("web3")); +global.Web3 = lazyFunction(() => require("web3").Web3); global.web3 = lazyObject(() => new global.Web3()); console.log(global.web3.eth.getAccounts.name); diff --git a/yarn.lock b/yarn.lock index 8f15159e61..04bd1c13d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,16 @@ # yarn lockfile v1 +"@adraffy/ens-normalize@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.0.tgz#223572538f6bea336750039bb43a4016dcc8182d" + integrity sha512-iowxq3U30sghZotgl4s/oJRci6WPBfNO5YYgk2cIOMCHr3LeGPcsZjCEr+33Q4N+oV3OABDAtA+pyvWjbvBifQ== + +"@adraffy/ens-normalize@1.9.2", "@adraffy/ens-normalize@^1.8.8": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz#60111a5d9db45b2e5cbb6231b0bb8d97e8659316" + integrity sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg== + "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -529,6 +539,11 @@ crc-32 "^1.2.0" ethereumjs-util "^7.1.5" +"@ethereumjs/rlp@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" + integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== + "@ethereumjs/tx@3.3.2": version "3.3.2" resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.3.2.tgz#348d4624bf248aaab6c44fec2ae67265efe3db00" @@ -537,7 +552,7 @@ "@ethereumjs/common" "^2.5.0" ethereumjs-util "^7.1.2" -"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.0-beta.146", "@ethersproject/abi@^5.0.9", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.7", "@ethersproject/abi@^5.6.3", "@ethersproject/abi@^5.7.0": +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.0-beta.146", "@ethersproject/abi@^5.0.9", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.6.3", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== @@ -728,7 +743,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.4.7", "@ethersproject/providers@^5.7.1", "@ethersproject/providers@^5.7.2": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.1", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -1010,11 +1025,33 @@ lodash "^4.17.16" uuid "^7.0.3" +"@noble/curves@1.0.0", "@noble/curves@~1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.0.0.tgz#e40be8c7daf088aaf291887cbc73f43464a92932" + integrity sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw== + dependencies: + "@noble/hashes" "1.3.0" + +"@noble/hashes@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.2.tgz#e9e035b9b166ca0af657a7848eb2718f0f22f183" + integrity sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA== + "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== +"@noble/hashes@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" + integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== + +"@noble/hashes@~1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" + integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== + "@noble/secp256k1@1.7.1", "@noble/secp256k1@~1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" @@ -1241,22 +1278,6 @@ "@nomicfoundation/solidity-analyzer-win32-ia32-msvc" "0.1.0" "@nomicfoundation/solidity-analyzer-win32-x64-msvc" "0.1.0" -"@nomiclabs/hardhat-etherscan@^3.0.0": - version "3.1.7" - resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.7.tgz#72e3d5bd5d0ceb695e097a7f6f5ff6fcbf062b9a" - integrity sha512-tZ3TvSgpvsQ6B6OGmo1/Au6u8BrAkvs1mIC/eURA3xgIfznUZBhmpne8hv7BXUzw9xNL3fXdpOYgOQlVMTcoHQ== - dependencies: - "@ethersproject/abi" "^5.1.2" - "@ethersproject/address" "^5.0.2" - cbor "^8.1.0" - chalk "^2.4.2" - debug "^4.1.1" - fs-extra "^7.0.1" - lodash "^4.17.11" - semver "^6.3.0" - table "^6.8.0" - undici "^5.14.0" - "@nomiclabs/truffle-contract@^4.2.23": version "4.5.10" resolved "https://registry.yarnpkg.com/@nomiclabs/truffle-contract/-/truffle-contract-4.5.10.tgz#52adcca1068647e1c2b44bf0e6a89fc4ad7f9213" @@ -1287,6 +1308,15 @@ "@noble/secp256k1" "~1.7.0" "@scure/base" "~1.1.0" +"@scure/bip32@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.0.tgz#6c8d980ef3f290987736acd0ee2e0f0d50068d87" + integrity sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q== + dependencies: + "@noble/curves" "~1.0.0" + "@noble/hashes" "~1.3.0" + "@scure/base" "~1.1.0" + "@scure/bip39@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5" @@ -1295,6 +1325,14 @@ "@noble/hashes" "~1.2.0" "@scure/base" "~1.1.0" +"@scure/bip39@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.0.tgz#a207e2ef96de354de7d0002292ba1503538fc77b" + integrity sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg== + dependencies: + "@noble/hashes" "~1.3.0" + "@scure/base" "~1.1.0" + "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" @@ -1535,18 +1573,18 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== -"@typechain/ethers-v5@^10.1.0": - version "10.2.0" - resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-10.2.0.tgz#68f5963efb5214cb2d881477228e4b5b315473e1" - integrity sha512-ikaq0N/w9fABM+G01OFmU3U3dNnyRwEahkdvi9mqy1a3XwKiPZaF/lu54OcNaEWnpvEYyhhS0N7buCtLQqC92w== +"@typechain/ethers-v6@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v6/-/ethers-v6-0.4.0.tgz#fb9e9b8eeadc455fd1fc9048b2340309860deaca" + integrity sha512-vD3Agzz63Gf2XlU3ed2/y+8dLWQj+wf+4Eq+0JXsyOio/plyV5F6r0yYe+s3XdGI858U3Sr263pl8mliDrUqbw== dependencies: lodash "^4.17.15" ts-essentials "^7.0.1" -"@typechain/hardhat@^6.1.2": - version "6.1.5" - resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-6.1.5.tgz#caad58a1d3e9cd88061a584eb4f4fa763d5dcad1" - integrity sha512-lg7LW4qDZpxFMknp3Xool61Fg6Lays8F8TXdFGBG+MxyYcYU5795P1U2XdStuzGq9S2Dzdgh+1jGww9wvZ6r4Q== +"@typechain/hardhat@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-8.0.0.tgz#60568b7a2d0260cc741fb0830a8caee8eb06ac64" + integrity sha512-XUVbqlMx8tJTOmzZCD/r196CidtNWAnTBZRcYxjLTKgcJMvc/kHQpWBnVMMB5QHxVKpYpCiz8g07FYCpG8rrjA== dependencies: fs-extra "^9.1.0" @@ -1555,13 +1593,6 @@ resolved "https://registry.yarnpkg.com/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz#f8e6280e87e8c60b2b938624b0a3530fb3e24712" integrity sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg== -"@types/bignumber.js@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@types/bignumber.js/-/bignumber.js-5.0.0.tgz#d9f1a378509f3010a3255e9cc822ad0eeb4ab969" - integrity sha512-0DH7aPGCClywOFaxxjE6UwpN2kQYe9LwuDQMv+zYA97j5GkOMo8e66LYT+a8JYU7jfmUFRZLa9KycxHDsKXJCA== - dependencies: - bignumber.js "*" - "@types/bn.js@^4.11.3": version "4.11.6" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" @@ -1747,6 +1778,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.13.0.tgz#0400d1e6ce87e9d3032c19eb6c58205b0d3f7850" integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg== +"@types/node@18.15.13": + version "18.15.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" + integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== + "@types/node@^10.0.3": version "10.17.60" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" @@ -2108,6 +2144,16 @@ aes-js@3.0.0: resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== +aes-js@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.3.tgz#da2253f0ff03a0b3a9e445c8cbdf78e7fda7d48c" + integrity sha512-/xJX0/VTPcbc5xQE2VUP91y1xN8q/rDfhEzLm+vLc3hYvb5+qHCnpJRuFcrKn63zumK/sCwYYzhG8HP78JYSTA== + +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -2465,16 +2511,16 @@ bigint-mod-arith@^3.1.0: resolved "https://registry.yarnpkg.com/bigint-mod-arith/-/bigint-mod-arith-3.1.2.tgz#658e416bc593a463d97b59766226d0a3021a76b1" integrity sha512-nx8J8bBeiRR+NlsROFH9jHswW5HO8mgfOSqW0AmjicMMvaONDa8AO+5ViKDUUNytBPWiwfvZP4/Bj4Y3lUfvgQ== -bignumber.js@*, bignumber.js@^9.0.0, bignumber.js@^9.0.1, bignumber.js@^9.0.2: - version "9.1.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" - integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== - bignumber.js@^7.2.1: version "7.2.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== +bignumber.js@^9.0.0, bignumber.js@^9.0.1, bignumber.js@^9.0.2: + version "9.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" + integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== + "bignumber.js@git+https://github.com/frozeman/bignumber.js-nolookahead.git": version "2.0.7" resolved "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934" @@ -3245,7 +3291,7 @@ cosmiconfig@^8.0.0: parse-json "^5.0.0" path-type "^4.0.0" -crc-32@^1.2.0: +crc-32@^1.2.0, crc-32@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== @@ -3278,7 +3324,7 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-fetch@^3.1.4: +cross-fetch@^3.1.4, cross-fetch@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== @@ -4132,6 +4178,16 @@ ethereum-cryptography@^1.0.3: "@scure/bip32" "1.1.5" "@scure/bip39" "1.1.1" +ethereum-cryptography@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.0.0.tgz#e052b49fa81affae29402e977b8d3a31f88612b6" + integrity sha512-g25m4EtfQGjstWgVE1aIz7XYYjf3kH5kG17ULWVB5dH6uLahsoltOhACzSxyDV+fhn4gbR4xRrOXGe6r2uh4Bg== + dependencies: + "@noble/curves" "1.0.0" + "@noble/hashes" "1.3.0" + "@scure/bip32" "1.3.0" + "@scure/bip39" "1.2.0" + ethereum-ens@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/ethereum-ens/-/ethereum-ens-0.8.0.tgz#6d0f79acaa61fdbc87d2821779c4e550243d4c57" @@ -4176,22 +4232,8 @@ ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.2, ethereum ethereum-cryptography "^0.1.3" rlp "^2.2.4" -ethers@^4.0.0-beta.1, ethers@^4.0.32, ethers@^4.0.40: - version "4.0.49" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.49.tgz#0eb0e9161a0c8b4761be547396bbe2fb121a8894" - integrity sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg== - dependencies: - aes-js "3.0.0" - bn.js "^4.11.9" - elliptic "6.5.4" - hash.js "1.1.3" - js-sha3 "0.5.7" - scrypt-js "2.0.4" - setimmediate "1.0.4" - uuid "2.0.1" - xmlhttprequest "1.8.0" - -ethers@^5.0.0, ethers@^5.0.13, ethers@^5.4.7, ethers@^5.7.1: +"ethers-v5@npm:ethers@5", ethers@^5.0.0, ethers@^5.0.13, ethers@^5.7.1: + name ethers-v5 version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -4227,6 +4269,46 @@ ethers@^5.0.0, ethers@^5.0.13, ethers@^5.4.7, ethers@^5.7.1: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" +ethers@^4.0.0-beta.1, ethers@^4.0.32, ethers@^4.0.40: + version "4.0.49" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.49.tgz#0eb0e9161a0c8b4761be547396bbe2fb121a8894" + integrity sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg== + dependencies: + aes-js "3.0.0" + bn.js "^4.11.9" + elliptic "6.5.4" + hash.js "1.1.3" + js-sha3 "0.5.7" + scrypt-js "2.0.4" + setimmediate "1.0.4" + uuid "2.0.1" + xmlhttprequest "1.8.0" + +ethers@^6.1.0: + version "6.2.3" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.2.3.tgz#9ddee438b5949e9724ba4c5d2c3b8deb5202ce96" + integrity sha512-l1Z/Yr+HrOk+7LTeYRHGMvYwVLGpTuVrT/kJ7Kagi3nekGISYILIby0f1ipV9BGzgERyy+w4emH+d3PhhcxIfA== + dependencies: + "@adraffy/ens-normalize" "1.9.0" + "@noble/hashes" "1.1.2" + "@noble/secp256k1" "1.7.1" + aes-js "4.0.0-beta.3" + tslib "2.4.0" + ws "8.5.0" + +ethers@^6.4.0: + version "6.4.2" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.4.2.tgz#8304660278ef7d43959ad5c73ccf14d0e32e0a3f" + integrity sha512-c+vcTycVHp1bN6msgs9iscZKQgEqdzFh6omyvil2musktHJkPtWOKMuiGVBlBqMV100At5AAZBzG9HUBcUWKQA== + dependencies: + "@adraffy/ens-normalize" "1.9.2" + "@noble/hashes" "1.1.2" + "@noble/secp256k1" "1.7.1" + "@types/node" "18.15.13" + aes-js "4.0.0-beta.5" + tslib "2.4.0" + ws "8.5.0" + ethjs-abi@0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/ethjs-abi/-/ethjs-abi-0.1.8.tgz#cd288583ed628cdfadaf8adefa3ba1dbcbca6c18" @@ -4702,6 +4784,20 @@ ganache-cli@^6.12.2: source-map-support "0.5.12" yargs "13.2.4" +generate-function@^2.0.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" + integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + dependencies: + is-property "^1.0.2" + +generate-object-property@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" + integrity sha512-TuOwZWgJ2VAMEGJvAyPWvpqxSANF0LDpmyHauMjFYzaACvn+QTT/AZomvPCzVBV7yDN3OmwHQ5OvHaeLKre3JQ== + dependencies: + is-property "^1.0.0" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -5492,6 +5588,22 @@ is-lower-case@^1.1.0: dependencies: lower-case "^1.1.0" +is-my-ip-valid@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.1.tgz#f7220d1146257c98672e6fba097a9f3f2d348442" + integrity sha512-jxc8cBcOWbNK2i2aTkCZP6i7wkHF1bqKFrwEHuN5Jtg5BSaZHUZQ/JTOJwoV41YvHnOaRyWWh72T/KvfNz9DJg== + +is-my-json-valid@^2.20.6: + version "2.20.6" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.20.6.tgz#a9d89e56a36493c77bda1440d69ae0dc46a08387" + integrity sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw== + dependencies: + generate-function "^2.0.0" + generate-object-property "^1.1.0" + is-my-ip-valid "^1.0.0" + jsonpointer "^5.0.0" + xtend "^4.0.0" + is-negative-zero@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" @@ -5519,6 +5631,11 @@ is-plain-obj@^2.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== +is-property@^1.0.0, is-property@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== + is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -5625,6 +5742,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isomorphic-ws@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -5837,6 +5959,11 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonpointer@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" + integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== + jsonschema@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" @@ -8779,6 +8906,11 @@ tsconfig-paths@^3.10.1: minimist "^1.2.6" strip-bom "^3.0.0" +tslib@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + tslib@^1.8.1, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -8898,10 +9030,10 @@ type@^2.7.2: resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== -typechain@^8.1.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.1.1.tgz#9c2e8012c2c4c586536fc18402dcd7034c4ff0bd" - integrity sha512-uF/sUvnXTOVF2FHKhQYnxHk4su4JjZR8vr4mA2mBaRwHTbwh0jIlqARz9XJr1tA0l7afJGvEa1dTSi4zt039LQ== +typechain@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.2.0.tgz#bd4fc8f111d4405e36858bae6f744604617b60f3" + integrity sha512-tZqhqjxJ9xAS/Lh32jccTjMkpx7sTdUVVHAy5Bf0TIer5QFNYXotiX74oCvoVYjyxUKDK3MXHtMFzMyD3kE+jg== dependencies: "@types/prettier" "^2.1.1" debug "^4.3.1" @@ -9208,6 +9340,28 @@ web3-core@1.8.2: web3-core-requestmanager "1.8.2" web3-utils "1.8.2" +web3-core@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.0.1.tgz#81bfafe3546e62560bd9ee35a62af667419f2c71" + integrity sha512-yGd9FuUhSeLXeSmj+S5YBNdBJfQgBsGGN+uqFRvQKGrKbOp7SXRVNxwTL/JKCLJW2rulcw0JrPD8Ope0A1YvZA== + dependencies: + web3-errors "^1.0.0" + web3-eth-iban "^4.0.1" + web3-providers-http "^4.0.1" + web3-providers-ws "^4.0.1" + web3-types "^1.0.0" + web3-utils "^4.0.1" + web3-validator "^1.0.0" + optionalDependencies: + web3-providers-ipc "^4.0.1" + +web3-errors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.0.0.tgz#c0464d19b8330ec2098b755f8c18330c4ec345e5" + integrity sha512-UadVmAm7FrWfIglZEbyKxEEeVp4p7SMrx1q1SNbX4Cngmsenth96oH+4GSSFLyDASGyWr/yDSDU2alEUTf0yug== + dependencies: + web3-types "^1.0.0" + web3-eth-abi@1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.8.2.tgz#16e1e9be40e2527404f041a4745111211488f31a" @@ -9216,6 +9370,17 @@ web3-eth-abi@1.8.2: "@ethersproject/abi" "^5.6.3" web3-utils "1.8.2" +web3-eth-abi@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.0.1.tgz#75464fb092b61276f1429d464eb5da4cebdeb474" + integrity sha512-l4vS3oxec8A5bO5ognCQQY+ZonPolw77roNVnFdqkmf3MQpUHHovxCn1kFD+eeiT3DpeSt6GbVT9Zt6koA/LHw== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + web3-errors "^1.0.0" + web3-types "^1.0.0" + web3-utils "^4.0.1" + web3-eth-accounts@1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.8.2.tgz#b894f5d5158fcae429da42de75d96520d0712971" @@ -9232,6 +9397,19 @@ web3-eth-accounts@1.8.2: web3-core-method "1.8.2" web3-utils "1.8.2" +web3-eth-accounts@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.0.1.tgz#4eb226a3a43768758e614b71c39661ef819dc18e" + integrity sha512-4SyowjO930H8/Rz6jspYaW2jCbEpqPYKDU/W2WFOHl7KiJ0edoO3mVsupCGAJQCbDG77ijSwMszHj8pA5KhB+A== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + crc-32 "^1.2.2" + ethereum-cryptography "^2.0.0" + web3-errors "^1.0.0" + web3-types "^1.0.0" + web3-utils "^4.0.1" + web3-validator "^1.0.0" + web3-eth-contract@1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.8.2.tgz#5388b7130923d2b790c09a420391a81312a867fb" @@ -9246,6 +9424,19 @@ web3-eth-contract@1.8.2: web3-eth-abi "1.8.2" web3-utils "1.8.2" +web3-eth-contract@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.0.1.tgz#7d753e550ecc97f42a8dcba0d24365d3b17e95e0" + integrity sha512-uVVb1ZZre/kwZIDFJBu7y2LdW5BZO3HJwQKhdqLmnyPTLWTnyKE8Mq2pX5eUZzpoSqXlJCoh0GeAQnIblXYsAw== + dependencies: + web3-core "^4.0.1" + web3-errors "^1.0.0" + web3-eth "^4.0.1" + web3-eth-abi "^4.0.1" + web3-types "^1.0.0" + web3-utils "^4.0.1" + web3-validator "^1.0.0" + web3-eth-ens@1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.8.2.tgz#0a086ad4d919102e28b9fd3036df246add9df22a" @@ -9260,6 +9451,21 @@ web3-eth-ens@1.8.2: web3-eth-contract "1.8.2" web3-utils "1.8.2" +web3-eth-ens@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.0.1.tgz#eb89736d4b17360610bc2cddc978ddd359ba2e75" + integrity sha512-AIPNKs5EyY+w9grIaDkaOxApilBT8Gi7RxJfrVGjR9UnGbHRiH3QX//Y7ZEEkFrhKnZMZ2uim81gyTHP4ujYmg== + dependencies: + "@adraffy/ens-normalize" "^1.8.8" + web3-core "^4.0.1" + web3-errors "^1.0.0" + web3-eth "^4.0.1" + web3-eth-contract "^4.0.1" + web3-net "^4.0.1" + web3-types "^1.0.0" + web3-utils "^4.0.1" + web3-validator "^1.0.0" + web3-eth-iban@1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.8.2.tgz#5cb3022234b13986f086353b53f0379a881feeaf" @@ -9268,6 +9474,16 @@ web3-eth-iban@1.8.2: bn.js "^5.2.1" web3-utils "1.8.2" +web3-eth-iban@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-4.0.1.tgz#8271607b3ca0f4b7062456d7f13b0b2eb2de95dc" + integrity sha512-SSwbB2+8+IlF97zk6wwdDv2rPAoIfXAsjLKBCRy6abf4lLFX3M1s80ZLCXISeB3DR72MvO4iqA5/hHlUu9Jlcg== + dependencies: + web3-errors "^1.0.0" + web3-types "^1.0.0" + web3-utils "^4.0.1" + web3-validator "^1.0.0" + web3-eth-personal@1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.8.2.tgz#3526c1ebaa4e7bf3a0a8ec77e34f067cc9a750b2" @@ -9280,6 +9496,18 @@ web3-eth-personal@1.8.2: web3-net "1.8.2" web3-utils "1.8.2" +web3-eth-personal@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-4.0.1.tgz#44056345f90ad340048789b2274bbaa7b600c660" + integrity sha512-fv/PiUYNtQhjYanHJ+veT5xp7+l+HUGk2/vklGxwl9ntrzvgdYJ7Z87WXI+dqzYAljyuunsjEVP4N5QPED5n7g== + dependencies: + web3-core "^4.0.1" + web3-eth "^4.0.1" + web3-rpc-methods "^1.0.0" + web3-types "^1.0.0" + web3-utils "^4.0.1" + web3-validator "^1.0.0" + web3-eth@1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.8.2.tgz#8562287ae1803c30eb54dc7d832092e5739ce06a" @@ -9298,6 +9526,23 @@ web3-eth@1.8.2: web3-net "1.8.2" web3-utils "1.8.2" +web3-eth@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.0.1.tgz#ce94a7ecf0e5879fb7b8d3623960355fbc50a9e1" + integrity sha512-5Tm6uusfZlWDby1zc8unoHtph5wpLoBgnRvtkzB3ZCwnQKL4KU2kqO/y4sUTSVrTM30y/CmZagTW9PKyRAt0UA== + dependencies: + setimmediate "^1.0.5" + web3-core "^4.0.1" + web3-errors "^1.0.0" + web3-eth-abi "^4.0.1" + web3-eth-accounts "^4.0.1" + web3-net "^4.0.1" + web3-providers-ws "^4.0.1" + web3-rpc-methods "^1.0.0" + web3-types "^1.0.0" + web3-utils "^4.0.1" + web3-validator "^1.0.0" + web3-net@1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.8.2.tgz#97e1e0015fabc4cda31017813e98d0b5468dd04f" @@ -9307,6 +9552,16 @@ web3-net@1.8.2: web3-core-method "1.8.2" web3-utils "1.8.2" +web3-net@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.0.1.tgz#263014e2712e12301da3614f8bd5fbfea6d83ba7" + integrity sha512-Fa4NyGyjx/aZwNxdFg1tSkZAQKyEYxfGOFjmsPCzfOC2zaFExv06UPgDEU+aOiY28cs+kTcx5mhjBKn9PLRraw== + dependencies: + web3-core "^4.0.1" + web3-rpc-methods "^1.0.0" + web3-types "^1.0.0" + web3-utils "^4.0.1" + web3-providers-http@1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.8.2.tgz#fbda3a3bbc8db004af36e91bec35f80273b37885" @@ -9317,6 +9572,16 @@ web3-providers-http@1.8.2: es6-promise "^4.2.8" web3-core-helpers "1.8.2" +web3-providers-http@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-4.0.1.tgz#a5fd9743176e07326792e52064e089aa18840abe" + integrity sha512-scdCB7bmUkZon3nxtP1LRByt16wiaksZtFOwk/sFrVHMbjYjqMvY5asWF+omTgawM20Ga22ESrV2l5FFsQodqA== + dependencies: + cross-fetch "^3.1.5" + web3-errors "^1.0.0" + web3-types "^1.0.0" + web3-utils "^4.0.1" + web3-providers-ipc@1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.8.2.tgz#e52a7250f40c83b99a2482ec5b4cf2728377ae5c" @@ -9325,6 +9590,15 @@ web3-providers-ipc@1.8.2: oboe "2.1.5" web3-core-helpers "1.8.2" +web3-providers-ipc@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-4.0.1.tgz#ea95af4caed2253d237cf67de28fff3fffc27fb3" + integrity sha512-F93UU9LyY5XIC3pHd2Ah3FO6lAbfkPoPUa9yYHgZhwWteZkeo8mDThDYg90QUBvP7qt22vyVpwVNXpw6Hs/QMg== + dependencies: + web3-errors "^1.0.0" + web3-types "^1.0.0" + web3-utils "^4.0.1" + web3-providers-ws@1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.8.2.tgz#56a2b701387011aca9154ca4bc06ea4b5f27e4ef" @@ -9334,6 +9608,26 @@ web3-providers-ws@1.8.2: web3-core-helpers "1.8.2" websocket "^1.0.32" +web3-providers-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-4.0.1.tgz#813f3e2f65e68e0ab55cde1693ab5f8326c34f14" + integrity sha512-TkNLyCkdZ7bBURbSv4+/AP6K4WjS24vuNFbJSyBDgJfmCQxi2/3hX/l+XT/AqkHj7c8amm4yuOZ6JnIkwIlzaw== + dependencies: + isomorphic-ws "^5.0.0" + web3-errors "^1.0.0" + web3-types "^1.0.0" + web3-utils "^4.0.1" + ws "^8.8.1" + +web3-rpc-methods@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.0.0.tgz#d570d10d0f65fea43f8dd3c116792f272d70a077" + integrity sha512-s3awsumvzz0pHxPi3oZxA9IK0Ei1lfZnNqkZ9AMhJjKpIXcPuUhUjYxiAsL1Q9pEcn5vGOLfq1RHNUdXrhNOrQ== + dependencies: + web3-core "^4.0.1" + web3-types "^1.0.0" + web3-validator "^1.0.0" + web3-shh@1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.8.2.tgz#217a417f0d6e243dd4d441848ffc2bd164cea8a0" @@ -9344,6 +9638,11 @@ web3-shh@1.8.2: web3-core-subscriptions "1.8.2" web3-net "1.8.2" +web3-types@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.0.0.tgz#086dddd9696f137620f6a5054dec7c154cccbc5e" + integrity sha512-X6MwXgaZmSCEmqwLnUYVVDn5N3G8RlKStizyy+yOK7qP2VHflM8Pk9ja3VifIXmT1cHgdfLKNBapwAict1X+IA== + web3-utils@1.8.2, web3-utils@^1.0.0-beta.31, web3-utils@^1.3.6: version "1.8.2" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.8.2.tgz#c32dec5e9b955acbab220eefd7715bc540b75cc9" @@ -9357,6 +9656,27 @@ web3-utils@1.8.2, web3-utils@^1.0.0-beta.31, web3-utils@^1.3.6: randombytes "^2.1.0" utf8 "3.0.0" +web3-utils@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.0.1.tgz#c743d408ce8c307191961e881b72a020360b9011" + integrity sha512-q5Pys++MarxUtN/OWrtv7l2kpNBJdDbV13/doO7A2W8I+TqigakKEJQtKiyAIbfnifrIZqyT7+/zzCfPS/sLnw== + dependencies: + ethereum-cryptography "^2.0.0" + web3-errors "^1.0.0" + web3-types "^1.0.0" + web3-validator "^1.0.0" + +web3-validator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-1.0.0.tgz#65860fd735b77c20927f53c2a0a0bb5776460585" + integrity sha512-WShojVeF7hcaPGzO9vgZukqxd6NWL5A9sIv5uhZzK0mGPvPvc0wqSdKeiwby0cFDH09AW2Q1Qz6knKhXDe7CzA== + dependencies: + ethereum-cryptography "^2.0.0" + is-my-json-valid "^2.20.6" + util "^0.12.5" + web3-errors "^1.0.0" + web3-types "^1.0.0" + web3@0.20.6: version "0.20.6" resolved "https://registry.yarnpkg.com/web3/-/web3-0.20.6.tgz#3e97306ae024fb24e10a3d75c884302562215120" @@ -9392,6 +9712,28 @@ web3@^0.20.0: xhr2-cookies "^1.1.0" xmlhttprequest "*" +web3@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/web3/-/web3-4.0.1.tgz#3b9a7978b1ae4568b535626ebdbfefd9cb07c23f" + integrity sha512-IVxPbRy3A+RYB2+NYReNPLDXvE2iamTTvx1oNjM4UdbhNt/KQujQusOaRfSpGqfIKBCIYrim1c5LSCFcKlfQhA== + dependencies: + web3-core "^4.0.1" + web3-errors "^1.0.0" + web3-eth "^4.0.1" + web3-eth-abi "^4.0.1" + web3-eth-accounts "^4.0.1" + web3-eth-contract "^4.0.1" + web3-eth-ens "^4.0.1" + web3-eth-iban "^4.0.1" + web3-eth-personal "^4.0.1" + web3-net "^4.0.1" + web3-providers-http "^4.0.1" + web3-providers-ws "^4.0.1" + web3-rpc-methods "^1.0.0" + web3-types "^1.0.0" + web3-utils "^4.0.1" + web3-validator "^1.0.0" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -9562,6 +9904,11 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== + ws@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" @@ -9576,6 +9923,11 @@ ws@^7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== +ws@^8.8.1: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + wsrun@^5.2.2: version "5.2.4" resolved "https://registry.yarnpkg.com/wsrun/-/wsrun-5.2.4.tgz#6eb6c3ccd3327721a8df073a5e3578fb0dea494e"