diff --git a/.changeset/six-pants-judge.md b/.changeset/six-pants-judge.md new file mode 100644 index 0000000000..5179baef6a --- /dev/null +++ b/.changeset/six-pants-judge.md @@ -0,0 +1,5 @@ +--- +"hardhat": patch +--- + +Added a viem option to the project initialization diff --git a/.github/workflows/compile-with-typescript-v4.yml b/.github/workflows/compile-with-typescript-v4.yml index b1f7da298e..24999a357e 100644 --- a/.github/workflows/compile-with-typescript-v4.yml +++ b/.github/workflows/compile-with-typescript-v4.yml @@ -20,5 +20,10 @@ jobs: - name: Install typescript v4 in all packages run: | sed -i 's/"typescript": "~5.0.0"/"typescript": "^4.0.0"/' package.json packages/*/package.json && yarn + # hardhat-viem is the only package that requires TypeScript v5 + - name: Remove hardhat-viem directory + run: rm -fr packages/hardhat-viem + - name: Remove hardhat-viem from the build script + run: sed -i 's/packages\/hardhat-viem packages\/hardhat-toolbox-viem//' package.json - name: Build run: yarn build diff --git a/.github/workflows/hardhat-viem-ci.yml b/.github/workflows/hardhat-viem-ci.yml new file mode 100644 index 0000000000..33e34fbf61 --- /dev/null +++ b/.github/workflows/hardhat-viem-ci.yml @@ -0,0 +1,81 @@ +name: hardhat-viem CI + +on: + push: + branches: main + paths: + - "packages/hardhat-viem/**" + - "packages/hardhat-core/**" + - "packages/hardhat-common/**" + - "config/**" + pull_request: + branches: + - "**" + paths: + - "packages/hardhat-viem/**" + - "packages/hardhat-core/**" + - "packages/hardhat-common/**" + - "config/**" + workflow_dispatch: + +defaults: + run: + working-directory: packages/hardhat-viem + +concurrency: + group: ${{github.workflow}}-${{github.ref}} + cancel-in-progress: true + +jobs: + test_on_windows: + name: Test hardhat-viem on Windows with Node 16 + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 16 + cache: yarn + - name: Install + run: yarn --frozen-lockfile + - name: Build + run: yarn build + - name: Run tests + run: yarn test + + test_on_macos: + name: Test hardhat-viem on MacOS with Node 16 + runs-on: macos-latest + # disable until actions/virtual-environments#4896 is fixed + if: ${{ false }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 16 + cache: yarn + - name: Install + run: yarn --frozen-lockfile + - name: Build + run: yarn build + - name: Run tests + run: yarn test + + test_on_linux: + name: Test hardhat-viem on Ubuntu with Node ${{ matrix.node }} + runs-on: ubuntu-latest + strategy: + matrix: + node: [16, 18, 20] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node }} + cache: yarn + - name: Install + run: yarn --frozen-lockfile + - name: Build + run: yarn build + - name: Run tests + run: yarn test diff --git a/.gitignore b/.gitignore index 2a6a1afa55..a21ffc4341 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,4 @@ Cargo.lock # VSCode settings .vscode/ +*.code-workspace diff --git a/config/eslint/constants.js b/config/eslint/constants.js index 932f0dc283..eb690df02e 100644 --- a/config/eslint/constants.js +++ b/config/eslint/constants.js @@ -16,5 +16,7 @@ module.exports.slowImportsCommonIgnoredModules = [ "hardhat/builtin-tasks/task-names", "hardhat/internal/core/errors", "hardhat/internal/core/providers/util", + "hardhat/internal/util/fs-utils", "hardhat/utils/contract-names", + "hardhat/utils/source-names", ]; diff --git a/docs/src/content/hardhat-runner/docs/advanced/_dirinfo.yaml b/docs/src/content/hardhat-runner/docs/advanced/_dirinfo.yaml index 6365297f03..523350049b 100644 --- a/docs/src/content/hardhat-runner/docs/advanced/_dirinfo.yaml +++ b/docs/src/content/hardhat-runner/docs/advanced/_dirinfo.yaml @@ -12,3 +12,4 @@ order: - href: /vscode-tests title: Running tests in VS Code - /using-esm + - /using-viem diff --git a/docs/src/content/hardhat-runner/docs/advanced/using-viem.md b/docs/src/content/hardhat-runner/docs/advanced/using-viem.md new file mode 100644 index 0000000000..22f5575c0b --- /dev/null +++ b/docs/src/content/hardhat-runner/docs/advanced/using-viem.md @@ -0,0 +1,314 @@ +# Using Viem + +## Overview + +Most of this documentation assumes that you are using [ethers](https://docs.ethers.org/v6/) as your connection library, but you can also use Hardhat with [Viem](https://viem.sh/docs/introduction.html), a more lightweight and type-safe alternative. This guide explains how to setup a project that uses [the Viem-based Toolbox](/hardhat-runner/plugins/nomicfoundation-hardhat-toolbox-viem) instead of the main one. + +## Installation + +To kickstart a Hardhat project with Typescript and Viem, you can follow these steps: + +1. Initialize a new npm project in an empty directory: + + ::::tabsgroup{options="npm 7+,npm 6,yarn"} + + :::tab{value="npm 7+"} + + ``` + npm init -y + ``` + + ::: + + :::tab{value="npm 6"} + + ``` + npm init -y + ``` + + ::: + + :::tab{value="yarn"} + + ``` + yarn init -y + ``` + + ::: + + :::: + +2. Install `hardhat`: + + ::::tabsgroup{options="npm 7+,npm 6,yarn"} + + :::tab{value="npm 7+"} + + ``` + npm i hardhat + ``` + + ::: + + :::tab{value="npm 6"} + + ``` + npm i hardhat + ``` + + ::: + + :::tab{value="yarn"} + + ``` + yarn add hardhat + ``` + + ::: + + :::: + +3. Run `npx hardhat init` and select the _Create a TypeScript project (with Viem)_ option. + + **Note:** you might want to pin Viem-related dependencies because Viem does not strictly follow semantic versioning for type changes. You can read more [here](#managing-types-and-version-stability). + +## Quick Start + +### Clients + +Viem provides a set of interfaces to interact with the blockchain. The `hardhat-viem` plugin wraps these interfaces and pre-configures them based on your Hardhat project settings, offering a more Hardhat-friendly experience. + +These interfaces are called **clients**, and each one is tailored to a specific type of interaction: + +- The **Public Client** is an interface to the "public” JSON-RPC API methods used to retrieve information from a node. +- The **Wallet Client** is an interface to interact with Ethereum Accounts used to retrieve accounts, execute transactions, sign messages, etc. +- The **Test Client** is an interface to the "test" JSON-RPC API methods used to perform actions that are only possible when connecting to a development node. + +To start using the client interfaces, you need to import the Hardhat Runtime Environment. You can then access them through the `viem` property of the `hre` object. In the following example, we will demonstrate how to use the public and wallet clients to log the balance of an account and then send a transaction. Follow these steps: + +1. Create a `scripts/clients.ts` inside your project directory. +2. Copy and paste the following code snippet into your `scripts/clients.ts` file: + + ```tsx + import { parseEther, formatEther } from "viem"; + import hre from "hardhat"; + + async function main() { + const [bobWalletClient, aliceWalletClient] = + await hre.viem.getWalletClients(); + + const publicClient = await hre.viem.getPublicClient(); + const balanceBefore = await publicClient.getBalance({ + address: bobWalletClient.account.address, + }); + + console.log( + `Balance of ${bobWalletClient.account.address}: ${formatEther( + balanceBefore + )} ETH` + ); + + const hash = await bobWalletClient.sendTransaction({ + to: aliceWalletClient.account.address, + value: parseEther("1"), + }); + + const tx = await publicClient.waitForTransactionReceipt({ hash }); + + console.log( + `Transaction from ${tx.from} to ${tx.to} mined in block ${tx.blockNumber}` + ); + + const balanceAfter = await publicClient.getBalance({ + address: bobWalletClient.account.address, + }); + + console.log( + `Balance of ${bobWalletClient.account.address}: ${formatEther( + balanceAfter + )} ETH` + ); + } + + main() + .then(() => { + process.exit(); + }) + .catch((error) => { + console.error(error); + process.exit(1); + }); + ``` + +3. Open your terminal and run `npx hardhat run scripts/clients.ts` to execute the script. + + This will run the code and display the results in your terminal. + +For more detailed documentation on clients, you can visit the [hardhat-viem plugin site](/hardhat-runner/plugins/nomicfoundation-hardhat-viem#clients) and [Viem's official site](https://viem.sh/docs/clients/intro.html). + +### Contracts + +In addition to the client interfaces, Viem provides functionality for interacting with contracts. The `hardhat-viem` plugin once again provides wrappers for the most useful methods. Additionally, it offers **type generation** for all your contracts, enhancing type checking and suggestions within your IDE! + +To access contract methods, import the Hardhat Runtime Environment and use the `viem` property of the `hre` object, similar to how you access clients. In the following example, we'll obtain an instance of an existing contract to call one of its methods, and then use the retrieved value to deploy a different contract. Follow these steps: + +1. Create a Solidity contract named `MyToken.sol` inside your project's `contract` directory and paste the following snippet: + + ```solidity + // SPDX-License-Identifier: MIT + pragma solidity ^{RECOMMENDED_SOLC_VERSION}; + + contract MyToken { + uint256 public totalSupply; + + constructor(uint256 _initialSupply) { + totalSupply = _initialSupply; + } + + function increaseSupply(uint256 _amount) public { + require(_amount > 0, "Amount must be greater than 0"); + totalSupply += _amount; + } + + function getCurrentSupply() public view returns (uint256) { + return totalSupply; + } + } + ``` + +2. Compile your Solidity contract by running `npx hardhat compile`. This will generate the types for your contract inside the `artifacts` folder of your project. +3. Create a `contracts.ts` inside your project's `scripts` directory with the following content: + + ```tsx + import hre from "hardhat"; + + async function main() { + const myToken = await hre.viem.deployContract("MyToken", [1_000_000n]); + + const initialSupply = await myToken.read.getCurrentSupply(); + console.log(`Initial supply of MyToken: ${initialSupply}`); + + await myToken.write.increaseSupply([500_000n]); + // increaseSupply sends a tx, so we need to wait for it to be mined + const publicClient = await hre.viem.getPublicClient(); + await publicClient.waitForTransactionReceipt({ hash }); + + const newSupply = await myToken.read.getCurrentSupply(); + console.log(`New supply of MyToken: ${newSupply}`); + } + + main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); + ``` + +4. Open your terminal and run `npx hardhat run scripts/contracts.ts` to execute the script. + + This will deploy the `MyToken` contract, use the `increaseSupply()` function to increase the initial supply, and display the result in your terminal. + +#### Contract Type Generation + +The proper types for each contract are generated during compilation. These types are used to overload the `hardhat-viem` types and improve type checking and suggestions. For example, if you copy and paste the following code at the end of the `main()` function of `scripts/contracts.ts`, TypeScript would highlight it as an error: + +```tsx +// The amount is required as a parameter +// TS Error: Expected 1-2 arguments, but got 0. +await myToken.write.increaseSupply(); + +// There is no setSupply function in the MyToken contract +// TS Error: Property 'setSupply' does not exist on type... +const tokenPrice = await myToken.write.setSupply([5000000n]); + +// The first argument of the constructor arguments is expected to be an bigint +// TS Error: No overload matches this call. +const myToken2 = await hre.viem.deployContract("MyToken", ["1000000"]); +``` + +If you want to learn more about working with contracts, you can visit the [`hardhat-viem` plugin site](/hardhat-runner/plugins/nomicfoundation-hardhat-viem#contracts) and [Viem's official site](https://viem.sh/docs/contract/getContract.html). + +### Testing + +In this example, we'll demonstrate how to write tests for the `MyToken` contract defined earlier. These tests cover scenarios like increasing supply and ensuring that certain operations revert as expected. + +1. Create a `test/my-token.ts` file inside your project's directory an copy the following code snippet: + + ```tsx + import hre from "hardhat"; + import { assert, expect } from "chai"; + import { loadFixture } from "@nomicfoundation/hardhat-toolbox-viem/network-helpers"; + + // A deployment function to set up the initial state + const deploy = async () => { + const myToken = await hre.viem.deployContract("MyToken", [1_000_000n]); + + return { myToken }; + }; + + describe("MyToken Contract Tests", function () { + it("should increase supply correctly", async function () { + // Load the contract instance using the deployment function + const { myToken } = await loadFixture(deploy); + + // Get the initial supply + const initialSupply = await myToken.read.getCurrentSupply(); + + // Increase the supply + await myToken.write.increaseSupply([500_000n]); + + // Get the new supply after the increase + const newSupply = await myToken.read.getCurrentSupply(); + + // Assert that the supply increased as expected + assert.equal(initialSupply + 500_000n, newSupply); + }); + + it("should revert when increasing supply by less than 1", async function () { + // Load the contract instance using the deployment function + const { myToken } = await loadFixture(deploy); + + // Attempt to increase supply by 0 (which should fail) + await expect(myToken.write.increaseSupply([0n])).to.be.rejectedWith( + "Amount must be greater than 0" + ); + }); + }); + ``` + +2. Open your terminal and run `npx hardhat test` to run your tests. + +### Managing Types and Version Stability + +Viem adopts a particular [approach to handling changes in types within their codebase](https://viem.sh/docs/typescript.html#typescript). They consider these changes as non-breaking and typically release them as patch version updates. This approach has implications for users of both `hardhat-viem` and `hardhat-toolbox-viem`. + +**Option 1: Pinning Versions (Recommended for Stability)** + +Viem recommends pinning their package version in your project. However, it's important to note that if you choose to follow this recommendation, you should also pin the versions of `hardhat-viem` and `hardhat-toolbox-viem`. This ensures version compatibility and stability for your project. However, it's worth mentioning that by pinning versions, you may miss out on potential improvements and updates shipped with our plugins. + +To pin the versions, follow these steps: + +1. Explicitly install `hardhat-viem`, `hardhat-toolbox-viem`, and `viem`. This will add these dependencies to your `package.json` file: + + ```tsx + npm i @nomicfoundation/hardhat-toolbox-viem @nomicfoundation/hardhat-viem viem + ``` + +2. Open your `package.json` file and remove the caret character (**`^`**) from the versions of the three packages: + + ```json + { + "dependencies": { + "@nomicfoundation/hardhat-toolbox-viem": "X.Y.Z", + "@nomicfoundation/hardhat-viem": "X.Y.Z", + "viem": "X.Y.Z" + } + } + ``` + +**Option 2: Stay Updated (Recommended for Features)** + +Alternatively, you can choose not to pin versions and remain aware that your project's types may break if a newer version of `viem` is installed. By opting for this approach, you won't miss out on important upgrades and features, but you might need to address type errors occasionally. + +Both options have their merits, and your choice depends on whether you prioritize stability or staying up-to-date with the latest features and improvements. diff --git a/docs/src/content/hardhat-runner/plugins/_dirinfo.yaml b/docs/src/content/hardhat-runner/plugins/_dirinfo.yaml index 8cff773a89..68dfd588ae 100644 --- a/docs/src/content/hardhat-runner/plugins/_dirinfo.yaml +++ b/docs/src/content/hardhat-runner/plugins/_dirinfo.yaml @@ -2,8 +2,10 @@ section-type: plugins section-title: Plugins order: - "@nomicfoundation/hardhat-toolbox" + - "@nomicfoundation/hardhat-toolbox-viem" - "@nomicfoundation/hardhat-chai-matchers" - "@nomicfoundation/hardhat-ethers" + - "@nomicfoundation/hardhat-viem" - "@nomicfoundation/hardhat-verify" - "@nomicfoundation/hardhat-foundry" - "@nomicfoundation/hardhat-ledger" diff --git a/docs/src/content/hardhat-runner/plugins/plugins.ts b/docs/src/content/hardhat-runner/plugins/plugins.ts index fa43a58895..2f423cc97f 100644 --- a/docs/src/content/hardhat-runner/plugins/plugins.ts +++ b/docs/src/content/hardhat-runner/plugins/plugins.ts @@ -226,7 +226,14 @@ const communityPlugins: IPlugin[] = [ authorUrl: "https://github.com/symfoni/", description: "A Hardhat plugin that generates a React hook component from your smart contracts. Hot reloaded into your React app. Deployed or not deployed. And everything typed and initialized.", - tags: ["Ethers", "React", "Deploy", "Typechain", "Frontend", "Web3modal"], + tags: [ + "Ethers.js", + "React", + "Deploy", + "Typechain", + "Frontend", + "Web3modal", + ], }, { name: "hardhat-etherscan-abi", @@ -845,8 +852,17 @@ const officialPlugins: IPlugin[] = [ name: "@nomicfoundation/hardhat-toolbox", author: "Nomic Foundation", authorUrl: "https://twitter.com/NomicFoundation", - description: "Nomic Foundation's recommended bundle of Hardhat plugins", - tags: ["Hardhat", "Setup"], + description: + "Nomic Foundation's recommended bundle of Hardhat plugins (ethers based)", + tags: ["Hardhat", "Setup", "Ethers.js"], + }, + { + name: "@nomicfoundation/hardhat-toolbox-viem", + author: "Nomic Foundation", + authorUrl: "https://twitter.com/NomicFoundation", + description: + "Nomic Foundation's recommended bundle of Hardhat plugins (viem based)", + tags: ["Hardhat", "Setup", "viem"], }, { name: "@nomicfoundation/hardhat-chai-matchers", @@ -862,6 +878,13 @@ const officialPlugins: IPlugin[] = [ description: "Injects ethers.js into the Hardhat Runtime Environment", tags: ["Ethers.js", "Testing", "Tasks", "Scripts"], }, + { + name: "@nomicfoundation/hardhat-viem", + author: "Nomic Foundation", + authorUrl: "https://twitter.com/NomicFoundation", + description: "Makes it easier to use viem in a Hardhat project", + tags: ["viem", "Testing", "Tasks", "Scripts"], + }, { name: "@nomicfoundation/hardhat-verify", author: "Nomic Foundation", diff --git a/package.json b/package.json index 43ef14899c..bb316af041 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,8 @@ "wsrun": "^5.2.2" }, "scripts": { - "build": "tsc --build packages/hardhat-core packages/hardhat-ethers packages/hardhat-verify packages/hardhat-solhint packages/hardhat-solpp packages/hardhat-truffle4 packages/hardhat-truffle5 packages/hardhat-vyper packages/hardhat-web3 packages/hardhat-web3-legacy packages/hardhat-chai-matchers packages/hardhat-network-helpers packages/hardhat-toolbox packages/hardhat-foundry packages/hardhat-ledger", - "watch": "tsc --build --watch packages/hardhat-core/src packages/hardhat-ethers packages/hardhat-verify packages/hardhat-solhint packages/hardhat-solpp packages/hardhat-truffle4 packages/hardhat-truffle5 packages/hardhat-vyper packages/hardhat-web3 packages/hardhat-web3-legacy packages/hardhat-chai-matchers packages/hardhat-network-helpers packages/hardhat-toolbox packages/hardhat-foundry packages/hardhat-ledger", + "build": "tsc --build packages/hardhat-core packages/hardhat-ethers packages/hardhat-verify packages/hardhat-solhint packages/hardhat-solpp packages/hardhat-truffle4 packages/hardhat-truffle5 packages/hardhat-vyper packages/hardhat-web3 packages/hardhat-web3-legacy packages/hardhat-chai-matchers packages/hardhat-network-helpers packages/hardhat-toolbox packages/hardhat-foundry packages/hardhat-ledger packages/hardhat-viem packages/hardhat-toolbox-viem", + "watch": "tsc --build --watch packages/hardhat-core/src packages/hardhat-ethers packages/hardhat-verify packages/hardhat-solhint packages/hardhat-solpp packages/hardhat-truffle4 packages/hardhat-truffle5 packages/hardhat-vyper packages/hardhat-web3 packages/hardhat-web3-legacy packages/hardhat-chai-matchers packages/hardhat-network-helpers packages/hardhat-toolbox packages/hardhat-foundry packages/hardhat-ledger packages/hardhat-viem packages/hardhat-toolbox-viem", "clean": "wsrun --exclude-missing clean", "test": "node scripts/run-tests.js", "lint": "wsrun --exclude-missing --stages lint && yarn prettier --check", diff --git a/packages/hardhat-core/sample-projects/typescript-viem/LICENSE.md b/packages/hardhat-core/sample-projects/typescript-viem/LICENSE.md new file mode 100644 index 0000000000..c539a505b4 --- /dev/null +++ b/packages/hardhat-core/sample-projects/typescript-viem/LICENSE.md @@ -0,0 +1,11 @@ +# License + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to [https://unlicense.org](https://unlicense.org) diff --git a/packages/hardhat-core/sample-projects/typescript-viem/README.md b/packages/hardhat-core/sample-projects/typescript-viem/README.md new file mode 100644 index 0000000000..7be82e5d68 --- /dev/null +++ b/packages/hardhat-core/sample-projects/typescript-viem/README.md @@ -0,0 +1,13 @@ +# Sample Hardhat Project + +This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a script that deploys that contract. + +Try running some of the following tasks: + +```shell +npx hardhat help +npx hardhat test +REPORT_GAS=true npx hardhat test +npx hardhat node +npx hardhat run scripts/deploy.ts +``` diff --git a/packages/hardhat-core/sample-projects/typescript-viem/contracts/Lock.sol b/packages/hardhat-core/sample-projects/typescript-viem/contracts/Lock.sol new file mode 100644 index 0000000000..50935f61fd --- /dev/null +++ b/packages/hardhat-core/sample-projects/typescript-viem/contracts/Lock.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.9; + +// Uncomment this line to use console.log +// import "hardhat/console.sol"; + +contract Lock { + uint public unlockTime; + address payable public owner; + + event Withdrawal(uint amount, uint when); + + constructor(uint _unlockTime) payable { + require( + block.timestamp < _unlockTime, + "Unlock time should be in the future" + ); + + unlockTime = _unlockTime; + owner = payable(msg.sender); + } + + function withdraw() public { + // Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal + // console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp); + + require(block.timestamp >= unlockTime, "You can't withdraw yet"); + require(msg.sender == owner, "You aren't the owner"); + + emit Withdrawal(address(this).balance, block.timestamp); + + owner.transfer(address(this).balance); + } +} diff --git a/packages/hardhat-core/sample-projects/typescript-viem/hardhat.config.ts b/packages/hardhat-core/sample-projects/typescript-viem/hardhat.config.ts new file mode 100644 index 0000000000..814b1ebce3 --- /dev/null +++ b/packages/hardhat-core/sample-projects/typescript-viem/hardhat.config.ts @@ -0,0 +1,8 @@ +import { HardhatUserConfig } from "hardhat/config"; +import "@nomicfoundation/hardhat-toolbox-viem"; + +const config: HardhatUserConfig = { + solidity: "0.8.19", +}; + +export default config; diff --git a/packages/hardhat-core/sample-projects/typescript-viem/scripts/deploy.ts b/packages/hardhat-core/sample-projects/typescript-viem/scripts/deploy.ts new file mode 100644 index 0000000000..d9d7d35597 --- /dev/null +++ b/packages/hardhat-core/sample-projects/typescript-viem/scripts/deploy.ts @@ -0,0 +1,26 @@ +import viem from "viem"; +import hre from "hardhat"; + +async function main() { + const currentTimestampInSeconds = Math.round(Date.now() / 1000); + const unlockTime = BigInt(currentTimestampInSeconds + 60); + + const lockedAmount = viem.parseEther("0.001"); + + const lock = await hre.viem.deployContract("Lock", [unlockTime], { + value: lockedAmount, + }); + + console.log( + `Lock with ${viem.formatEther( + lockedAmount + )}ETH and unlock timestamp ${unlockTime} deployed to ${lock.address}` + ); +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/packages/hardhat-core/sample-projects/typescript-viem/test/Lock.ts b/packages/hardhat-core/sample-projects/typescript-viem/test/Lock.ts new file mode 100644 index 0000000000..59d6906d96 --- /dev/null +++ b/packages/hardhat-core/sample-projects/typescript-viem/test/Lock.ts @@ -0,0 +1,132 @@ +import { + time, + loadFixture, +} from "@nomicfoundation/hardhat-toolbox-viem/network-helpers"; +import { expect } from "chai"; +import hre from "hardhat"; +import { getAddress, parseGwei } from "viem"; + +describe("Lock", function () { + // We define a fixture to reuse the same setup in every test. + // We use loadFixture to run this setup once, snapshot that state, + // and reset Hardhat Network to that snapshot in every test. + async function deployOneYearLockFixture() { + const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; + + const lockedAmount = parseGwei("1"); + const unlockTime = BigInt((await time.latest()) + ONE_YEAR_IN_SECS); + + // Contracts are deployed using the first signer/account by default + const [owner, otherAccount] = await hre.viem.getWalletClients(); + + const lock = await hre.viem.deployContract("Lock", [unlockTime], { + value: lockedAmount, + }); + + const publicClient = await hre.viem.getPublicClient(); + + return { + lock, + unlockTime, + lockedAmount, + owner, + otherAccount, + publicClient, + }; + } + + describe("Deployment", function () { + it("Should set the right unlockTime", async function () { + const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture); + + expect(await lock.read.unlockTime()).to.equal(unlockTime); + }); + + it("Should set the right owner", async function () { + const { lock, owner } = await loadFixture(deployOneYearLockFixture); + + expect(await lock.read.owner()).to.equal(getAddress(owner.account.address)); + }); + + it("Should receive and store the funds to lock", async function () { + const { lock, lockedAmount, publicClient } = await loadFixture( + deployOneYearLockFixture + ); + + expect( + await publicClient.getBalance({ + address: lock.address, + }) + ).to.equal(lockedAmount); + }); + + it("Should fail if the unlockTime is not in the future", async function () { + // We don't use the fixture here because we want a different deployment + const latestTime = BigInt(await time.latest()); + await expect( + hre.viem.deployContract("Lock", [latestTime], { + value: 1n, + }) + ).to.be.rejectedWith("Unlock time should be in the future"); + }); + }); + + describe("Withdrawals", function () { + describe("Validations", function () { + it("Should revert with the right error if called too soon", async function () { + const { lock } = await loadFixture(deployOneYearLockFixture); + + await expect(lock.write.withdraw()).to.be.rejectedWith( + "You can't withdraw yet" + ); + }); + + it("Should revert with the right error if called from another account", async function () { + const { lock, unlockTime, otherAccount } = await loadFixture( + deployOneYearLockFixture + ); + + // We can increase the time in Hardhat Network + await time.increaseTo(unlockTime); + + // We retrieve the contract with a different account to send a transaction + const lockAsOtherAccount = await hre.viem.getContractAt( + "Lock", + lock.address, + { walletClient: otherAccount } + ); + await expect(lockAsOtherAccount.write.withdraw()).to.be.rejectedWith( + "You aren't the owner" + ); + }); + + it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () { + const { lock, unlockTime } = await loadFixture( + deployOneYearLockFixture + ); + + // Transactions are sent using the first signer by default + await time.increaseTo(unlockTime); + + await expect(lock.write.withdraw()).to.be.fulfilled; + }); + }); + + describe("Events", function () { + it("Should emit an event on withdrawals", async function () { + const { lock, unlockTime, lockedAmount, publicClient } = + await loadFixture(deployOneYearLockFixture); + + await time.increaseTo(unlockTime); + + const hash = await lock.write.withdraw(); + await publicClient.waitForTransactionReceipt({ hash }); + + // get the withdrawal events in the latest block + const withdrawalEvents = await lock.getEvents.Withdrawal() + expect(withdrawalEvents).to.have.lengthOf(1); + expect(withdrawalEvents[0].args.amount).to.equal(lockedAmount); + }); + }); + }); +}); diff --git a/packages/hardhat-core/sample-projects/typescript-viem/tsconfig.json b/packages/hardhat-core/sample-projects/typescript-viem/tsconfig.json new file mode 100644 index 0000000000..574e785c71 --- /dev/null +++ b/packages/hardhat-core/sample-projects/typescript-viem/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + } +} diff --git a/packages/hardhat-core/src/builtin-tasks/compile.ts b/packages/hardhat-core/src/builtin-tasks/compile.ts index 91eb11fbb5..19dc860801 100644 --- a/packages/hardhat-core/src/builtin-tasks/compile.ts +++ b/packages/hardhat-core/src/builtin-tasks/compile.ts @@ -82,14 +82,9 @@ import { SolidityFilesCache, } from "./utils/solidity-files-cache"; -type ArtifactsEmittedPerFile = Array<{ - file: taskTypes.ResolvedFile; - artifactsEmitted: string[]; -}>; - type ArtifactsEmittedPerJob = Array<{ compilationJob: CompilationJob; - artifactsEmittedPerFile: ArtifactsEmittedPerFile; + artifactsEmittedPerFile: taskTypes.ArtifactsEmittedPerFile; }>; function isConsoleLogError(error: any): boolean { @@ -854,7 +849,7 @@ subtask(TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS) }, { artifacts, run } ): Promise<{ - artifactsEmittedPerFile: ArtifactsEmittedPerFile; + artifactsEmittedPerFile: taskTypes.ArtifactsEmittedPerFile; }> => { const pathToBuildInfo = await artifacts.saveBuildInfo( compilationJob.getSolcConfig().version, @@ -863,7 +858,7 @@ subtask(TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS) output ); - const artifactsEmittedPerFile: ArtifactsEmittedPerFile = + const artifactsEmittedPerFile: taskTypes.ArtifactsEmittedPerFile = await Promise.all( compilationJob .getResolvedFiles() @@ -991,7 +986,7 @@ subtask(TASK_COMPILE_SOLIDITY_COMPILE_JOB) }, { run } ): Promise<{ - artifactsEmittedPerFile: ArtifactsEmittedPerFile; + artifactsEmittedPerFile: taskTypes.ArtifactsEmittedPerFile; compilationJob: taskTypes.CompilationJob; input: CompilerInput; output: CompilerOutput; diff --git a/packages/hardhat-core/src/internal/cli/project-creation.ts b/packages/hardhat-core/src/internal/cli/project-creation.ts index 3be3321b77..9dc392c2e2 100644 --- a/packages/hardhat-core/src/internal/cli/project-creation.ts +++ b/packages/hardhat-core/src/internal/cli/project-creation.ts @@ -30,35 +30,51 @@ import { Dependencies, PackageManager } from "./types"; enum Action { CREATE_JAVASCRIPT_PROJECT_ACTION = "Create a JavaScript project", CREATE_TYPESCRIPT_PROJECT_ACTION = "Create a TypeScript project", + CREATE_TYPESCRIPT_VIEM_PROJECT_ACTION = "Create a TypeScript project (with Viem)", CREATE_EMPTY_HARDHAT_CONFIG_ACTION = "Create an empty hardhat.config.js", QUIT_ACTION = "Quit", } type SampleProjectTypeCreationAction = | Action.CREATE_JAVASCRIPT_PROJECT_ACTION - | Action.CREATE_TYPESCRIPT_PROJECT_ACTION; + | Action.CREATE_TYPESCRIPT_PROJECT_ACTION + | Action.CREATE_TYPESCRIPT_VIEM_PROJECT_ACTION; const HARDHAT_PACKAGE_NAME = "hardhat"; -const PROJECT_DEPENDENCIES: Dependencies = { +const PROJECT_DEPENDENCIES: Dependencies = {}; + +const ETHERS_PROJECT_DEPENDENCIES: Dependencies = { "@nomicfoundation/hardhat-toolbox": "^3.0.0", }; +const VIEM_PROJECT_DEPENDENCIES: Dependencies = { + "@nomicfoundation/hardhat-toolbox-viem": "^1.0.0", +}; + const PEER_DEPENDENCIES: Dependencies = { hardhat: "^2.14.0", "@nomicfoundation/hardhat-network-helpers": "^1.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: "^6.4.0", "hardhat-gas-reporter": "^1.0.8", "solidity-coverage": "^0.8.0", +}; + +const ETHERS_PEER_DEPENDENCIES: Dependencies = { + "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", + ethers: "^6.4.0", "@typechain/hardhat": "^8.0.0", typechain: "^8.1.0", "@typechain/ethers-v6": "^0.4.0", }; +const VIEM_PEER_DEPENDENCIES: Dependencies = { + "@nomicfoundation/hardhat-viem": "^1.0.0", + viem: "^1.15.1", +}; + const TYPESCRIPT_DEPENDENCIES: Dependencies = {}; const TYPESCRIPT_PEER_DEPENDENCIES: Dependencies = { @@ -69,6 +85,15 @@ const TYPESCRIPT_PEER_DEPENDENCIES: Dependencies = { typescript: ">=4.5.0", }; +const TYPESCRIPT_ETHERS_PEER_DEPENDENCIES: Dependencies = { + typescript: ">=4.5.0", +}; + +const TYPESCRIPT_VIEM_PEER_DEPENDENCIES: Dependencies = { + "@types/chai-as-promised": "^7.1.6", + typescript: "~5.0.4", +}; + // generated with the "colossal" font function printAsciiLogo() { console.log( @@ -130,6 +155,8 @@ async function copySampleProject( false, "Shouldn't try to create a TypeScript project in an ESM based project" ); + } else if (projectType === Action.CREATE_TYPESCRIPT_VIEM_PROJECT_ACTION) { + sampleProjectName = "typescript-viem"; } else { sampleProjectName = "typescript"; } @@ -262,26 +289,36 @@ async function getAction(isEsm: boolean): Promise { type: "select", message: "What do you want to do?", initial: 0, - choices: Object.values(Action).map((a: Action) => { - let message: string; - if (isEsm) { - if (a === Action.CREATE_EMPTY_HARDHAT_CONFIG_ACTION) { - message = a.replace(".js", ".cjs"); - } else if (a === Action.CREATE_TYPESCRIPT_PROJECT_ACTION) { - message = `${a} (not available for ESM projects)`; + choices: Object.values(Action) + .filter((a: Action) => { + if (isEsm && a === Action.CREATE_TYPESCRIPT_VIEM_PROJECT_ACTION) { + // we omit the viem option for ESM projects to avoid showing + // two disabled options + return false; + } + + return true; + }) + .map((a: Action) => { + let message: string; + if (isEsm) { + if (a === Action.CREATE_EMPTY_HARDHAT_CONFIG_ACTION) { + message = a.replace(".js", ".cjs"); + } else if (a === Action.CREATE_TYPESCRIPT_PROJECT_ACTION) { + message = `${a} (not available for ESM projects)`; + } else { + message = a; + } } else { message = a; } - } else { - message = a; - } - - return { - name: a, - message, - value: a, - }; - }), + + return { + name: a, + message, + value: a, + }; + }), }, ]); @@ -588,12 +625,13 @@ async function getDependencies( !(await doesNpmAutoInstallPeerDependencies()); const shouldInstallTypescriptDependencies = - projectType === Action.CREATE_TYPESCRIPT_PROJECT_ACTION; + projectType === Action.CREATE_TYPESCRIPT_PROJECT_ACTION || + projectType === Action.CREATE_TYPESCRIPT_VIEM_PROJECT_ACTION; const shouldInstallTypescriptPeerDependencies = shouldInstallTypescriptDependencies && shouldInstallPeerDependencies; - return { + const commonDependencies: Dependencies = { [HARDHAT_PACKAGE_NAME]: `^${(await getPackageJson()).version}`, ...PROJECT_DEPENDENCIES, ...(shouldInstallPeerDependencies ? PEER_DEPENDENCIES : {}), @@ -602,4 +640,33 @@ async function getDependencies( ? TYPESCRIPT_PEER_DEPENDENCIES : {}), }; + + // At the moment, the default toolbox is the ethers based toolbox + const shouldInstallDefaultToolbox = + projectType !== Action.CREATE_TYPESCRIPT_VIEM_PROJECT_ACTION; + + const ethersToolboxDependencies: Dependencies = { + ...ETHERS_PROJECT_DEPENDENCIES, + ...(shouldInstallPeerDependencies ? ETHERS_PEER_DEPENDENCIES : {}), + ...(shouldInstallTypescriptPeerDependencies + ? TYPESCRIPT_ETHERS_PEER_DEPENDENCIES + : {}), + }; + + const viemToolboxDependencies: Dependencies = { + ...VIEM_PROJECT_DEPENDENCIES, + ...(shouldInstallPeerDependencies ? VIEM_PEER_DEPENDENCIES : {}), + ...(shouldInstallTypescriptPeerDependencies + ? TYPESCRIPT_VIEM_PEER_DEPENDENCIES + : {}), + }; + + const toolboxDependencies: Dependencies = shouldInstallDefaultToolbox + ? ethersToolboxDependencies + : viemToolboxDependencies; + + return { + ...commonDependencies, + ...toolboxDependencies, + }; } diff --git a/packages/hardhat-core/src/types/builtin-tasks/compile.ts b/packages/hardhat-core/src/types/builtin-tasks/compile.ts index 6f1221a3f5..b4a36bb643 100644 --- a/packages/hardhat-core/src/types/builtin-tasks/compile.ts +++ b/packages/hardhat-core/src/types/builtin-tasks/compile.ts @@ -15,6 +15,11 @@ export interface ResolvedFile { getVersionedName(): string; } +export type ArtifactsEmittedPerFile = Array<{ + file: ResolvedFile; + artifactsEmitted: string[]; +}>; + /** * Information about an npm library. */ diff --git a/packages/hardhat-toolbox-viem/.eslintrc.js b/packages/hardhat-toolbox-viem/.eslintrc.js new file mode 100644 index 0000000000..44ed8ed6d5 --- /dev/null +++ b/packages/hardhat-toolbox-viem/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: [`${__dirname}/../../config/eslint/eslintrc.js`], + parserOptions: { + project: `${__dirname}/src/tsconfig.json`, + sourceType: "module", + }, +}; diff --git a/packages/hardhat-toolbox-viem/.gitignore b/packages/hardhat-toolbox-viem/.gitignore new file mode 100644 index 0000000000..5914df8f08 --- /dev/null +++ b/packages/hardhat-toolbox-viem/.gitignore @@ -0,0 +1,102 @@ +# Node modules +/node_modules + +# Compilation output +/build-test/ +/dist +/internal +/*.js +/*.js.map +/*.d.ts +/*.d.ts.map + +!.eslintrc.js + +# Code coverage artifacts +/coverage +/.nyc_output + +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +#node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + diff --git a/packages/hardhat-toolbox-viem/.mocharc.json b/packages/hardhat-toolbox-viem/.mocharc.json new file mode 100644 index 0000000000..d00ceb4138 --- /dev/null +++ b/packages/hardhat-toolbox-viem/.mocharc.json @@ -0,0 +1,5 @@ +{ + "require": "ts-node/register/files", + "ignore": ["test/fixture-projects/**/*"], + "timeout": 10000 +} diff --git a/packages/hardhat-toolbox-viem/.prettierignore b/packages/hardhat-toolbox-viem/.prettierignore new file mode 100644 index 0000000000..1961b8ba50 --- /dev/null +++ b/packages/hardhat-toolbox-viem/.prettierignore @@ -0,0 +1,13 @@ +/node_modules +/dist +/internal +/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 +!.eslintrc.js diff --git a/packages/hardhat-toolbox-viem/CHANGELOG.md b/packages/hardhat-toolbox-viem/CHANGELOG.md new file mode 100644 index 0000000000..0267443b2f --- /dev/null +++ b/packages/hardhat-toolbox-viem/CHANGELOG.md @@ -0,0 +1 @@ +# @nomicfoundation/hardhat-toolbox-viem diff --git a/packages/hardhat-toolbox-viem/LICENSE b/packages/hardhat-toolbox-viem/LICENSE new file mode 100644 index 0000000000..3b7e8c7eab --- /dev/null +++ b/packages/hardhat-toolbox-viem/LICENSE @@ -0,0 +1,21 @@ +MIT License + +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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/hardhat-toolbox-viem/README.md b/packages/hardhat-toolbox-viem/README.md new file mode 100644 index 0000000000..c6ec648044 --- /dev/null +++ b/packages/hardhat-toolbox-viem/README.md @@ -0,0 +1,28 @@ +[![npm](https://img.shields.io/npm/v/@nomicfoundation/hardhat-toolbox-viem.svg)](https://www.npmjs.com/package/@nomicfoundation/hardhat-toolbox-viem) [![hardhat](https://hardhat.org/buidler-plugin-badge.svg?1)](https://hardhat.org) + +# Hardhat Toolbox (Viem based) + +The `@nomicfoundation/hardhat-toolbox-viem` plugin bundles all the commonly used packages and Hardhat plugins we recommend to start developing with Hardhat. + +When you use this plugin, you'll be able to: + +- Deploy and interact with your contracts using [Viem](https://viem.sh/) and the [`hardhat-viem`](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-viem) plugin. +- Test your contracts with [Mocha](https://mochajs.org/), [Chai](https://chaijs.com/) and [Chai as Promised](https://github.com/domenic/chai-as-promised#chai-assertions-for-promises). Note: the plugin Hardhat Chai Matchers is currently not available for Viem. +- 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-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). + +**Note:** you might want to pin Viem-related dependencies because Viem does not strictly follow semantic versioning for type changes. You can read more [here](https://hardhat.org/hardhat-runner/docs/advanced/using-viem#managing-types-and-version-stability). + +### Usage + +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) but select the _Create a TypeScript project (with Viem)_ option instead. + +### 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-viem/package.json b/packages/hardhat-toolbox-viem/package.json new file mode 100644 index 0000000000..b4ba550daa --- /dev/null +++ b/packages/hardhat-toolbox-viem/package.json @@ -0,0 +1,93 @@ +{ + "name": "@nomicfoundation/hardhat-toolbox-viem", + "version": "1.0.0", + "description": "Nomic Foundation's recommended bundle of Hardhat plugins (viem based)", + "repository": "github:nomicfoundation/hardhat", + "homepage": "https://github.com/nomicfoundation/hardhat/tree/main/packages/hardhat-toolbox-viem", + "author": "Nomic Foundation", + "contributors": [ + "Nomic Foundation" + ], + "license": "MIT", + "main": "index.js", + "types": "index.d.ts", + "keywords": [ + "ethereum", + "smart-contracts", + "hardhat", + "hardhat-plugin", + "hardhat-viem" + ], + "scripts": { + "lint": "yarn prettier --check && yarn eslint", + "lint:fix": "yarn prettier --write && yarn eslint --fix", + "eslint": "eslint 'src/**/*.ts' 'test/**/*.ts'", + "prettier": "prettier \"**/*.{js,md,json}\"", + "pretest": "cd ../.. && yarn build", + "test": "mocha --recursive \"test/**/*.ts\" --exit", + "build": "tsc --build .", + "prepublishOnly": "yarn build", + "clean": "rimraf dist *.{d.ts,js}{,.map} build-test tsconfig.tsbuildinfo" + }, + "files": [ + "src/", + "internal/", + "*.d.ts", + "*.d.ts.map", + "*.js", + "*.js.map", + "LICENSE", + "README.md" + ], + "dependencies": { + "chai-as-promised": "^7.1.1" + }, + "devDependencies": { + "@nomicfoundation/hardhat-network-helpers": "^1.0.0", + "@nomicfoundation/hardhat-verify": "^1.0.0", + "@nomicfoundation/hardhat-viem": "^1.0.0", + "@types/chai": "^4.2.0", + "@types/chai-as-promised": "^7.1.6", + "@types/mocha": ">=9.1.0", + "@types/node": "^16.0.0", + "@typescript-eslint/eslint-plugin": "5.61.0", + "@typescript-eslint/parser": "5.61.0", + "chai": "^4.2.0", + "eslint": "^8.44.0", + "eslint-config-prettier": "8.3.0", + "eslint-plugin-import": "2.27.5", + "eslint-plugin-no-only-tests": "3.0.0", + "eslint-plugin-prettier": "3.4.0", + "hardhat": "^2.11.0", + "hardhat-gas-reporter": "^1.0.8", + "mocha": "^10.0.0", + "prettier": "2.4.1", + "rimraf": "^3.0.2", + "solidity-coverage": "^0.8.1", + "ts-node": "^10.8.0", + "typescript": "~5.0.4", + "viem": "^1.15.1" + }, + "peerDependencies": { + "@nomicfoundation/hardhat-network-helpers": "^1.0.0", + "@nomicfoundation/hardhat-verify": "^1.0.0", + "@nomicfoundation/hardhat-viem": "^1.0.0", + "@types/chai": "^4.2.0", + "@types/chai-as-promised": "^7.1.6", + "@types/mocha": ">=9.1.0", + "@types/node": ">=16.0.0", + "chai": "^4.2.0", + "hardhat": "^2.11.0", + "hardhat-gas-reporter": "^1.0.8", + "solidity-coverage": "^0.8.1", + "ts-node": ">=8.0.0", + "typescript": "~5.0.4", + "viem": "^1.15.1" + }, + "bugs": { + "url": "https://github.com/nomicfoundation/hardhat/issues" + }, + "directories": { + "test": "test" + } +} diff --git a/packages/hardhat-toolbox-viem/src/index.ts b/packages/hardhat-toolbox-viem/src/index.ts new file mode 100644 index 0000000000..c3e0f8d0f3 --- /dev/null +++ b/packages/hardhat-toolbox-viem/src/index.ts @@ -0,0 +1,38 @@ +import "@nomicfoundation/hardhat-verify"; +import "@nomicfoundation/hardhat-viem"; +import "hardhat-gas-reporter"; +import "solidity-coverage"; +import "./internal/chai-setup"; + +/** + * If a new official plugin is added, make sure to update: + * - The tsconfig.json file + * - The hardhat-toolbox GitHub workflow + * - The parts of the documentation that install hardhat-toolbox with npm 6 or yarn + * - The list of dependencies that the sample projects install + * - The README + */ + +import { extendConfig } from "hardhat/config"; + +extendConfig((config, userConfig) => { + const configAsAny = config as any; + + // hardhat-gas-reporter doesn't use extendConfig, so + // the values of config.gasReporter and userConfig.gasReporter + // are the same. The userConfigVersion is frozen though, so we + // shouldn't use it. + const gasReporterConfig = + configAsAny.gasReporter as typeof userConfig.gasReporter; + + configAsAny.gasReporter = gasReporterConfig ?? {}; + + if (gasReporterConfig?.enabled === undefined) { + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + configAsAny.gasReporter.enabled = process.env.REPORT_GAS ? true : false; + } + + if (gasReporterConfig?.currency === undefined) { + configAsAny.gasReporter.currency = "USD"; + } +}); diff --git a/packages/hardhat-toolbox-viem/src/internal/chai-setup.ts b/packages/hardhat-toolbox-viem/src/internal/chai-setup.ts new file mode 100644 index 0000000000..c0c8e71856 --- /dev/null +++ b/packages/hardhat-toolbox-viem/src/internal/chai-setup.ts @@ -0,0 +1,4 @@ +import chai from "chai"; +import chaiAsPromised from "chai-as-promised"; + +chai.use(chaiAsPromised); diff --git a/packages/hardhat-toolbox-viem/src/network-helpers.ts b/packages/hardhat-toolbox-viem/src/network-helpers.ts new file mode 100644 index 0000000000..71747baddc --- /dev/null +++ b/packages/hardhat-toolbox-viem/src/network-helpers.ts @@ -0,0 +1 @@ +export * from "@nomicfoundation/hardhat-network-helpers"; diff --git a/packages/hardhat-toolbox-viem/src/tsconfig.json b/packages/hardhat-toolbox-viem/src/tsconfig.json new file mode 100644 index 0000000000..a703f8f42e --- /dev/null +++ b/packages/hardhat-toolbox-viem/src/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../../config/typescript/tsconfig.json", + "compilerOptions": { + "outDir": "../", + "rootDirs": ["."], + "composite": true + }, + "include": ["./**/*.ts"], + "exclude": [], + "references": [ + { + "path": "../../hardhat-core/src" + }, + { + "path": "../../hardhat-network-helpers" + }, + { + "path": "../../hardhat-verify/src" + }, + { + "path": "../../hardhat-viem/src" + } + ] +} diff --git a/packages/hardhat-toolbox-viem/test/.eslintrc.js b/packages/hardhat-toolbox-viem/test/.eslintrc.js new file mode 100644 index 0000000000..757fe8a3ca --- /dev/null +++ b/packages/hardhat-toolbox-viem/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-viem/test/fixture-projects/only-toolbox/hardhat.config.js b/packages/hardhat-toolbox-viem/test/fixture-projects/only-toolbox/hardhat.config.js new file mode 100644 index 0000000000..dfbbe371ca --- /dev/null +++ b/packages/hardhat-toolbox-viem/test/fixture-projects/only-toolbox/hardhat.config.js @@ -0,0 +1 @@ +require("../../../"); diff --git a/packages/hardhat-toolbox-viem/test/fixture-projects/only-toolbox/script.js b/packages/hardhat-toolbox-viem/test/fixture-projects/only-toolbox/script.js new file mode 100644 index 0000000000..7a6ae79dd4 --- /dev/null +++ b/packages/hardhat-toolbox-viem/test/fixture-projects/only-toolbox/script.js @@ -0,0 +1,22 @@ +const assert = require("assert"); +const chai = require("chai"); + +async function main() { + // check that viem exists + assert(viem !== undefined); + + // check that the expected tasks are there + const taskNames = Object.keys(tasks); + assert(taskNames.includes("verify")); + assert(taskNames.includes("coverage")); + + // assert that chai-as-promised is loaded + assert(chai.assert.eventually); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/packages/hardhat-toolbox-viem/test/fixture-projects/with-gas-reporter-config/hardhat.config.js b/packages/hardhat-toolbox-viem/test/fixture-projects/with-gas-reporter-config/hardhat.config.js new file mode 100644 index 0000000000..77263c1734 --- /dev/null +++ b/packages/hardhat-toolbox-viem/test/fixture-projects/with-gas-reporter-config/hardhat.config.js @@ -0,0 +1,7 @@ +require("../../../"); + +module.exports = { + gasReporter: { + enabled: true, + }, +}; diff --git a/packages/hardhat-toolbox-viem/test/helpers.ts b/packages/hardhat-toolbox-viem/test/helpers.ts new file mode 100644 index 0000000000..4fc372b905 --- /dev/null +++ b/packages/hardhat-toolbox-viem/test/helpers.ts @@ -0,0 +1,24 @@ +// 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"; + +declare module "mocha" { + interface Context { + env: HardhatRuntimeEnvironment; + } +} + +export function useEnvironment(fixtureProjectName: string) { + beforeEach("Loading hardhat environment", function () { + process.chdir(path.join(__dirname, "fixture-projects", fixtureProjectName)); + + this.env = require("hardhat"); + }); + + afterEach("Resetting hardhat", function () { + resetHardhatContext(); + }); +} diff --git a/packages/hardhat-toolbox-viem/test/test.ts b/packages/hardhat-toolbox-viem/test/test.ts new file mode 100644 index 0000000000..1b77777598 --- /dev/null +++ b/packages/hardhat-toolbox-viem/test/test.ts @@ -0,0 +1,26 @@ +import { assert } from "chai"; + +import { useEnvironment } from "./helpers"; + +describe("hardhat-toolbox-viem", function () { + describe("only-toolbox", function () { + useEnvironment("only-toolbox"); + + it("has all the expected things in the HRE", async function () { + await this.env.run("run", { + noCompile: true, + script: "script.js", + }); + + assert.equal(process.exitCode, 0); + }); + }); + + describe("hardhat-gas-reporter-config", function () { + useEnvironment("with-gas-reporter-config"); + + it("Should not crash while loading the HRE", async function () { + assert.isDefined(this.env); + }); + }); +}); diff --git a/packages/hardhat-toolbox-viem/tsconfig.json b/packages/hardhat-toolbox-viem/tsconfig.json new file mode 100644 index 0000000000..65ecfc33b9 --- /dev/null +++ b/packages/hardhat-toolbox-viem/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../config/typescript/tsconfig.json", + "compilerOptions": { + "outDir": "./build-test", + "rootDirs": ["./test"], + "composite": true + }, + "include": ["./test/**/*.ts"], + "exclude": ["./node_modules", "./test/**/hardhat.config.ts"], + "references": [ + { + "path": "./src" + } + ] +} diff --git a/packages/hardhat-toolbox/package.json b/packages/hardhat-toolbox/package.json index ae9b9ccca9..81bb41fd73 100644 --- a/packages/hardhat-toolbox/package.json +++ b/packages/hardhat-toolbox/package.json @@ -1,7 +1,7 @@ { "name": "@nomicfoundation/hardhat-toolbox", "version": "3.0.0", - "description": "Nomic Foundation's recommended bundle of Hardhat plugins", + "description": "Nomic Foundation's recommended bundle of Hardhat plugins (ethers based)", "repository": "github:nomicfoundation/hardhat", "homepage": "https://github.com/nomicfoundation/hardhat/tree/main/packages/hardhat-toolbox", "author": "Nomic Foundation", @@ -15,7 +15,8 @@ "ethereum", "smart-contracts", "hardhat", - "hardhat-plugin" + "hardhat-plugin", + "hardhat-ethers" ], "scripts": { "lint": "yarn prettier --check && yarn eslint", diff --git a/packages/hardhat-toolbox/src/tsconfig.json b/packages/hardhat-toolbox/src/tsconfig.json index 3c04a1ea5c..9f84761f8b 100644 --- a/packages/hardhat-toolbox/src/tsconfig.json +++ b/packages/hardhat-toolbox/src/tsconfig.json @@ -21,7 +21,7 @@ "path": "../../hardhat-ethers/src" }, { - "path": "../../hardhat-verify" + "path": "../../hardhat-verify/src" } ] } diff --git a/packages/hardhat-viem/.eslintignore b/packages/hardhat-viem/.eslintignore new file mode 100644 index 0000000000..54bbdb1232 --- /dev/null +++ b/packages/hardhat-viem/.eslintignore @@ -0,0 +1,8 @@ +.eslintrc.js + +*.d.ts +index.js + +/build-test +/internal +/test/fixture-projects diff --git a/packages/hardhat-viem/.eslintrc.js b/packages/hardhat-viem/.eslintrc.js new file mode 100644 index 0000000000..13139c79c6 --- /dev/null +++ b/packages/hardhat-viem/.eslintrc.js @@ -0,0 +1,24 @@ +const { + slowImportsCommonIgnoredModules, +} = require("../../config/eslint/constants"); + +module.exports = { + extends: [`${__dirname}/../../config/eslint/eslintrc.js`], + parserOptions: { + project: `${__dirname}/src/tsconfig.json`, + sourceType: "module", + }, + overrides: [ + { + files: ["src/index.ts"], + rules: { + "@nomicfoundation/slow-imports/no-top-level-external-import": [ + "error", + { + ignoreModules: [...slowImportsCommonIgnoredModules], + }, + ], + }, + }, + ], +}; diff --git a/packages/hardhat-viem/.gitignore b/packages/hardhat-viem/.gitignore new file mode 100644 index 0000000000..aed847320f --- /dev/null +++ b/packages/hardhat-viem/.gitignore @@ -0,0 +1,108 @@ +# Node modules +/node_modules + +# Compilation output +/build-test/ +/dist + +# Code coverage artifacts +/coverage +/.nyc_output + +/*.js +!/.eslintrc.js +/*.js.map +/*.d.ts +/*.d.ts.map +/builtin-tasks +/common +/internal +/types +/utils + +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +#node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +test/fixture-projects/*/artifacts +test/fixture-projects/*/cache diff --git a/packages/hardhat-viem/.mocharc.json b/packages/hardhat-viem/.mocharc.json new file mode 100644 index 0000000000..cc5f5768f7 --- /dev/null +++ b/packages/hardhat-viem/.mocharc.json @@ -0,0 +1,6 @@ +{ + "require": "ts-node/register/files", + "file": "./test/setup.ts", + "ignore": ["test/fixture-projects/**/*", "test/update-snapshots.ts"], + "timeout": 10000 +} diff --git a/packages/hardhat-viem/.prettierignore b/packages/hardhat-viem/.prettierignore new file mode 100644 index 0000000000..4c9a81a3da --- /dev/null +++ b/packages/hardhat-viem/.prettierignore @@ -0,0 +1,12 @@ +/node_modules +/internal +/*.d.ts +/*.d.ts.map +/*.js +/*.js.map +/build-test +/test/fixture-projects/**/artifacts +/test/fixture-projects/**/cache +/test/fixture-projects/**/snapshots +CHANGELOG.md +.nyc_output diff --git a/packages/hardhat-viem/LICENSE b/packages/hardhat-viem/LICENSE new file mode 100644 index 0000000000..3b7e8c7eab --- /dev/null +++ b/packages/hardhat-viem/LICENSE @@ -0,0 +1,21 @@ +MIT License + +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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/hardhat-viem/README.md b/packages/hardhat-viem/README.md new file mode 100644 index 0000000000..f2bfc30d51 --- /dev/null +++ b/packages/hardhat-viem/README.md @@ -0,0 +1,237 @@ +[![npm](https://img.shields.io/npm/v/@nomicfoundation/hardhat-viem.svg)](https://www.npmjs.com/package/@nomicfoundation/hardhat-viem) [![hardhat](https://hardhat.org/buidler-plugin-badge.svg?1)](https://hardhat.org) + +# hardhat-viem + +[Hardhat](https://hardhat.org) plugin for integration with [Viem](https://github.com/wagmi-dev/viem), a lightweight, composable, and type-safe Ethereum library. + +## What + +This plugin integrates the Viem Ethereum library into your Hardhat development environment. Viem is an alternative to [ethers.js](https://docs.ethers.io/) that offers developers a different way to interact with the Ethereum blockchain. + +By installing and configuring `hardhat-viem`, you gain access to the capabilities of the Viem library directly within your Hardhat projects. This integration enables you to perform various Ethereum-related tasks using Viem's features and functionalities. + +Note: This plugin relies on the Viem library, so familiarity with [Viem's documentation](https://viem.sh/docs/getting-started.html) can enhance your experience when working with `hardhat-viem`. + +## Installation + +```bash +npm install --save-dev @nomicfoundation/hardhat-viem viem +``` + +And add the following statement to your `hardhat.config.js`: + +```js +require("@nomicfoundation/hardhat-viem"); +``` + +Or, if you are using TypeScript, add this to your `hardhat.config.ts`: + +```js +import "@nomicfoundation/hardhat-viem"; +``` + +**Note:** you might want to pin Viem-related dependencies because Viem does not strictly follow semantic versioning for type changes. You can read more [here](https://hardhat.org/hardhat-runner/docs/advanced/using-viem#managing-types-and-version-stability). + +## Required plugins + +No plugins dependencies. + +## Tasks + +This plugin creates no additional tasks. + +## Environment extensions + +This plugins adds a `viem` object to the Hardhat Runtime Environment which provides a minimal set of capabilities for interacting with the blockchain. + +### Clients + +Viem supports three types of clients: + +#### Public client + +A Public Client is an interface to "public" JSON-RPC API methods such as retrieving block numbers, transactions, reading from smart contracts, etc through [Public Actions](https://viem.sh/docs/actions/public/introduction.html). + +```typescript +import hre from "hardhat"; + +const publicClient = await hre.viem.getPublicClient(); + +const blockNumber = await publicClient.getBlockNumber(); + +const balance = await publicClient.getBalance({ + address: "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", +}); +``` + +#### Wallet client + +A Wallet Client is an interface to interact with Ethereum Accounts and provides the ability to retrieve accounts, execute transactions, sign messages, etc. through [Wallet Actions](https://viem.sh/docs/actions/wallet/introduction.html). + +```typescript +import hre from "hardhat"; + +const [fromWalletClient, toWalletClient] = await hre.viem.getWalletClients(); + +const hash = await fromWalletClient.sendTransaction({ + to: toWalletClient.account.address, + value: parseEther("0.0001"), +}); +``` + +```typescript +import hre from "hardhat"; + +const walletClient = await hre.viem.getWalletClient( + "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e" +); + +const signature = await walletClient.signMessage({ + account, + message: "hello world", +}); +``` + +#### Test client + +A Test Client is an interface to "test" JSON-RPC API methods such as mining blocks, impersonating accounts, setting fees, etc. through [Test Actions](https://viem.sh/docs/actions/test/introduction.html). + +```typescript +import hre from "hardhat"; + +const testClient = await hre.viem.getTestClient(); + +await testClient.mine({ + blocks: 1000000, +}); +``` + +#### Client options + +You can pass options to the `getPublicClient`, `getWalletClient`, and `getTestClient` methods to customize the client's behavior. + +```typescript +import hre from "hardhat"; + +const publicClient = await hre.viem.getPublicClient({ + pollingInterval: 1000, + cacheTime: 2000, +}); +``` + +For a complete list of options, see: + +- [Public client parameters](https://viem.sh/docs/clients/public.html#parameters) +- [Wallet client parameters](https://viem.sh/docs/clients/wallet.html#parameters) +- [Test client parameters](https://viem.sh/docs/clients/test.html#parameters) + +### Contracts + +The `viem` object provides convenient methods for deploying and interacting with smart contracts on the blockchain. + +#### Deploying a Contract + +To deploy a contract to the blockchain, use the `deployContract` method: + +```typescript +import hre from "hardhat"; + +const contract = await hre.viem.deployContract("contractName", [ + "arg1", + 50, + "arg3", +]); +``` + +By default, the first wallet client retrieved by `hre.viem.getWalletClients()` is used to deploy the contract. You can also specify a different wallet client by passing a third parameter, along with other properties, such as `gas` and `value`: + +```typescript +import hre from "hardhat"; + +const [_, secondWalletClient] = await hre.viem.getWalletClients(); + +const contractA = await hre.viem.deployContract( + "contractName", + ["arg1", 50, "arg3"], + { + walletClient: secondWalletClient, + gas: 1000000, + value: parseEther("0.0001"), + confirmations: 5, // 1 by default + } +); +``` + +#### Retrieving an Existing Contract + +If the contract is already deployed, you can retrieve an instance of it using the `getContractAt` method: + +```typescript +import hre from "hardhat"; + +const contract = await hre.viem.getContractAt( + "contractName", + "0x1234567890123456789012345678901234567890" +); +``` + +By default, the first wallet client retrieved by `hre.viem.getWalletClients()` will be used for interacting with the contract. If you want to specify a different wallet client, you can do so by passing it as a third parameter, just like when deploying a contract: + +```typescript +import hre from "hardhat"; + +const [_, secondWalletClient] = await hre.viem.getWalletClients(); + +const contract = await hre.viem.getContractAt( + "contractName", + "0x1234567890123456789012345678901234567890", + { walletClient: secondWalletClient } +); +``` + +#### Interacting with Contracts + +Once you have an instance of a contract, you can interact with it by calling its methods: + +```typescript +let response = await contract.read.method1(); +await contract.write.method2([10, "arg2"]); +``` + +##### Send deployment transaction + +By default, the `deployContract` method sends a deployment transaction to the blockchain and waits for the transaction to be mined. If you want to send the transaction without waiting for it to be mined, you can do so by using `sendDeploymentTransaction`: + +```typescript +import hre from "hardhat"; + +const { contract: contractName, deploymentTransaction } = + await hre.viem.sendDeploymentTransaction( + "contractName", + ["arg1", 50, "arg3"], + { + walletClient: secondWalletClient, + gas: 1000000, + value: parseEther("0.0001"), + } + ); +``` + +Then, if you want to wait for the transaction to be mined, you can do: + +```typescript +import hre from "hardhat"; + +const publicClient = await hre.viem.getPublicClient(); +const { contractAddress } = await publicClient.waitForTransactionReceipt({ + hash: deploymentTransaction.hash, +}); +``` + +## Usage + +There are no additional steps you need to take for this plugin to work. + +Install it and access Viem through the Hardhat Runtime Environment anywhere you need it (tasks, scripts, tests, etc). + +Read the documentation on the [Hardhat Runtime Environment](https://hardhat.org/hardhat-runner/docs/advanced/hardhat-runtime-environment) to learn how to access the HRE in different ways to use Viem from anywhere the HRE is accessible. diff --git a/packages/hardhat-viem/package.json b/packages/hardhat-viem/package.json new file mode 100644 index 0000000000..6ad09f94c0 --- /dev/null +++ b/packages/hardhat-viem/package.json @@ -0,0 +1,75 @@ +{ + "name": "@nomicfoundation/hardhat-viem", + "version": "1.0.0", + "description": "Hardhat plugin for viem", + "homepage": "https://github.com/nomicfoundation/hardhat/tree/main/packages/hardhat-viem", + "repository": "github:nomicfoundation/hardhat", + "author": "Nomic Foundation", + "license": "MIT", + "main": "index.js", + "types": "index.d.ts", + "keywords": [ + "ethereum", + "smart-contracts", + "hardhat", + "hardhat-plugin", + "viem" + ], + "scripts": { + "lint": "yarn prettier --check && yarn eslint", + "lint:fix": "yarn prettier --write && yarn eslint --fix", + "eslint": "eslint 'src/**/*.ts' 'test/**/*.ts'", + "prettier": "prettier \"**/*.{js,md,json}\"", + "pretest": "cd ../.. && yarn build", + "test": "mocha --recursive \"test/**/*.ts\" -c --exit", + "snapshots:update": "ts-node test/update-snapshots.ts", + "coverage": "nyc yarn test -- --reporter min", + "build": "tsc --build .", + "prepublishOnly": "yarn build", + "clean": "rimraf internal *.{d.ts,js}{,.map} build-test tsconfig.tsbuildinfo" + }, + "files": [ + "src/", + "internal/", + "*.d.ts", + "*.d.ts.map", + "*.js", + "*.js.map", + "LICENSE", + "README.md" + ], + "devDependencies": { + "@types/chai": "^4.2.0", + "@types/chai-as-promised": "^7.1.3", + "@types/lodash.memoize": "^4.1.7", + "@types/mocha": ">=9.1.0", + "@types/node": "^16.0.0", + "@types/sinon": "^9.0.8", + "@typescript-eslint/eslint-plugin": "5.61.0", + "@typescript-eslint/parser": "5.61.0", + "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", + "eslint": "^8.44.0", + "eslint-config-prettier": "8.3.0", + "eslint-plugin-import": "2.27.5", + "eslint-plugin-no-only-tests": "3.0.0", + "eslint-plugin-prettier": "3.4.0", + "hardhat": "^2.17.0", + "jest-diff": "^29.7.0", + "mocha": "^10.0.0", + "prettier": "2.4.1", + "sinon": "^9.0.0", + "ts-node": "^10.8.0", + "typescript": "~5.0.0", + "viem": "^1.15.1" + }, + "peerDependencies": { + "hardhat": "^2.17.0", + "typescript": "~5.0.0", + "viem": "^1.15.1" + }, + "dependencies": { + "abitype": "^0.9.8", + "lodash.memoize": "^4.1.2" + } +} diff --git a/packages/hardhat-viem/src/index.ts b/packages/hardhat-viem/src/index.ts new file mode 100644 index 0000000000..d7b1f9f45f --- /dev/null +++ b/packages/hardhat-viem/src/index.ts @@ -0,0 +1,42 @@ +import { extendEnvironment } from "hardhat/config"; + +import { + getPublicClient, + getWalletClients, + getWalletClient, + getTestClient, +} from "./internal/clients"; +import { + deployContract, + sendDeploymentTransaction, + getContractAt, +} from "./internal/contracts"; +import "./internal/type-extensions"; +import "./internal/tasks"; + +extendEnvironment((hre) => { + const { provider } = hre.network; + + hre.viem = { + getPublicClient: (publicClientConfig) => + getPublicClient(provider, publicClientConfig), + + getWalletClients: (walletClientConfig) => + getWalletClients(provider, walletClientConfig), + + getWalletClient: (address, walletClientConfig) => + getWalletClient(provider, address, walletClientConfig), + + getTestClient: (testClientConfig) => + getTestClient(provider, testClientConfig), + + deployContract: (contractName, constructorArgs, config) => + deployContract(hre, contractName, constructorArgs, config), + + sendDeploymentTransaction: (contractName, constructorArgs, config) => + sendDeploymentTransaction(hre, contractName, constructorArgs, config), + + getContractAt: (contractName, address, config) => + getContractAt(hre, contractName, address, config), + }; +}); diff --git a/packages/hardhat-viem/src/internal/accounts.ts b/packages/hardhat-viem/src/internal/accounts.ts new file mode 100644 index 0000000000..479cd4942e --- /dev/null +++ b/packages/hardhat-viem/src/internal/accounts.ts @@ -0,0 +1,9 @@ +import type { EthereumProvider } from "hardhat/types"; +import type { Address } from "viem"; + +import memoize from "lodash.memoize"; + +export const getAccounts = memoize( + async (provider: EthereumProvider): Promise => + provider.send("eth_accounts") +); diff --git a/packages/hardhat-viem/src/internal/chains.ts b/packages/hardhat-viem/src/internal/chains.ts new file mode 100644 index 0000000000..b695428206 --- /dev/null +++ b/packages/hardhat-viem/src/internal/chains.ts @@ -0,0 +1,91 @@ +import type { EthereumProvider } from "hardhat/types"; +import type { Chain } from "viem"; +import type { TestClientMode } from "../types"; + +import memoize from "lodash.memoize"; + +import { + UnknownDevelopmentNetworkError, + NetworkNotFoundError, + MultipleMatchingNetworksError, +} from "./errors"; + +export async function getChain(provider: EthereumProvider): Promise { + const chains: Record = require("viem/chains"); + const chainId = await getChainId(provider); + + if (isDevelopmentNetwork(chainId)) { + if (await isHardhatNetwork(provider)) { + return chains.hardhat; + } else if (await isFoundryNetwork(provider)) { + return chains.foundry; + } else { + throw new UnknownDevelopmentNetworkError(); + } + } + + const matchingChains = Object.values(chains).filter( + ({ id }) => id === chainId + ); + + if (matchingChains.length === 0) { + if (await isHardhatNetwork(provider)) { + return chains.hardhat; + } else if (await isFoundryNetwork(provider)) { + return chains.foundry; + } else { + throw new NetworkNotFoundError(chainId); + } + } + + if (matchingChains.length > 1) { + throw new MultipleMatchingNetworksError(chainId); + } + + return matchingChains[0]; +} + +export function isDevelopmentNetwork(chainId: number) { + return chainId === 31337; +} + +export async function getMode( + provider: EthereumProvider +): Promise { + if (await isHardhatNetwork(provider)) { + return "hardhat"; + } else if (await isFoundryNetwork(provider)) { + return "anvil"; + } else { + throw new UnknownDevelopmentNetworkError(); + } +} + +const getChainId = memoize(async (provider: EthereumProvider) => + Number(await provider.send("eth_chainId")) +); + +const isHardhatNetwork = memoize(async (provider: EthereumProvider) => + detectNetworkByMethodName(provider, NetworkMethod.HARDHAT_METADATA) +); + +const isFoundryNetwork = memoize(async (provider: EthereumProvider) => + detectNetworkByMethodName(provider, NetworkMethod.ANVIL_NODE_INFO) +); + +enum NetworkMethod { + HARDHAT_METADATA = "hardhat_metadata", + ANVIL_NODE_INFO = "anvil_nodeInfo", +} + +async function detectNetworkByMethodName( + provider: EthereumProvider, + methodName: string +) { + try { + await provider.send(methodName); + return true; + } catch { + return false; + } +} diff --git a/packages/hardhat-viem/src/internal/clients.ts b/packages/hardhat-viem/src/internal/clients.ts new file mode 100644 index 0000000000..2fef4a7fcb --- /dev/null +++ b/packages/hardhat-viem/src/internal/clients.ts @@ -0,0 +1,152 @@ +import type { EthereumProvider } from "hardhat/types"; +import type { + Address, + Chain, + PublicClientConfig, + WalletClientConfig, + TestClientConfig, +} from "viem"; +import type { + PublicClient, + TestClient, + TestClientMode, + WalletClient, +} from "../types"; + +import { getChain, getMode, isDevelopmentNetwork } from "./chains"; +import { getAccounts } from "./accounts"; + +/** + * Get a PublicClient instance. This is a read-only client that can be used to + * query the blockchain. + * + * @param provider The Ethereum provider used to connect to the blockchain. + * @param publicClientConfig Optional configuration for the PublicClient instance. See the viem documentation for more information. + * @returns A PublicClient instance. + */ +export async function getPublicClient( + provider: EthereumProvider, + publicClientConfig?: Partial +): Promise { + const chain = publicClientConfig?.chain ?? (await getChain(provider)); + return innerGetPublicClient(provider, chain, publicClientConfig); +} + +export async function innerGetPublicClient( + provider: EthereumProvider, + chain: Chain, + publicClientConfig?: Partial +): Promise { + const viem = await import("viem"); + const defaultParameters = isDevelopmentNetwork(chain.id) + ? { pollingInterval: 50, cacheTime: 0 } + : {}; + const parameters = { ...defaultParameters, ...publicClientConfig }; + + const publicClient = viem.createPublicClient({ + chain, + transport: viem.custom(provider), + ...parameters, + }); + + return publicClient; +} + +/** + * Get a list of WalletClient instances. These are read-write clients that can + * be used to send transactions to the blockchain. Each client is associated + * with a an account obtained from the provider using `eth_accounts`. + * + * @param provider The Ethereum provider used to connect to the blockchain. + * @param walletClientConfig Optional configuration for the WalletClient instances. See the viem documentation for more information. + * @returns A list of WalletClient instances. + */ +export async function getWalletClients( + provider: EthereumProvider, + walletClientConfig?: Partial +): Promise { + const chain = walletClientConfig?.chain ?? (await getChain(provider)); + const accounts = await getAccounts(provider); + return innerGetWalletClients(provider, chain, accounts, walletClientConfig); +} + +export async function innerGetWalletClients( + provider: EthereumProvider, + chain: Chain, + accounts: Address[], + walletClientConfig?: Partial +): Promise { + const viem = await import("viem"); + const defaultParameters = isDevelopmentNetwork(chain.id) + ? { pollingInterval: 50, cacheTime: 0 } + : {}; + const parameters = { ...defaultParameters, ...walletClientConfig }; + + const walletClients = accounts.map((account) => + viem.createWalletClient({ + chain, + account, + transport: viem.custom(provider), + ...parameters, + }) + ); + + return walletClients; +} + +/** + * Get a WalletClient instance for a specific address. This is a read-write + * client that can be used to send transactions to the blockchain. + * + * @param provider The Ethereum provider used to connect to the blockchain. + * @param address The public address of the account to use. + * @param walletClientConfig Optional configuration for the WalletClient instance. See the viem documentation for more information. + * @returns A WalletClient instance. + */ +export async function getWalletClient( + provider: EthereumProvider, + address: Address, + walletClientConfig?: Partial +): Promise { + const chain = walletClientConfig?.chain ?? (await getChain(provider)); + return ( + await innerGetWalletClients(provider, chain, [address], walletClientConfig) + )[0]; +} + +/** + * Get a TestClient instance. This is a read-write client that can be used to + * perform actions only available on test nodes such as hardhat or anvil. + * + * @param provider The Ethereum provider used to connect to the blockchain. + * @param testClientConfig Optional configuration for the TestClient instance. See the viem documentation for more information. + * @returns A TestClient instance. + */ +export async function getTestClient( + provider: EthereumProvider, + testClientConfig?: Partial +): Promise { + const chain = testClientConfig?.chain ?? (await getChain(provider)); + const mode = await getMode(provider); + return innerGetTestClient(provider, chain, mode, testClientConfig); +} + +export async function innerGetTestClient( + provider: EthereumProvider, + chain: Chain, + mode: TestClientMode, + testClientConfig?: Partial +): Promise { + const viem = await import("viem"); + const defaultParameters = { pollingInterval: 50, cacheTime: 0 }; + const parameters = { ...defaultParameters, ...testClientConfig }; + + const testClient = viem.createTestClient({ + mode, + chain, + transport: viem.custom(provider), + ...parameters, + }); + + return testClient; +} diff --git a/packages/hardhat-viem/src/internal/contracts.ts b/packages/hardhat-viem/src/internal/contracts.ts new file mode 100644 index 0000000000..25f58d8943 --- /dev/null +++ b/packages/hardhat-viem/src/internal/contracts.ts @@ -0,0 +1,243 @@ +import type { + EthereumProvider, + HardhatRuntimeEnvironment, +} from "hardhat/types"; +import type { Abi, Address, Hex } from "viem"; +import type { + DeployContractConfig, + GetContractAtConfig, + GetContractReturnType, + GetTransactionReturnType, + PublicClient, + SendDeploymentTransactionConfig, + WalletClient, +} from "../types"; + +import { getPublicClient, getWalletClients } from "./clients"; +import { + DefaultWalletClientNotFoundError, + DeployContractError, + HardhatViemError, + InvalidConfirmationsError, +} from "./errors"; + +export async function deployContract( + { artifacts, network }: HardhatRuntimeEnvironment, + contractName: string, + constructorArgs: any[] = [], + config: DeployContractConfig = {} +): Promise { + const { + walletClient: configWalletClient, + confirmations, + ...deployContractParameters + } = config; + const [publicClient, walletClient, contractArtifact] = await Promise.all([ + getPublicClient(network.provider), + configWalletClient ?? + getDefaultWalletClient(network.provider, network.name), + artifacts.readArtifact(contractName), + ]); + + return innerDeployContract( + publicClient, + walletClient, + contractArtifact.abi, + contractArtifact.bytecode as Hex, + constructorArgs, + deployContractParameters, + confirmations + ); +} + +export async function innerDeployContract( + publicClient: PublicClient, + walletClient: WalletClient, + contractAbi: Abi, + contractBytecode: Hex, + constructorArgs: any[], + deployContractParameters: DeployContractConfig = {}, + confirmations: number = 1 +): Promise { + let deploymentTxHash: Hex; + // If gasPrice is defined, then maxFeePerGas and maxPriorityFeePerGas + // must be undefined because it's a legaxy tx. + if (deployContractParameters.gasPrice !== undefined) { + deploymentTxHash = await walletClient.deployContract({ + abi: contractAbi, + bytecode: contractBytecode, + args: constructorArgs, + ...deployContractParameters, + maxFeePerGas: undefined, + maxPriorityFeePerGas: undefined, + }); + } else { + deploymentTxHash = await walletClient.deployContract({ + abi: contractAbi, + bytecode: contractBytecode, + args: constructorArgs, + ...deployContractParameters, + gasPrice: undefined, + }); + } + + if (confirmations < 0) { + throw new HardhatViemError("Confirmations must be greater than 0."); + } + if (confirmations === 0) { + throw new InvalidConfirmationsError(); + } + + const { contractAddress } = await publicClient.waitForTransactionReceipt({ + hash: deploymentTxHash, + confirmations, + }); + + if (contractAddress === null) { + const transaction = await publicClient.getTransaction({ + hash: deploymentTxHash, + }); + throw new DeployContractError(deploymentTxHash, transaction.blockNumber); + } + + const contract = await innerGetContractAt( + publicClient, + walletClient, + contractAbi, + contractAddress + ); + + return contract; +} + +export async function sendDeploymentTransaction( + { artifacts, network }: HardhatRuntimeEnvironment, + contractName: string, + constructorArgs: any[] = [], + config: SendDeploymentTransactionConfig = {} +): Promise<{ + contract: GetContractReturnType; + deploymentTransaction: GetTransactionReturnType; +}> { + const { walletClient: configWalletClient, ...deployContractParameters } = + config; + const [publicClient, walletClient, contractArtifact] = await Promise.all([ + getPublicClient(network.provider), + configWalletClient ?? + getDefaultWalletClient(network.provider, network.name), + artifacts.readArtifact(contractName), + ]); + + return innerSendDeploymentTransaction( + publicClient, + walletClient, + contractArtifact.abi, + contractArtifact.bytecode as Hex, + constructorArgs, + deployContractParameters + ); +} + +async function innerSendDeploymentTransaction( + publicClient: PublicClient, + walletClient: WalletClient, + contractAbi: Abi, + contractBytecode: Hex, + constructorArgs: any[], + deployContractParameters: SendDeploymentTransactionConfig = {} +): Promise<{ + contract: GetContractReturnType; + deploymentTransaction: GetTransactionReturnType; +}> { + let deploymentTxHash: Hex; + // If gasPrice is defined, then maxFeePerGas and maxPriorityFeePerGas + // must be undefined because it's a legaxy tx. + if (deployContractParameters.gasPrice !== undefined) { + deploymentTxHash = await walletClient.deployContract({ + abi: contractAbi, + bytecode: contractBytecode, + args: constructorArgs, + ...deployContractParameters, + maxFeePerGas: undefined, + maxPriorityFeePerGas: undefined, + }); + } else { + deploymentTxHash = await walletClient.deployContract({ + abi: contractAbi, + bytecode: contractBytecode, + args: constructorArgs, + ...deployContractParameters, + gasPrice: undefined, + }); + } + + const deploymentTx = await publicClient.getTransaction({ + hash: deploymentTxHash, + }); + + const { getContractAddress } = await import("viem"); + const contractAddress = getContractAddress({ + from: walletClient.account.address, + nonce: BigInt(deploymentTx.nonce), + }); + + const contract = await innerGetContractAt( + publicClient, + walletClient, + contractAbi, + contractAddress + ); + + return { contract, deploymentTransaction: deploymentTx }; +} + +export async function getContractAt( + { artifacts, network }: HardhatRuntimeEnvironment, + contractName: string, + address: Address, + config: GetContractAtConfig = {} +): Promise { + const [publicClient, walletClient, contractArtifact] = await Promise.all([ + getPublicClient(network.provider), + config.walletClient ?? + getDefaultWalletClient(network.provider, network.name), + artifacts.readArtifact(contractName), + ]); + + return innerGetContractAt( + publicClient, + walletClient, + contractArtifact.abi, + address + ); +} + +async function innerGetContractAt( + publicClient: PublicClient, + walletClient: WalletClient, + contractAbi: Abi, + address: Address +): Promise { + const viem = await import("viem"); + const contract = viem.getContract({ + address, + publicClient, + walletClient, + abi: contractAbi, + }); + + return contract; +} + +async function getDefaultWalletClient( + provider: EthereumProvider, + networkName: string +): Promise { + const [defaultWalletClient] = await getWalletClients(provider); + + if (defaultWalletClient === undefined) { + throw new DefaultWalletClientNotFoundError(networkName); + } + + return defaultWalletClient; +} diff --git a/packages/hardhat-viem/src/internal/errors.ts b/packages/hardhat-viem/src/internal/errors.ts new file mode 100644 index 0000000000..324c6a8b53 --- /dev/null +++ b/packages/hardhat-viem/src/internal/errors.ts @@ -0,0 +1,76 @@ +import { NomicLabsHardhatPluginError } from "hardhat/plugins"; + +export class HardhatViemError extends NomicLabsHardhatPluginError { + constructor(message: string, parent?: Error) { + super("@nomicfoundation/hardhat-viem", message, parent); + } +} + +export class UnknownDevelopmentNetworkError extends HardhatViemError { + constructor() { + super(`The chain id corresponds to a development network but we couldn't detect which one. +Please report this issue if you're using Hardhat or Foundry.`); + } +} + +export class NetworkNotFoundError extends HardhatViemError { + constructor(chainId: number) { + super( + `No network with chain id ${chainId} found. You can override the chain by passing it as a parameter to the client getter: + +import { someChain } from "viem/chains"; +const client = await hre.viem.getPublicClient({ + chain: someChain, + ... +}); + +You can find a list of supported networks here: https://viem.sh/docs/clients/chains.html` + ); + } +} + +export class MultipleMatchingNetworksError extends HardhatViemError { + constructor(chainId: number) { + super( + `Multiple networks with chain id ${chainId} found. You can override the chain by passing it as a parameter to the client getter: + +import { someChain } from "viem/chains"; +const client = await hre.viem.getPublicClient({ + chain: someChain, + ... +}); + +You can find a list of supported networks here: https://viem.sh/docs/clients/chains.html` + ); + } +} + +export class DefaultWalletClientNotFoundError extends HardhatViemError { + constructor(networkName: string) { + super( + `Default wallet client not found. This can happen if no accounts were configured for this network (network: '${networkName}'). + +Alternatively, you can set a custom wallet client by passing it as a parameter in the deployContract function: + +const walletClient = await hre.viem.getWalletClient(address); +const contractA = await hre.viem.deployContract("A", [], { walletClient }); +const contractB = await hre.viem.getContractAt("B", address, { walletClient });` + ); + } +} + +export class InvalidConfirmationsError extends HardhatViemError { + constructor() { + super( + "deployContract does not support 0 confirmations. Use sendDeploymentTransaction if you want to handle the deployment transaction yourself." + ); + } +} + +export class DeployContractError extends HardhatViemError { + constructor(txHash: string, blockNumber: bigint) { + super( + `The deployment transaction '${txHash}' was mined in block '${blockNumber}' but its receipt doesn't contain a contract address` + ); + } +} diff --git a/packages/hardhat-viem/src/internal/tasks.ts b/packages/hardhat-viem/src/internal/tasks.ts new file mode 100644 index 0000000000..7e053a051b --- /dev/null +++ b/packages/hardhat-viem/src/internal/tasks.ts @@ -0,0 +1,319 @@ +import type { Artifact, Artifacts } from "hardhat/types"; +import type { ArtifactsEmittedPerFile } from "hardhat/types/builtin-tasks"; + +import { join, dirname, relative } from "path"; +import { mkdir, writeFile, rm } from "fs/promises"; + +import { subtask } from "hardhat/config"; +import { + TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS, + TASK_COMPILE_SOLIDITY, + TASK_COMPILE_REMOVE_OBSOLETE_ARTIFACTS, +} from "hardhat/builtin-tasks/task-names"; +import { + getFullyQualifiedName, + parseFullyQualifiedName, +} from "hardhat/utils/contract-names"; +import { getAllFilesMatching } from "hardhat/internal/util/fs-utils"; +import { replaceBackslashes } from "hardhat/utils/source-names"; + +interface EmittedArtifacts { + artifactsEmittedPerFile: ArtifactsEmittedPerFile; +} + +/** + * Override task that generates an `artifacts.d.ts` file with `never` + * types for duplicate contract names. This file is used in conjunction with + * the `artifacts.d.ts` file inside each contract directory to type + * `hre.artifacts`. + */ +subtask(TASK_COMPILE_SOLIDITY).setAction( + async (_, { config, artifacts }, runSuper) => { + const superRes = await runSuper(); + + const duplicateContractNames = await findDuplicateContractNames(artifacts); + + const duplicateArtifactsDTs = generateDuplicateArtifactsDefinition( + duplicateContractNames + ); + + try { + await writeFile( + join(config.paths.artifacts, "artifacts.d.ts"), + duplicateArtifactsDTs + ); + } catch (error) { + console.error("Error writing artifacts definition:", error); + } + + return superRes; + } +); + +/** + * Override task to emit TypeScript and definition files for each contract. + * Generates a `.d.ts` file per contract, and a `artifacts.d.ts` per solidity + * file, which is used in conjunction to the root `artifacts.d.ts` + * to type `hre.artifacts`. + */ +subtask(TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS).setAction( + async (_, { artifacts, config }, runSuper): Promise => { + const { artifactsEmittedPerFile }: EmittedArtifacts = await runSuper(); + const duplicateContractNames = await findDuplicateContractNames(artifacts); + + await Promise.all( + artifactsEmittedPerFile.map(async ({ file, artifactsEmitted }) => { + const srcDir = join(config.paths.artifacts, file.sourceName); + await mkdir(srcDir, { + recursive: true, + }); + + const contractTypeData = await Promise.all( + artifactsEmitted.map(async (contractName) => { + const fqn = getFullyQualifiedName(file.sourceName, contractName); + const artifact = await artifacts.readArtifact(fqn); + const isDuplicate = duplicateContractNames.has(contractName); + const declaration = generateContractDeclaration( + artifact, + isDuplicate + ); + + const typeName = `${contractName}$Type`; + + return { contractName, fqn, typeName, declaration }; + }) + ); + + const fp: Array> = []; + for (const { contractName, declaration } of contractTypeData) { + fp.push(writeFile(join(srcDir, `${contractName}.d.ts`), declaration)); + } + + const dTs = generateArtifactsDefinition(contractTypeData); + fp.push(writeFile(join(srcDir, "artifacts.d.ts"), dTs)); + + try { + await Promise.all(fp); + } catch (error) { + console.error("Error writing artifacts definition:", error); + } + }) + ); + + return { artifactsEmittedPerFile }; + } +); + +/** + * Override task for cleaning up outdated artifacts. + * Deletes directories with stale `artifacts.d.ts` files that no longer have + * a matching `.sol` file. + */ +subtask(TASK_COMPILE_REMOVE_OBSOLETE_ARTIFACTS).setAction( + async (_, { config, artifacts }, runSuper) => { + const superRes = await runSuper(); + + const fqns = await artifacts.getAllFullyQualifiedNames(); + const existingSourceNames = new Set( + fqns.map((fqn) => parseFullyQualifiedName(fqn).sourceName) + ); + const allArtifactsDTs = await getAllFilesMatching( + config.paths.artifacts, + (f) => f.endsWith("artifacts.d.ts") + ); + + for (const artifactDTs of allArtifactsDTs) { + const dir = dirname(artifactDTs); + const sourceName = replaceBackslashes( + relative(config.paths.artifacts, dir) + ); + // If sourceName is empty, it means that the artifacts.d.ts file is in the + // root of the artifacts directory, and we shouldn't delete it. + if (sourceName === "") { + continue; + } + + if (!existingSourceNames.has(sourceName)) { + await rm(dir, { force: true, recursive: true }); + } + } + + return superRes; + } +); + +const AUTOGENERATED_FILE_PREFACE = `// This file was autogenerated by hardhat-viem, do not edit it. +// prettier-ignore +// tslint:disable +// eslint-disable`; + +/** + * Generates TypeScript code that extends the `ArtifactsMap` with `never` types + * for duplicate contract names. + */ +function generateDuplicateArtifactsDefinition( + duplicateContractNames: Set +) { + return `${AUTOGENERATED_FILE_PREFACE} + +import "hardhat/types/artifacts"; + +declare module "hardhat/types/artifacts" { + interface ArtifactsMap { + ${Array.from(duplicateContractNames) + .map((name) => `${name}: never;`) + .join("\n ")} + } +} +`; +} + +/** + * Generates TypeScript code to declare a contract and its associated + * TypeScript types. + */ +function generateContractDeclaration(artifact: Artifact, isDuplicate: boolean) { + const { contractName, sourceName } = artifact; + const fqn = getFullyQualifiedName(sourceName, contractName); + const validNames = isDuplicate ? [fqn] : [contractName, fqn]; + const json = JSON.stringify(artifact, undefined, 2); + const contractTypeName = `${contractName}$Type`; + + const constructorAbi = artifact.abi.find( + ({ type }) => type === "constructor" + ); + + const inputs: Array<{ + internalType: string; + name: string; + type: string; + }> = constructorAbi !== undefined ? constructorAbi.inputs : []; + + const constructorArgs = + inputs.length > 0 + ? `constructorArgs: [${inputs + .map(({ name, type }) => getArgType(name, type)) + .join(", ")}]` + : `constructorArgs?: []`; + + return `${AUTOGENERATED_FILE_PREFACE} + +import type { Address } from "viem"; +${ + inputs.length > 0 + ? `import type { AbiParameterToPrimitiveType, GetContractReturnType } from "@nomicfoundation/hardhat-viem/types";` + : `import type { GetContractReturnType } from "@nomicfoundation/hardhat-viem/types";` +} +import "@nomicfoundation/hardhat-viem/types"; + +export interface ${contractTypeName} ${json} + +declare module "@nomicfoundation/hardhat-viem/types" { + ${validNames + .map( + (name) => `export function deployContract( + contractName: "${name}", + ${constructorArgs}, + config?: DeployContractConfig + ): Promise>;` + ) + .join("\n ")} + + ${validNames + .map( + (name) => `export function sendDeploymentTransaction( + contractName: "${name}", + ${constructorArgs}, + config?: SendDeploymentTransactionConfig + ): Promise<{ + contract: GetContractReturnType<${contractTypeName}["abi"]>; + deploymentTransaction: GetTransactionReturnType; + }>;` + ) + .join("\n ")} + + ${validNames + .map( + (name) => `export function getContractAt( + contractName: "${name}", + address: Address, + config?: GetContractAtConfig + ): Promise>;` + ) + .join("\n ")} +} +`; +} + +/** + * Generates TypeScript code to extend the `ArtifactsMap` interface with + * contract types. + */ +function generateArtifactsDefinition( + contractTypeData: Array<{ + contractName: string; + fqn: string; + typeName: string; + declaration: string; + }> +) { + return `${AUTOGENERATED_FILE_PREFACE} + +import "hardhat/types/artifacts"; + +${contractTypeData + .map((ctd) => `import { ${ctd.typeName} } from "./${ctd.contractName}";`) + .join("\n")} + +declare module "hardhat/types/artifacts" { + interface ArtifactsMap { + ${contractTypeData + .map((ctd) => `["${ctd.contractName}"]: ${ctd.typeName};`) + .join("\n ")} + ${contractTypeData + .map((ctd) => `["${ctd.fqn}"]: ${ctd.typeName};`) + .join("\n ")} + } +} +`; +} + +/** + * Returns the type of a function argument in one of the following formats: + * - If the 'name' is provided: + * "name: AbiParameterToPrimitiveType<{ name: string; type: string; }>" + * + * - If the 'name' is empty: + * "AbiParameterToPrimitiveType<{ name: string; type: string; }>" + */ +function getArgType(name: string | undefined, type: string) { + const argType = `AbiParameterToPrimitiveType<${JSON.stringify({ + name, + type, + })}>`; + + return name !== "" && name !== undefined ? `${name}: ${argType}` : argType; +} + +/** + * Returns a set of duplicate contract names. + */ +async function findDuplicateContractNames(artifacts: Artifacts) { + const fqns = await artifacts.getAllFullyQualifiedNames(); + const contractNames = fqns.map( + (fqn) => parseFullyQualifiedName(fqn).contractName + ); + + const duplicates = new Set(); + const existing = new Set(); + + for (const name of contractNames) { + if (existing.has(name)) { + duplicates.add(name); + } + + existing.add(name); + } + + return duplicates; +} diff --git a/packages/hardhat-viem/src/internal/type-extensions.ts b/packages/hardhat-viem/src/internal/type-extensions.ts new file mode 100644 index 0000000000..8310439a2e --- /dev/null +++ b/packages/hardhat-viem/src/internal/type-extensions.ts @@ -0,0 +1,54 @@ +import type { + Address, + PublicClientConfig, + WalletClientConfig, + TestClientConfig, +} from "viem"; +import type { + PublicClient, + TestClient, + WalletClient, + deployContract, + sendDeploymentTransaction, + getContractAt, +} from "../types"; +import "hardhat/types/runtime"; +import "hardhat/types/artifacts"; + +declare module "hardhat/types/runtime" { + interface HardhatRuntimeEnvironment { + viem: { + getPublicClient( + publicClientConfig?: Partial + ): Promise; + getWalletClients( + walletClientConfig?: Partial + ): Promise; + getWalletClient( + address: Address, + walletClientConfig?: Partial + ): Promise; + getTestClient( + testClientConfig?: Partial + ): Promise; + deployContract: typeof deployContract; + sendDeploymentTransaction: typeof sendDeploymentTransaction; + getContractAt: typeof getContractAt; + }; + } +} + +declare module "hardhat/types/artifacts" { + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface ArtifactsMap {} + + interface Artifacts { + readArtifact( + contractNameOrFullyQualifiedName: ArgT + ): Promise; + + readArtifactSync( + contractNameOrFullyQualifiedName: ArgT + ): ArtifactsMap[ArgT]; + } +} diff --git a/packages/hardhat-viem/src/tsconfig.json b/packages/hardhat-viem/src/tsconfig.json new file mode 100644 index 0000000000..aa452dcdf7 --- /dev/null +++ b/packages/hardhat-viem/src/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../config/typescript/tsconfig.json", + "compilerOptions": { + "outDir": "../", + "rootDirs": ["."], + "composite": true + }, + "include": ["./**/*.ts"], + "exclude": [], + "references": [ + { + "path": "../../hardhat-core/src" + } + ] +} diff --git a/packages/hardhat-viem/src/types.ts b/packages/hardhat-viem/src/types.ts new file mode 100644 index 0000000000..27414bf5e8 --- /dev/null +++ b/packages/hardhat-viem/src/types.ts @@ -0,0 +1,77 @@ +import type * as viemT from "viem"; +import type { ArtifactsMap } from "hardhat/types/artifacts"; + +export type PublicClient = viemT.PublicClient; +export type WalletClient = viemT.WalletClient< + viemT.Transport, + viemT.Chain, + viemT.Account +>; +export type TestClient = viemT.TestClient< + TestClientMode, + viemT.Transport, + viemT.Chain +>; + +export type TestClientMode = Parameters< + typeof viemT.createTestClient +>[0]["mode"]; + +export interface SendTransactionConfig { + walletClient?: WalletClient; + gas?: bigint; + gasPrice?: bigint; + maxFeePerGas?: bigint; + maxPriorityFeePerGas?: bigint; + value?: bigint; +} + +export interface DeployContractConfig extends SendTransactionConfig { + confirmations?: number; +} + +export type SendDeploymentTransactionConfig = SendTransactionConfig; + +export interface GetContractAtConfig { + walletClient?: WalletClient; +} + +export type GetContractReturnType< + TAbi extends viemT.Abi | readonly unknown[] = viemT.Abi +> = viemT.GetContractReturnType< + TAbi, + PublicClient, + WalletClient, + viemT.Address +>; + +export type GetTransactionReturnType = viemT.GetTransactionReturnType< + viemT.Chain, + "latest" +>; + +export type ContractName = + StringT extends keyof ArtifactsMap ? never : StringT; + +export declare function deployContract( + contractName: ContractName, + constructorArgs?: any[], + config?: DeployContractConfig +): Promise; + +export declare function sendDeploymentTransaction( + contractName: ContractName, + constructorArgs?: any[], + config?: SendDeploymentTransactionConfig +): Promise<{ + contract: GetContractReturnType; + deploymentTransaction: GetTransactionReturnType; +}>; + +export declare function getContractAt( + contractName: ContractName, + address: viemT.Address, + config?: GetContractAtConfig +): Promise; + +export type { AbiParameterToPrimitiveType } from "abitype"; diff --git a/packages/hardhat-viem/test/.eslintrc.js b/packages/hardhat-viem/test/.eslintrc.js new file mode 100644 index 0000000000..757fe8a3ca --- /dev/null +++ b/packages/hardhat-viem/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-viem/test/chains.ts b/packages/hardhat-viem/test/chains.ts new file mode 100644 index 0000000000..36395302a7 --- /dev/null +++ b/packages/hardhat-viem/test/chains.ts @@ -0,0 +1,139 @@ +import type { EthereumProvider } from "hardhat/types"; + +import { expect, assert } from "chai"; +import sinon from "sinon"; +import * as chains from "viem/chains"; + +import { + getChain, + getMode, + isDevelopmentNetwork, +} from "../src/internal/chains"; +import { EthereumMockedProvider } from "./mocks/provider"; + +describe("chains", () => { + describe("getChain", () => { + afterEach(sinon.restore); + + it("should return the chain corresponding to the chain id", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + const sendStub = sinon.stub(provider, "send"); + sendStub.withArgs("eth_chainId").returns(Promise.resolve("0x1")); // mainnet chain id + sendStub.withArgs("hardhat_metadata").throws(); + sendStub.withArgs("anvil_nodeInfo").throws(); + + const chain = await getChain(provider); + + expect(chain).to.deep.equal(chains.mainnet); + assert.equal(sendStub.callCount, 1); + }); + + it("should return the hardhat chain if the chain id is 31337 and the network is hardhat", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + const sendStub = sinon.stub(provider, "send"); + sendStub.withArgs("eth_chainId").returns(Promise.resolve("0x7a69")); // 31337 in hex + sendStub.withArgs("hardhat_metadata").returns(Promise.resolve({})); + sendStub.withArgs("anvil_nodeInfo").throws(); + + const chain = await getChain(provider); + + expect(chain).to.deep.equal(chains.hardhat); + }); + + it("should return the foundry chain if the chain id is 31337 and the network is foundry", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + const sendStub = sinon.stub(provider, "send"); + sendStub.withArgs("eth_chainId").returns(Promise.resolve("0x7a69")); // 31337 in hex + sendStub.withArgs("hardhat_metadata").throws(); + sendStub.withArgs("anvil_nodeInfo").returns(Promise.resolve({})); + + const chain = await getChain(provider); + + expect(chain).to.deep.equal(chains.foundry); + }); + + it("should throw if the chain id is 31337 and the network is neither hardhat nor foundry", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + const sendStub = sinon.stub(provider, "send"); + sendStub.withArgs("eth_chainId").returns(Promise.resolve("0x7a69")); // 31337 in hex + sendStub.withArgs("hardhat_metadata").throws(); + sendStub.withArgs("anvil_nodeInfo").throws(); + + await expect(getChain(provider)).to.be.rejectedWith( + `The chain id corresponds to a development network but we couldn't detect which one. +Please report this issue if you're using Hardhat or Foundry.` + ); + }); + + it("should throw if the chain id is not 31337 and there is no chain with that id", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + const sendStub = sinon.stub(provider, "send"); + sendStub.withArgs("eth_chainId").returns(Promise.resolve("0x0")); // fake chain id 0 + sendStub.withArgs("hardhat_metadata").throws(); + sendStub.withArgs("anvil_nodeInfo").throws(); + + await expect(getChain(provider)).to.be.rejectedWith( + /No network with chain id 0 found/ + ); + }); + + it("should throw if the chain id is not 31337 and there are multiple chains with that id", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + const sendStub = sinon.stub(provider, "send"); + // chain id 999 corresponds to Wanchain Testnet but also Zora Goerli Testnet + sendStub.withArgs("eth_chainId").returns(Promise.resolve("0x3e7")); + sendStub.withArgs("hardhat_metadata").throws(); + sendStub.withArgs("anvil_nodeInfo").throws(); + + await expect(getChain(provider)).to.be.rejectedWith( + /Multiple networks with chain id 999 found./ + ); + }); + }); + + describe("isDevelopmentNetwork", () => { + it("should return true if the chain id is 31337", () => { + assert.ok(isDevelopmentNetwork(31337)); + }); + + it("should return false if the chain id is not 31337", () => { + assert.notOk(isDevelopmentNetwork(1)); + }); + }); + + describe("getMode", () => { + it("should return hardhat if the network is hardhat", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + const sendStub = sinon.stub(provider, "send"); + sendStub.withArgs("hardhat_metadata").returns(Promise.resolve({})); + sendStub.withArgs("anvil_nodeInfo").throws(); + + const mode = await getMode(provider); + + expect(mode).to.equal("hardhat"); + }); + + it("should return anvil if the network is foundry", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + const sendStub = sinon.stub(provider, "send"); + sendStub.withArgs("hardhat_metadata").throws(); + sendStub.withArgs("anvil_nodeInfo").returns(Promise.resolve({})); + + const mode = await getMode(provider); + + expect(mode).to.equal("anvil"); + }); + + it("should throw if the network is neither hardhat nor foundry", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + const sendStub = sinon.stub(provider, "send"); + sendStub.withArgs("hardhat_metadata").throws(); + sendStub.withArgs("anvil_nodeInfo").throws(); + + await expect(getMode(provider)).to.be.rejectedWith( + `The chain id corresponds to a development network but we couldn't detect which one. +Please report this issue if you're using Hardhat or Foundry.` + ); + }); + }); +}); diff --git a/packages/hardhat-viem/test/clients.ts b/packages/hardhat-viem/test/clients.ts new file mode 100644 index 0000000000..399f8d0b82 --- /dev/null +++ b/packages/hardhat-viem/test/clients.ts @@ -0,0 +1,174 @@ +import type { EthereumProvider } from "hardhat/types"; + +import { assert } from "chai"; +import * as chains from "viem/chains"; + +import { + innerGetPublicClient, + innerGetWalletClients, + innerGetTestClient, +} from "../src/internal/clients"; +import { EthereumMockedProvider } from "./mocks/provider"; + +describe("clients", () => { + describe("innerGetPublicClient", () => { + it("should return a public client", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + + const client = await innerGetPublicClient(provider, chains.mainnet); + + assert.isDefined(client); + assert.equal(client.type, "publicClient"); + assert.equal(client.chain.id, chains.mainnet.id); + }); + + it("should return a public client with custom parameters", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + + const client = await innerGetPublicClient(provider, chains.mainnet, { + pollingInterval: 1000, + cacheTime: 2000, + }); + + assert.equal(client.pollingInterval, 1000); + assert.equal(client.cacheTime, 2000); + }); + + it("should return a public client with default parameters for development networks", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + + const client = await innerGetPublicClient(provider, chains.hardhat); + + assert.equal(client.pollingInterval, 50); + assert.equal(client.cacheTime, 0); + }); + }); + + describe("innerGetWalletClients", () => { + it("should return a list of wallet clients", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + + const clients = await innerGetWalletClients(provider, chains.mainnet, [ + "0x1", + "0x2", + ]); + + assert.isArray(clients); + assert.isNotEmpty(clients); + clients.forEach((client) => { + assert.equal(client.type, "walletClient"); + assert.equal(client.chain.id, chains.mainnet.id); + }); + assert.equal(clients[0].account.address, "0x1"); + assert.equal(clients[1].account.address, "0x2"); + }); + + it("should return a list of wallet clients with custom parameters", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + + const clients = await innerGetWalletClients( + provider, + chains.mainnet, + ["0x1", "0x2"], + { + pollingInterval: 1000, + cacheTime: 2000, + } + ); + + assert.isArray(clients); + assert.isNotEmpty(clients); + clients.forEach((client) => { + assert.equal(client.pollingInterval, 1000); + assert.equal(client.cacheTime, 2000); + }); + }); + + it("should return a list of wallet clients with default parameters for development networks", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + + const clients = await innerGetWalletClients(provider, chains.hardhat, [ + "0x1", + "0x2", + ]); + + assert.isArray(clients); + assert.isNotEmpty(clients); + clients.forEach((client) => { + assert.equal(client.pollingInterval, 50); + assert.equal(client.cacheTime, 0); + }); + }); + + it("should return an empty array if there are no accounts owned by the user", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + + const clients = await innerGetWalletClients(provider, chains.mainnet, []); + + assert.isArray(clients); + assert.isEmpty(clients); + }); + }); + + describe("innerGetTestClient", () => { + it("should return a test client with hardhat mode", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + + const client = await innerGetTestClient( + provider, + chains.hardhat, + "hardhat" + ); + + assert.isDefined(client); + assert.equal(client.type, "testClient"); + assert.equal(client.chain.id, chains.hardhat.id); + assert.equal(client.mode, "hardhat"); + }); + + it("should return a test client with anvil mode", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + + const client = await innerGetTestClient( + provider, + chains.foundry, + "anvil" + ); + + assert.isDefined(client); + assert.equal(client.type, "testClient"); + assert.equal(client.chain.id, chains.foundry.id); + assert.equal(client.mode, "anvil"); + }); + + it("should return a test client with custom parameters", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + + const client = await innerGetTestClient( + provider, + chains.hardhat, + "hardhat", + { + pollingInterval: 1000, + cacheTime: 2000, + } + ); + + assert.equal(client.pollingInterval, 1000); + assert.equal(client.cacheTime, 2000); + }); + + it("should return a test client with default parameters for development networks", async () => { + const provider: EthereumProvider = new EthereumMockedProvider(); + + const client = await innerGetTestClient( + provider, + chains.hardhat, + "hardhat" + ); + + assert.equal(client.pollingInterval, 50); + assert.equal(client.cacheTime, 0); + }); + }); +}); diff --git a/packages/hardhat-viem/test/fixture-projects/hardhat-project/contracts/WithConstructorArgs.sol b/packages/hardhat-viem/test/fixture-projects/hardhat-project/contracts/WithConstructorArgs.sol new file mode 100644 index 0000000000..3352d4fad5 --- /dev/null +++ b/packages/hardhat-viem/test/fixture-projects/hardhat-project/contracts/WithConstructorArgs.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +contract WithConstructorArgs { + uint256 public data; + address public owner; + + constructor(uint256 _data) { + data = _data; + owner = msg.sender; + } + + function setData(uint256 _newValue) external { + data = _newValue; + } + + function getData() external view returns (uint256) { + return data; + } + + function getOwner() external view returns (address) { + return owner; + } +} diff --git a/packages/hardhat-viem/test/fixture-projects/hardhat-project/contracts/WithoutConstructorArgs.sol b/packages/hardhat-viem/test/fixture-projects/hardhat-project/contracts/WithoutConstructorArgs.sol new file mode 100644 index 0000000000..80d165a039 --- /dev/null +++ b/packages/hardhat-viem/test/fixture-projects/hardhat-project/contracts/WithoutConstructorArgs.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +contract WithoutConstructorArgs { + uint256 public data; + address public owner; + + constructor() payable { + owner = msg.sender; + } + + function setData(uint256 _newValue) external { + data = _newValue; + } + + function getData() external view returns (uint256) { + return data; + } + + function getOwner() external view returns (address) { + return owner; + } +} diff --git a/packages/hardhat-viem/test/fixture-projects/hardhat-project/hardhat.config.js b/packages/hardhat-viem/test/fixture-projects/hardhat-project/hardhat.config.js new file mode 100644 index 0000000000..a59d1ed4d8 --- /dev/null +++ b/packages/hardhat-viem/test/fixture-projects/hardhat-project/hardhat.config.js @@ -0,0 +1,5 @@ +require("../../../src/index"); + +module.exports = { + solidity: "0.8.19", +}; diff --git a/packages/hardhat-viem/test/fixture-projects/type-generation/contracts/A.sol b/packages/hardhat-viem/test/fixture-projects/type-generation/contracts/A.sol new file mode 100644 index 0000000000..ad028f8b3a --- /dev/null +++ b/packages/hardhat-viem/test/fixture-projects/type-generation/contracts/A.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +contract A { + address owner; + string name; + + constructor(address _owner, uint256, string memory _name) { + owner = _owner; + name = _name; + } + + function getA() public pure returns (uint256) { + return 1; + } + + function getOwner() public view returns (address) { + return owner; + } +} + +contract B { + function getB() public pure returns (uint256) { + return 2; + } +} diff --git a/packages/hardhat-viem/test/fixture-projects/type-generation/contracts/C.sol b/packages/hardhat-viem/test/fixture-projects/type-generation/contracts/C.sol new file mode 100644 index 0000000000..9bac18334d --- /dev/null +++ b/packages/hardhat-viem/test/fixture-projects/type-generation/contracts/C.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +contract C { + function getC() public pure returns (uint256) { + return 3; + } +} + +contract B { + uint256 b; + string s; + + constructor(uint256 _b, string memory _s) { + b = _b; + s = _s; + } + + function getB() public view returns (uint256) { + return b; + } +} diff --git a/packages/hardhat-viem/test/fixture-projects/type-generation/hardhat.config.js b/packages/hardhat-viem/test/fixture-projects/type-generation/hardhat.config.js new file mode 100644 index 0000000000..a59d1ed4d8 --- /dev/null +++ b/packages/hardhat-viem/test/fixture-projects/type-generation/hardhat.config.js @@ -0,0 +1,5 @@ +require("../../../src/index"); + +module.exports = { + solidity: "0.8.19", +}; diff --git a/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/artifacts.d.ts b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/artifacts.d.ts new file mode 100644 index 0000000000..eeda088124 --- /dev/null +++ b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/artifacts.d.ts @@ -0,0 +1,12 @@ +// This file was autogenerated by hardhat-viem, do not edit it. +// prettier-ignore +// tslint:disable +// eslint-disable + +import "hardhat/types/artifacts"; + +declare module "hardhat/types/artifacts" { + interface ArtifactsMap { + B: never; + } +} diff --git a/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/A.sol/A.d.ts b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/A.sol/A.d.ts new file mode 100644 index 0000000000..912e6385cd --- /dev/null +++ b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/A.sol/A.d.ts @@ -0,0 +1,108 @@ +// This file was autogenerated by hardhat-viem, do not edit it. +// prettier-ignore +// tslint:disable +// eslint-disable + +import type { Address } from "viem"; +import type { AbiParameterToPrimitiveType, GetContractReturnType } from "@nomicfoundation/hardhat-viem/types"; +import "@nomicfoundation/hardhat-viem/types"; + +export interface A$Type { + "_format": "hh-sol-artifact-1", + "contractName": "A", + "sourceName": "contracts/A.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "string", + "name": "_name", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "getA", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b5060405161076938038061076983398181016040528101906100329190610293565b826000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600190816100819190610519565b505050506105eb565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100c98261009e565b9050919050565b6100d9816100be565b81146100e457600080fd5b50565b6000815190506100f6816100d0565b92915050565b6000819050919050565b61010f816100fc565b811461011a57600080fd5b50565b60008151905061012c81610106565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6101858261013c565b810181811067ffffffffffffffff821117156101a4576101a361014d565b5b80604052505050565b60006101b761008a565b90506101c3828261017c565b919050565b600067ffffffffffffffff8211156101e3576101e261014d565b5b6101ec8261013c565b9050602081019050919050565b60005b838110156102175780820151818401526020810190506101fc565b60008484015250505050565b6000610236610231846101c8565b6101ad565b90508281526020810184848401111561025257610251610137565b5b61025d8482856101f9565b509392505050565b600082601f83011261027a57610279610132565b5b815161028a848260208601610223565b91505092915050565b6000806000606084860312156102ac576102ab610094565b5b60006102ba868287016100e7565b93505060206102cb8682870161011d565b925050604084015167ffffffffffffffff8111156102ec576102eb610099565b5b6102f886828701610265565b9150509250925092565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061035457607f821691505b6020821081036103675761036661030d565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026103cf7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610392565b6103d98683610392565b95508019841693508086168417925050509392505050565b6000819050919050565b600061041661041161040c846100fc565b6103f1565b6100fc565b9050919050565b6000819050919050565b610430836103fb565b61044461043c8261041d565b84845461039f565b825550505050565b600090565b61045961044c565b610464818484610427565b505050565b5b818110156104885761047d600082610451565b60018101905061046a565b5050565b601f8211156104cd5761049e8161036d565b6104a784610382565b810160208510156104b6578190505b6104ca6104c285610382565b830182610469565b50505b505050565b600082821c905092915050565b60006104f0600019846008026104d2565b1980831691505092915050565b600061050983836104df565b9150826002028217905092915050565b61052282610302565b67ffffffffffffffff81111561053b5761053a61014d565b5b610545825461033c565b61055082828561048c565b600060209050601f8311600181146105835760008415610571578287015190505b61057b85826104fd565b8655506105e3565b601f1984166105918661036d565b60005b828110156105b957848901518255600182019150602085019450602081019050610594565b868310156105d657848901516105d2601f8916826104df565b8355505b6001600288020188555050505b505050505050565b61016f806105fa6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063893d20e81461003b578063d46300fd14610059575b600080fd5b610043610077565b60405161005091906100ea565b60405180910390f35b6100616100a0565b60405161006e919061011e565b60405180910390f35b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006001905090565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100d4826100a9565b9050919050565b6100e4816100c9565b82525050565b60006020820190506100ff60008301846100db565b92915050565b6000819050919050565b61011881610105565b82525050565b6000602082019050610133600083018461010f565b9291505056fea264697066735822122096d0f807114d408e2aa4f29fa4fa9774523526abe709bbf7de02dbe02a245ae264736f6c63430008130033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c8063893d20e81461003b578063d46300fd14610059575b600080fd5b610043610077565b60405161005091906100ea565b60405180910390f35b6100616100a0565b60405161006e919061011e565b60405180910390f35b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006001905090565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100d4826100a9565b9050919050565b6100e4816100c9565b82525050565b60006020820190506100ff60008301846100db565b92915050565b6000819050919050565b61011881610105565b82525050565b6000602082019050610133600083018461010f565b9291505056fea264697066735822122096d0f807114d408e2aa4f29fa4fa9774523526abe709bbf7de02dbe02a245ae264736f6c63430008130033", + "linkReferences": {}, + "deployedLinkReferences": {} +} + +declare module "@nomicfoundation/hardhat-viem/types" { + export function deployContract( + contractName: "A", + constructorArgs: [_owner: AbiParameterToPrimitiveType<{"name":"_owner","type":"address"}>, AbiParameterToPrimitiveType<{"name":"","type":"uint256"}>, _name: AbiParameterToPrimitiveType<{"name":"_name","type":"string"}>], + config?: DeployContractConfig + ): Promise>; + export function deployContract( + contractName: "contracts/A.sol:A", + constructorArgs: [_owner: AbiParameterToPrimitiveType<{"name":"_owner","type":"address"}>, AbiParameterToPrimitiveType<{"name":"","type":"uint256"}>, _name: AbiParameterToPrimitiveType<{"name":"_name","type":"string"}>], + config?: DeployContractConfig + ): Promise>; + + export function sendDeploymentTransaction( + contractName: "A", + constructorArgs: [_owner: AbiParameterToPrimitiveType<{"name":"_owner","type":"address"}>, AbiParameterToPrimitiveType<{"name":"","type":"uint256"}>, _name: AbiParameterToPrimitiveType<{"name":"_name","type":"string"}>], + config?: SendDeploymentTransactionConfig + ): Promise<{ + contract: GetContractReturnType; + deploymentTransaction: GetTransactionReturnType; + }>; + export function sendDeploymentTransaction( + contractName: "contracts/A.sol:A", + constructorArgs: [_owner: AbiParameterToPrimitiveType<{"name":"_owner","type":"address"}>, AbiParameterToPrimitiveType<{"name":"","type":"uint256"}>, _name: AbiParameterToPrimitiveType<{"name":"_name","type":"string"}>], + config?: SendDeploymentTransactionConfig + ): Promise<{ + contract: GetContractReturnType; + deploymentTransaction: GetTransactionReturnType; + }>; + + export function getContractAt( + contractName: "A", + address: Address, + config?: GetContractAtConfig + ): Promise>; + export function getContractAt( + contractName: "contracts/A.sol:A", + address: Address, + config?: GetContractAtConfig + ): Promise>; +} diff --git a/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/A.sol/B.d.ts b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/A.sol/B.d.ts new file mode 100644 index 0000000000..917fea72bd --- /dev/null +++ b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/A.sol/B.d.ts @@ -0,0 +1,56 @@ +// This file was autogenerated by hardhat-viem, do not edit it. +// prettier-ignore +// tslint:disable +// eslint-disable + +import type { Address } from "viem"; +import type { GetContractReturnType } from "@nomicfoundation/hardhat-viem/types"; +import "@nomicfoundation/hardhat-viem/types"; + +export interface B$Type { + "_format": "hh-sol-artifact-1", + "contractName": "B", + "sourceName": "contracts/A.sol", + "abi": [ + { + "inputs": [], + "name": "getB", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063a1c5191514602d575b600080fd5b60336047565b604051603e91906067565b60405180910390f35b60006002905090565b6000819050919050565b6061816050565b82525050565b6000602082019050607a6000830184605a565b9291505056fea2646970667358221220fe48236ca287d1fb02c67c759d4bbebdc9702638fef90f605955fa2db9c0049864736f6c63430008130033", + "deployedBytecode": "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063a1c5191514602d575b600080fd5b60336047565b604051603e91906067565b60405180910390f35b60006002905090565b6000819050919050565b6061816050565b82525050565b6000602082019050607a6000830184605a565b9291505056fea2646970667358221220fe48236ca287d1fb02c67c759d4bbebdc9702638fef90f605955fa2db9c0049864736f6c63430008130033", + "linkReferences": {}, + "deployedLinkReferences": {} +} + +declare module "@nomicfoundation/hardhat-viem/types" { + export function deployContract( + contractName: "contracts/A.sol:B", + constructorArgs?: [], + config?: DeployContractConfig + ): Promise>; + + export function sendDeploymentTransaction( + contractName: "contracts/A.sol:B", + constructorArgs?: [], + config?: SendDeploymentTransactionConfig + ): Promise<{ + contract: GetContractReturnType; + deploymentTransaction: GetTransactionReturnType; + }>; + + export function getContractAt( + contractName: "contracts/A.sol:B", + address: Address, + config?: GetContractAtConfig + ): Promise>; +} diff --git a/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/A.sol/artifacts.d.ts b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/A.sol/artifacts.d.ts new file mode 100644 index 0000000000..18aea5c6b7 --- /dev/null +++ b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/A.sol/artifacts.d.ts @@ -0,0 +1,18 @@ +// This file was autogenerated by hardhat-viem, do not edit it. +// prettier-ignore +// tslint:disable +// eslint-disable + +import "hardhat/types/artifacts"; + +import { A$Type } from "./A"; +import { B$Type } from "./B"; + +declare module "hardhat/types/artifacts" { + interface ArtifactsMap { + ["A"]: A$Type; + ["B"]: B$Type; + ["contracts/A.sol:A"]: A$Type; + ["contracts/A.sol:B"]: B$Type; + } +} diff --git a/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/C.sol/B.d.ts b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/C.sol/B.d.ts new file mode 100644 index 0000000000..2f0deb8393 --- /dev/null +++ b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/C.sol/B.d.ts @@ -0,0 +1,72 @@ +// This file was autogenerated by hardhat-viem, do not edit it. +// prettier-ignore +// tslint:disable +// eslint-disable + +import type { Address } from "viem"; +import type { AbiParameterToPrimitiveType, GetContractReturnType } from "@nomicfoundation/hardhat-viem/types"; +import "@nomicfoundation/hardhat-viem/types"; + +export interface B$Type { + "_format": "hh-sol-artifact-1", + "contractName": "B", + "sourceName": "contracts/C.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_b", + "type": "uint256" + }, + { + "internalType": "string", + "name": "_s", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "getB", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50604051610604380380610604833981810160405281019061003291906101fb565b816000819055508060019081610048919061046e565b505050610540565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b61007781610064565b811461008257600080fd5b50565b6000815190506100948161006e565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100ed826100a4565b810181811067ffffffffffffffff8211171561010c5761010b6100b5565b5b80604052505050565b600061011f610050565b905061012b82826100e4565b919050565b600067ffffffffffffffff82111561014b5761014a6100b5565b5b610154826100a4565b9050602081019050919050565b60005b8381101561017f578082015181840152602081019050610164565b60008484015250505050565b600061019e61019984610130565b610115565b9050828152602081018484840111156101ba576101b961009f565b5b6101c5848285610161565b509392505050565b600082601f8301126101e2576101e161009a565b5b81516101f284826020860161018b565b91505092915050565b600080604083850312156102125761021161005a565b5b600061022085828601610085565b925050602083015167ffffffffffffffff8111156102415761024061005f565b5b61024d858286016101cd565b9150509250929050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806102a957607f821691505b6020821081036102bc576102bb610262565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026103247fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826102e7565b61032e86836102e7565b95508019841693508086168417925050509392505050565b6000819050919050565b600061036b61036661036184610064565b610346565b610064565b9050919050565b6000819050919050565b61038583610350565b61039961039182610372565b8484546102f4565b825550505050565b600090565b6103ae6103a1565b6103b981848461037c565b505050565b5b818110156103dd576103d26000826103a6565b6001810190506103bf565b5050565b601f821115610422576103f3816102c2565b6103fc846102d7565b8101602085101561040b578190505b61041f610417856102d7565b8301826103be565b50505b505050565b600082821c905092915050565b600061044560001984600802610427565b1980831691505092915050565b600061045e8383610434565b9150826002028217905092915050565b61047782610257565b67ffffffffffffffff8111156104905761048f6100b5565b5b61049a8254610291565b6104a58282856103e1565b600060209050601f8311600181146104d857600084156104c6578287015190505b6104d08582610452565b865550610538565b601f1984166104e6866102c2565b60005b8281101561050e578489015182556001820191506020850194506020810190506104e9565b8683101561052b5784890151610527601f891682610434565b8355505b6001600288020188555050505b505050505050565b60b68061054e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063a1c5191514602d575b600080fd5b60336047565b604051603e91906067565b60405180910390f35b60008054905090565b6000819050919050565b6061816050565b82525050565b6000602082019050607a6000830184605a565b9291505056fea2646970667358221220d9268b96329d3a8617cfa3925eac59afcfbe7935e6fa7baab75e296746f136f264736f6c63430008130033", + "deployedBytecode": "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063a1c5191514602d575b600080fd5b60336047565b604051603e91906067565b60405180910390f35b60008054905090565b6000819050919050565b6061816050565b82525050565b6000602082019050607a6000830184605a565b9291505056fea2646970667358221220d9268b96329d3a8617cfa3925eac59afcfbe7935e6fa7baab75e296746f136f264736f6c63430008130033", + "linkReferences": {}, + "deployedLinkReferences": {} +} + +declare module "@nomicfoundation/hardhat-viem/types" { + export function deployContract( + contractName: "contracts/C.sol:B", + constructorArgs: [_b: AbiParameterToPrimitiveType<{"name":"_b","type":"uint256"}>, _s: AbiParameterToPrimitiveType<{"name":"_s","type":"string"}>], + config?: DeployContractConfig + ): Promise>; + + export function sendDeploymentTransaction( + contractName: "contracts/C.sol:B", + constructorArgs: [_b: AbiParameterToPrimitiveType<{"name":"_b","type":"uint256"}>, _s: AbiParameterToPrimitiveType<{"name":"_s","type":"string"}>], + config?: SendDeploymentTransactionConfig + ): Promise<{ + contract: GetContractReturnType; + deploymentTransaction: GetTransactionReturnType; + }>; + + export function getContractAt( + contractName: "contracts/C.sol:B", + address: Address, + config?: GetContractAtConfig + ): Promise>; +} diff --git a/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/C.sol/C.d.ts b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/C.sol/C.d.ts new file mode 100644 index 0000000000..cc7edacffa --- /dev/null +++ b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/C.sol/C.d.ts @@ -0,0 +1,74 @@ +// This file was autogenerated by hardhat-viem, do not edit it. +// prettier-ignore +// tslint:disable +// eslint-disable + +import type { Address } from "viem"; +import type { GetContractReturnType } from "@nomicfoundation/hardhat-viem/types"; +import "@nomicfoundation/hardhat-viem/types"; + +export interface C$Type { + "_format": "hh-sol-artifact-1", + "contractName": "C", + "sourceName": "contracts/C.sol", + "abi": [ + { + "inputs": [], + "name": "getC", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063a2375d1e14602d575b600080fd5b60336047565b604051603e91906067565b60405180910390f35b60006003905090565b6000819050919050565b6061816050565b82525050565b6000602082019050607a6000830184605a565b9291505056fea264697066735822122047c1ea5199833309c2261ac9cf25569e6acf133d1cf8dc638f656e33f555635d64736f6c63430008130033", + "deployedBytecode": "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063a2375d1e14602d575b600080fd5b60336047565b604051603e91906067565b60405180910390f35b60006003905090565b6000819050919050565b6061816050565b82525050565b6000602082019050607a6000830184605a565b9291505056fea264697066735822122047c1ea5199833309c2261ac9cf25569e6acf133d1cf8dc638f656e33f555635d64736f6c63430008130033", + "linkReferences": {}, + "deployedLinkReferences": {} +} + +declare module "@nomicfoundation/hardhat-viem/types" { + export function deployContract( + contractName: "C", + constructorArgs?: [], + config?: DeployContractConfig + ): Promise>; + export function deployContract( + contractName: "contracts/C.sol:C", + constructorArgs?: [], + config?: DeployContractConfig + ): Promise>; + + export function sendDeploymentTransaction( + contractName: "C", + constructorArgs?: [], + config?: SendDeploymentTransactionConfig + ): Promise<{ + contract: GetContractReturnType; + deploymentTransaction: GetTransactionReturnType; + }>; + export function sendDeploymentTransaction( + contractName: "contracts/C.sol:C", + constructorArgs?: [], + config?: SendDeploymentTransactionConfig + ): Promise<{ + contract: GetContractReturnType; + deploymentTransaction: GetTransactionReturnType; + }>; + + export function getContractAt( + contractName: "C", + address: Address, + config?: GetContractAtConfig + ): Promise>; + export function getContractAt( + contractName: "contracts/C.sol:C", + address: Address, + config?: GetContractAtConfig + ): Promise>; +} diff --git a/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/C.sol/artifacts.d.ts b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/C.sol/artifacts.d.ts new file mode 100644 index 0000000000..4994bfede7 --- /dev/null +++ b/packages/hardhat-viem/test/fixture-projects/type-generation/snapshots/contracts/C.sol/artifacts.d.ts @@ -0,0 +1,18 @@ +// This file was autogenerated by hardhat-viem, do not edit it. +// prettier-ignore +// tslint:disable +// eslint-disable + +import "hardhat/types/artifacts"; + +import { B$Type } from "./B"; +import { C$Type } from "./C"; + +declare module "hardhat/types/artifacts" { + interface ArtifactsMap { + ["B"]: B$Type; + ["C"]: C$Type; + ["contracts/C.sol:B"]: B$Type; + ["contracts/C.sol:C"]: C$Type; + } +} diff --git a/packages/hardhat-viem/test/helpers.ts b/packages/hardhat-viem/test/helpers.ts new file mode 100644 index 0000000000..a9a16b807f --- /dev/null +++ b/packages/hardhat-viem/test/helpers.ts @@ -0,0 +1,63 @@ +import type { HardhatRuntimeEnvironment } from "hardhat/types"; + +import path from "path"; +import fs from "fs/promises"; +import { assert } from "chai"; +import { diffLinesUnified } from "jest-diff"; +import { resetHardhatContext } from "hardhat/plugins-testing"; + +// Import this plugin type extensions for the HardhatRuntimeEnvironment +import "../src/internal/type-extensions"; + +declare module "mocha" { + interface Context { + hre: HardhatRuntimeEnvironment; + } +} + +export const useEnvironment = (fixtureProjectName: string): void => { + before("Loading hardhat environment", function () { + process.chdir(path.join(__dirname, "fixture-projects", fixtureProjectName)); + process.env.HARDHAT_NETWORK = "hardhat"; + + this.hre = require("hardhat"); + }); + + after("Resetting hardhat context", async function () { + process.chdir(path.resolve(`${__dirname}/..`)); + resetHardhatContext(); + delete process.env.HARDHAT_NETWORK; + }); +}; + +export const assertSnapshotMatch = async ( + snapshotPath: string, + generatedFilePath: string +) => { + const expectedSnapshotContent = await fs.readFile(snapshotPath, "utf-8"); + const generatedFileContent = await fs.readFile(generatedFilePath, "utf-8"); + + if (expectedSnapshotContent !== generatedFileContent) { + assert.fail(` +Generated file differs from the expected snapshot: + +${generatedFilePath} should match ${snapshotPath} + +To update the snapshot, run: +yarn snapshots:update + +${diffLinesUnified( + expectedSnapshotContent.split("\n"), + generatedFileContent.split("\n"), + { + contextLines: 3, + expand: false, + includeChangeCounts: true, + } +)}`); + } +}; + +export function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/packages/hardhat-viem/test/integration.ts b/packages/hardhat-viem/test/integration.ts new file mode 100644 index 0000000000..f8ff1454c5 --- /dev/null +++ b/packages/hardhat-viem/test/integration.ts @@ -0,0 +1,426 @@ +import type { Hex, TransactionReceipt } from "viem"; +import type { EthereumProvider } from "hardhat/types"; + +import path from "path"; +import { assert, expect } from "chai"; +import sinon from "sinon"; +import { getAddress, parseEther } from "viem"; + +import { TASK_CLEAN, TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; +import { deployContract, innerDeployContract } from "../src/internal/contracts"; +import { EthereumMockedProvider } from "./mocks/provider"; +import { assertSnapshotMatch, sleep, useEnvironment } from "./helpers"; + +describe("Integration tests", function () { + afterEach(function () { + sinon.restore(); + }); + + describe("Hardhat Runtime Environment extension", function () { + useEnvironment("hardhat-project"); + + it("should add the viem object and it's properties", function () { + expect(this.hre.viem) + .to.be.an("object") + .that.has.all.keys([ + "getPublicClient", + "getWalletClients", + "getWalletClient", + "getTestClient", + "deployContract", + "sendDeploymentTransaction", + "getContractAt", + ]); + }); + }); + + describe("Viem plugin", function () { + useEnvironment("hardhat-project"); + + before(async function () { + await this.hre.run(TASK_COMPILE, { quiet: true }); + }); + + after(async function () { + await this.hre.run(TASK_CLEAN); + }); + + beforeEach(async function () { + await this.hre.network.provider.send("hardhat_reset"); + }); + + describe("Clients", function () { + it("should be able to query the blockchain using the public client", async function () { + const client = await this.hre.viem.getPublicClient(); + const blockNumber = await client.getBlockNumber(); + + assert.equal(blockNumber, 0n); + }); + + it("should be able to query the blockchain using the wallet client", async function () { + const publicClient = await this.hre.viem.getPublicClient(); + const [fromWalletClient, toWalletClient] = + await this.hre.viem.getWalletClients(); + const fromAddress = fromWalletClient.account.address; + const toAddress = toWalletClient.account.address; + + const fromBalanceBefore: bigint = await publicClient.getBalance({ + address: fromAddress, + }); + const toBalanceBefore: bigint = await publicClient.getBalance({ + address: toAddress, + }); + + const etherAmount = parseEther("0.0001"); + const hash = await fromWalletClient.sendTransaction({ + to: toAddress, + value: etherAmount, + }); + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + const transactionFee = receipt.gasUsed * receipt.effectiveGasPrice; + + const fromBalanceAfter: bigint = await publicClient.getBalance({ + address: fromAddress, + }); + const toBalanceAfter: bigint = await publicClient.getBalance({ + address: toAddress, + }); + + assert.isDefined(receipt); + assert.equal(receipt.status, "success"); + assert.equal( + fromBalanceAfter, + fromBalanceBefore - etherAmount - transactionFee + ); + assert.equal(toBalanceAfter, toBalanceBefore + etherAmount); + }); + + it("should be able to query the blockchain using the test client", async function () { + const publicClient = await this.hre.viem.getPublicClient(); + const testClient = await this.hre.viem.getTestClient(); + + await testClient.mine({ + blocks: 1000000, + }); + const blockNumber = await publicClient.getBlockNumber(); + assert.equal(blockNumber, 1000000n); + }); + }); + + describe("deployContract", function () { + it("should be able to deploy a contract without constructor args", async function () { + const contract = await this.hre.viem.deployContract( + "WithoutConstructorArgs" + ); + + await contract.write.setData([50n]); + const data = await contract.read.getData(); + assert.equal(data, 50n); + }); + + it("should be able to deploy a contract with constructor args", async function () { + const [defaultWalletClient] = await this.hre.viem.getWalletClients(); + const contract = await this.hre.viem.deployContract( + "WithConstructorArgs", + [50n] + ); + + let data = await contract.read.getData(); + assert.equal(data, 50n); + + const owner = await contract.read.getOwner(); + assert.equal(owner, getAddress(defaultWalletClient.account.address)); + + await contract.write.setData([100n]); + data = await contract.read.getData(); + assert.equal(data, 100n); + }); + + it("should be able to deploy a contract with a different wallet client", async function () { + const [_, secondWalletClient] = await this.hre.viem.getWalletClients(); + const contract = await this.hre.viem.deployContract( + "WithoutConstructorArgs", + [], + { walletClient: secondWalletClient } + ); + + const owner = await contract.read.getOwner(); + assert.equal(owner, getAddress(secondWalletClient.account.address)); + }); + + it("should be able to deploy a contract with initial ETH", async function () { + const publicClient = await this.hre.viem.getPublicClient(); + const [defaultWalletClient] = await this.hre.viem.getWalletClients(); + const ownerBalanceBefore = await publicClient.getBalance({ + address: defaultWalletClient.account.address, + }); + const etherAmount = parseEther("0.0001"); + const contract = await this.hre.viem.deployContract( + "WithoutConstructorArgs", + [], + { value: etherAmount } + ); + const ownerBalanceAfter = await publicClient.getBalance({ + address: defaultWalletClient.account.address, + }); + const contractBalance = await publicClient.getBalance({ + address: contract.address, + }); + const block = await publicClient.getBlock({ + includeTransactions: true, + }); + const receipt = await publicClient.getTransactionReceipt({ + hash: block.transactions[0].hash, + }); + const transactionFee = receipt.gasUsed * receipt.effectiveGasPrice; + + assert.equal(contractBalance, etherAmount); + assert.equal( + ownerBalanceAfter, + ownerBalanceBefore - etherAmount - transactionFee + ); + }); + + it("should throw an error if the contract address can't be retrieved", async function () { + const publicClient = await this.hre.viem.getPublicClient(); + sinon.stub(publicClient, "waitForTransactionReceipt").returns( + Promise.resolve({ + contractAddress: null, + }) as unknown as Promise + ); + const [walletClient] = await this.hre.viem.getWalletClients(); + const contractArtifact = await this.hre.artifacts.readArtifact( + "WithoutConstructorArgs" + ); + + await expect( + innerDeployContract( + publicClient, + walletClient, + contractArtifact.abi, + contractArtifact.bytecode as Hex, + [] + ) + ).to.be.rejectedWith( + /The deployment transaction '0x[a-fA-F0-9]{64}' was mined in block '\d+' but its receipt doesn't contain a contract address/ + ); + }); + + it("should throw an error if no accounts are configured for the network", async function () { + const provider: EthereumProvider = new EthereumMockedProvider(); + const sendStub = sinon.stub(provider, "send"); + sendStub.withArgs("eth_accounts").returns(Promise.resolve([])); + const hre = { + ...this.hre, + network: { + ...this.hre.network, + provider, + }, + }; + + await expect( + deployContract(hre, "WithoutConstructorArgs") + ).to.be.rejectedWith( + /Default wallet client not found. This can happen if no accounts were configured for this network/ + ); + }); + + it("should wait for confirmations", async function () { + const publicClient = await this.hre.viem.getPublicClient(); + const testClient = await this.hre.viem.getTestClient(); + const sleepingTime = 2 * publicClient.pollingInterval; + await testClient.setAutomine(false); + + let contractPromiseResolved = false; + const contractPromise = this.hre.viem + .deployContract("WithoutConstructorArgs", [], { + confirmations: 5, + }) + .then(() => { + contractPromiseResolved = true; + }); + await sleep(sleepingTime); + assert.isFalse(contractPromiseResolved); + + await testClient.mine({ + blocks: 3, + }); + await sleep(sleepingTime); + assert.isFalse(contractPromiseResolved); + + await testClient.mine({ + blocks: 1, + }); + await sleep(sleepingTime); + assert.isFalse(contractPromiseResolved); + + await testClient.mine({ + blocks: 1, + }); + await contractPromise; + assert.isTrue(contractPromiseResolved); + }); + + it("should throw if the confirmations parameter is less than 0", async function () { + await expect( + this.hre.viem.deployContract("WithoutConstructorArgs", [], { + confirmations: -1, + }) + ).to.be.rejectedWith("Confirmations must be greater than 0."); + }); + + it("should throw if the confirmations parameter is 0", async function () { + await expect( + this.hre.viem.deployContract("WithoutConstructorArgs", [], { + confirmations: 0, + }) + ).to.be.rejectedWith( + "deployContract does not support 0 confirmations. Use sendDeploymentTransaction if you want to handle the deployment transaction yourself." + ); + }); + }); + + describe("sendDeploymentTransaction", function () { + it("should return the contract and the deployment transaction", async function () { + const publicClient = await this.hre.viem.getPublicClient(); + const { contract, deploymentTransaction } = + await this.hre.viem.sendDeploymentTransaction( + "WithoutConstructorArgs" + ); + assert.exists(contract); + assert.exists(deploymentTransaction); + + const { contractAddress } = + await publicClient.waitForTransactionReceipt({ + hash: deploymentTransaction.hash, + }); + assert.equal(contract.address, getAddress(contractAddress!)); + + await contract.write.setData([50n]); + const data = await contract.read.getData(); + assert.equal(data, 50n); + }); + }); + }); + + describe("Contract type generation", function () { + useEnvironment("type-generation"); + + before(async function () { + await this.hre.run(TASK_COMPILE, { quiet: true }); + }); + + after(async function () { + await this.hre.run(TASK_CLEAN); + }); + + it("should generate artifacts.d.ts", async function () { + const snapshotPath = path.join("snapshots", "artifacts.d.ts"); + const generatedFilePath = path.join("artifacts", "artifacts.d.ts"); + + await assertSnapshotMatch(snapshotPath, generatedFilePath); + }); + + it("should generate contracts/A.sol/A.d.ts", async function () { + const snapshotPath = path.join( + "snapshots", + "contracts", + "A.sol", + "A.d.ts" + ); + const generatedFilePath = path.join( + "artifacts", + "contracts", + "A.sol", + "A.d.ts" + ); + + await assertSnapshotMatch(snapshotPath, generatedFilePath); + }); + + it("should generate contracts/A.sol/B.d.ts", async function () { + const snapshotPath = path.join( + "snapshots", + "contracts", + "A.sol", + "B.d.ts" + ); + const generatedFilePath = path.join( + "artifacts", + "contracts", + "A.sol", + "B.d.ts" + ); + + await assertSnapshotMatch(snapshotPath, generatedFilePath); + }); + + it("should generate contracts/A.sol/artifacts.d.ts", async function () { + const snapshotPath = path.join( + "snapshots", + "contracts", + "A.sol", + "artifacts.d.ts" + ); + const generatedFilePath = path.join( + "artifacts", + "contracts", + "A.sol", + "artifacts.d.ts" + ); + + await assertSnapshotMatch(snapshotPath, generatedFilePath); + }); + + it("should generate contracts/C.sol/B.d.ts", async function () { + const snapshotPath = path.join( + "snapshots", + "contracts", + "C.sol", + "B.d.ts" + ); + const generatedFilePath = path.join( + "artifacts", + "contracts", + "C.sol", + "B.d.ts" + ); + + await assertSnapshotMatch(snapshotPath, generatedFilePath); + }); + + it("should generate contracts/C.sol/C.d.ts", async function () { + const snapshotPath = path.join( + "snapshots", + "contracts", + "C.sol", + "C.d.ts" + ); + const generatedFilePath = path.join( + "artifacts", + "contracts", + "C.sol", + "C.d.ts" + ); + + await assertSnapshotMatch(snapshotPath, generatedFilePath); + }); + + it("should generate contracts/C.sol/artifacts.d.ts", async function () { + const snapshotPath = path.join( + "snapshots", + "contracts", + "C.sol", + "artifacts.d.ts" + ); + const generatedFilePath = path.join( + "artifacts", + "contracts", + "C.sol", + "artifacts.d.ts" + ); + + await assertSnapshotMatch(snapshotPath, generatedFilePath); + }); + }); +}); diff --git a/packages/hardhat-viem/test/mocks/provider.ts b/packages/hardhat-viem/test/mocks/provider.ts new file mode 100644 index 0000000000..44fe69ab2f --- /dev/null +++ b/packages/hardhat-viem/test/mocks/provider.ts @@ -0,0 +1,24 @@ +import type { + EthereumProvider, + RequestArguments, + JsonRpcRequest, + JsonRpcResponse, +} from "hardhat/types"; + +import EventEmitter from "events"; + +export class EthereumMockedProvider + extends EventEmitter + implements EthereumProvider +{ + public async request(_args: RequestArguments): Promise {} + + public async send(_method: string, _params: any[] = []) {} + + public sendAsync( + _payload: JsonRpcRequest, + callback: (error: any, response: JsonRpcResponse) => void + ) { + callback(null, {} as JsonRpcRequest); // this is here just to finish the "async" operation + } +} diff --git a/packages/hardhat-viem/test/setup.ts b/packages/hardhat-viem/test/setup.ts new file mode 100644 index 0000000000..1c993d01e0 --- /dev/null +++ b/packages/hardhat-viem/test/setup.ts @@ -0,0 +1,6 @@ +import chai from "chai"; +import chaiAsPromised from "chai-as-promised"; + +chai.use(chaiAsPromised); + +chai.config.truncateThreshold = 0; diff --git a/packages/hardhat-viem/test/update-snapshots.ts b/packages/hardhat-viem/test/update-snapshots.ts new file mode 100644 index 0000000000..03540b8a57 --- /dev/null +++ b/packages/hardhat-viem/test/update-snapshots.ts @@ -0,0 +1,53 @@ +import path from "path"; +import fs from "fs"; + +import { TASK_COMPILE, TASK_CLEAN } from "hardhat/builtin-tasks/task-names"; +import { resetHardhatContext } from "hardhat/plugins-testing"; + +const snapshotPartialPaths = [ + "artifacts.d.ts", + path.join("contracts", "A.sol", "A.d.ts"), + path.join("contracts", "A.sol", "B.d.ts"), + path.join("contracts", "A.sol", "artifacts.d.ts"), + path.join("contracts", "C.sol", "B.d.ts"), + path.join("contracts", "C.sol", "C.d.ts"), + path.join("contracts", "C.sol", "artifacts.d.ts"), +]; + +const originalCwd = process.cwd(); + +async function updateSnapshots() { + process.chdir(path.join(__dirname, "fixture-projects", "type-generation")); + process.env.HARDHAT_NETWORK = "hardhat"; + + const hre = require("hardhat"); + await hre.run(TASK_COMPILE, { quiet: true }); + + snapshotPartialPaths.forEach((partialPath) => { + const snapshotPath = path.join(process.cwd(), "snapshots", partialPath); + const generatedFilePath = path.join( + process.cwd(), + "artifacts", + partialPath + ); + + fs.copyFileSync(generatedFilePath, snapshotPath); + }); + + await hre.run(TASK_CLEAN); + + process.chdir(path.resolve(`${__dirname}/..`)); + resetHardhatContext(); + delete process.env.HARDHAT_NETWORK; + + console.log("Snapshots updated!"); +} + +updateSnapshots() + .catch((error) => { + console.error(error); + process.exitCode = 1; + }) + .finally(() => { + process.chdir(originalCwd); + }); diff --git a/packages/hardhat-viem/tsconfig.json b/packages/hardhat-viem/tsconfig.json new file mode 100644 index 0000000000..65ecfc33b9 --- /dev/null +++ b/packages/hardhat-viem/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../config/typescript/tsconfig.json", + "compilerOptions": { + "outDir": "./build-test", + "rootDirs": ["./test"], + "composite": true + }, + "include": ["./test/**/*.ts"], + "exclude": ["./node_modules", "./test/**/hardhat.config.ts"], + "references": [ + { + "path": "./src" + } + ] +} diff --git a/scripts/check-dependencies.js b/scripts/check-dependencies.js index a9012784e5..f71455600b 100644 --- a/scripts/check-dependencies.js +++ b/scripts/check-dependencies.js @@ -168,8 +168,11 @@ function main() { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); - // temporarily ignore hardhat-toolbox - if (packageJson.name === "@nomicfoundation/hardhat-toolbox") { + // temporarily ignore hardhat toolboxs + if ( + packageJson.name === "@nomicfoundation/hardhat-toolbox" || + packageJson.name === "@nomicfoundation/hardhat-toolbox-viem" + ) { continue; } diff --git a/yarn.lock b/yarn.lock index 14747800fd..a76b80e042 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,11 @@ resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz#60111a5d9db45b2e5cbb6231b0bb8d97e8659316" integrity sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg== +"@adraffy/ens-normalize@1.9.4": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.4.tgz#aae21cb858bbb0411949d5b7b3051f4209043f62" + integrity sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw== + "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -943,6 +948,13 @@ dependencies: "@sinclair/typebox" "^0.27.8" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/types@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" @@ -1133,6 +1145,13 @@ tweetnacl "^1.0.3" tweetnacl-util "^0.15.1" +"@noble/curves@1.2.0", "@noble/curves@~1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + "@noble/hashes@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.2.tgz#e9e035b9b166ca0af657a7848eb2718f0f22f183" @@ -1143,6 +1162,16 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== +"@noble/hashes@1.3.2", "@noble/hashes@~1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + +"@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" @@ -1390,6 +1419,11 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== +"@scure/base@~1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.3.tgz#8584115565228290a6c6c4961973e0903bb3df2f" + integrity sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q== + "@scure/bip32@1.1.5": version "1.1.5" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.1.5.tgz#d2ccae16dcc2e75bc1d75f5ef3c66a338d1ba300" @@ -1399,6 +1433,15 @@ "@noble/secp256k1" "~1.7.0" "@scure/base" "~1.1.0" +"@scure/bip32@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.2.tgz#90e78c027d5e30f0b22c1f8d50ff12f3fb7559f8" + integrity sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA== + dependencies: + "@noble/curves" "~1.2.0" + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.2" + "@scure/bip39@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5" @@ -1407,6 +1450,14 @@ "@noble/hashes" "~1.2.0" "@scure/base" "~1.1.0" +"@scure/bip39@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" + integrity sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg== + 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" @@ -1710,6 +1761,13 @@ dependencies: "@types/chai" "*" +"@types/chai-as-promised@^7.1.6": + version "7.1.6" + resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.6.tgz#3b08cbe1e7206567a480dc6538bade374b19e4e1" + integrity sha512-cQLhk8fFarRVZAXUQV1xEnZgMoPxqKojBvRkqPCKPQCzEhpbbSKl1Uu75kDng7k5Ln6LQLUmNBjLlFthCgm1NA== + dependencies: + "@types/chai" "*" + "@types/chai@*", "@types/chai@^4.2.0": version "4.3.5" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.5.tgz#ae69bcbb1bebb68c4ac0b11e9d8ed04526b3562b" @@ -1831,6 +1889,13 @@ dependencies: "@types/lodash" "*" +"@types/lodash.memoize@^4.1.7": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@types/lodash.memoize/-/lodash.memoize-4.1.7.tgz#aff94ab32813c557cbc1104e127030e3d60a3b27" + integrity sha512-lGN7WeO4vO6sICVpf041Q7BX/9k1Y24Zo3FY0aUezr1QlKznpjzsDk3T3wvH8ofYzoK0QupN9TWcFAFZlyPwQQ== + dependencies: + "@types/lodash" "*" + "@types/lodash@*", "@types/lodash@^4.14.123": version "4.14.197" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.197.tgz#e95c5ddcc814ec3e84c891910a01e0c8a378c54b" @@ -1994,6 +2059,13 @@ dependencies: "@types/node" "*" +"@types/ws@^8.5.5": + version "8.5.6" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.6.tgz#e9ad51f0ab79b9110c50916c9fcbddc36d373065" + integrity sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" @@ -2148,6 +2220,11 @@ abbrev@1.0.x: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" integrity sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q== +abitype@0.9.8, abitype@^0.9.8: + version "0.9.8" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.9.8.tgz#1f120b6b717459deafd213dfbf3a3dd1bf10ae8c" + integrity sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ== + abortcontroller-polyfill@^1.7.3: version "1.7.5" resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz#6738495f4e901fbb57b6c0611d0c75f76c485bed" @@ -3759,6 +3836,11 @@ diff-sequences@^29.4.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + diff@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -4458,6 +4540,7 @@ ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.2, ethereum rlp "^2.2.4" "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== @@ -5969,6 +6052,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" @@ -6053,11 +6141,26 @@ jest-diff@^29.2.1: jest-get-type "^29.4.3" pretty-format "^29.6.2" +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-get-type@^29.4.3: version "29.4.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + js-sdsl@^4.1.4: version "4.4.2" resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.2.tgz#2e3c031b1f47d3aca8b775532e3ebb0818e7f847" @@ -6396,6 +6499,11 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -7803,6 +7911,15 @@ pretty-format@^29.6.2: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + pretty-ms@^0.2.1: version "0.2.2" resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-0.2.2.tgz#da879a682ff33a37011046f13d627f67c73b84f6" @@ -9613,7 +9730,7 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@~5.0.0: +typescript@~5.0.0, typescript@~5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== @@ -9811,6 +9928,21 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +viem@^1.15.1: + version "1.15.1" + resolved "https://registry.yarnpkg.com/viem/-/viem-1.15.1.tgz#030fb900d8099e6a1bd16164fa4e1e5b8e24607a" + integrity sha512-lxk8wwUK7ZivYAUZ6pH+9Y6jjrfXXjafCOoASa4lw3ULUCT2BajU4SELarlxJQimpsFd7OZD4m4iEXYLF/bt6w== + dependencies: + "@adraffy/ens-normalize" "1.9.4" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@scure/bip32" "1.3.2" + "@scure/bip39" "1.2.1" + "@types/ws" "^8.5.5" + abitype "0.9.8" + isomorphic-ws "5.0.0" + ws "8.13.0" + wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" @@ -10238,6 +10370,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.13.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + ws@8.5.0: version "8.5.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"