vscode+solidity+hardhat 开发

331 阅读11分钟

vscode+solidity+hardhat 开发

环境安装

Hardhat官网

创建项目

  • 初始化仓库
> yarn init
yarn init v1.22.22
question name (hardhat-simple-storage-fcc):
question version (1.0.0):
question description:
question entry point (index.js):
question repository url:
question author: yourself
question license (MIT):
question private:
success Saved package.json
Done in 15.23s.

执行成功后生成 package.json文件:

{
  "name": "hardhat-simple-storage-fcc",
  "version": "1.0.0",
  "main": "index.js",
  "author": "yourself",
  "license": "MIT"
}
  • 给项目添加 hardhat
> yarn add --dev hardhat

网络不好时需要很久,安装完后 package.json 文件变化:

{
  "devDependencies": {
    "hardhat": "^2.22.19"
  }
}
  • hardhat 配置初始化: 添加依赖及配置项.
> yarn hardhat          
888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888

Welcome to Hardhat v2.22.19

√ What do you want to do? · Create a JavaScript project
√ Hardhat project root: · D:\workspace\src\solidity\hh-fcc\hardhat-simple-storage-fcc
√ Do you want to add a .gitignore? (Y/n) · y
√ Do you want to install this sample project's dependencies with yarn (@nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-verify chai hardhat-gas-reporter solidity-coverage @nomicfoundation/hardhat-ignition @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-chai-matchers @nomicfoundation/hardhat-ethers ethers @typechain/hardhat typechain @typechain/ethers-v6 @nomicfoundation/hardhat-ignition-ethers)? (Y/n) · y



yarn add --dev "@nomicfoundation/hardhat-network-helpers@^1.0.0" "@nomicfoundation/hardhat-verify@^2.0.0" "chai@^4.2.0" "hardhat-gas-reporter@^1.0.8" "solidity-coverage@^0.8.0" "@nomicfoundation/hardhat-ignition@^0.15.0" "@nomicfoundation/hardhat-toolbox@^5.0.0" "@nomicfoundation/hardhat-chai-matchers@^2.0.0" "@nomicfoundation/hardhat-ethers@^3.0.0" "ethers@^6.4.0" "@typechain/hardhat@^9.0.0" "typechain@^8.3.0" "@typechain/ethers-v6@^0.5.0" "@nomicfoundation/hardhat-ignition-ethers@^0.15.0"
➤ YN0000: · Yarn 4.6.0
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + @nomicfoundation/hardhat-chai-matchers@npm:2.0.8, @nomicfoundation/hardhat-ethers@npm:3.0.8, @nomicfoundation/hardhat-ignition-ethers@npm:0.15.10, and 270 more.    
➤ YN0000: └ Completed in 9s 519ms
➤ YN0000: ┌ Post-resolution validation
➤ YN0002: │ hardhat-simple-storage-fcc@workspace:. doesn't provide @nomicfoundation/ignition-core (p3e4f1), requested by @nomicfoundation/hardhat-ignition-ethers.
➤ YN0002: │ hardhat-simple-storage-fcc@workspace:. doesn't provide @types/chai (p49220), requested by @nomicfoundation/hardhat-chai-matchers and other dependencies.
➤ YN0002: │ hardhat-simple-storage-fcc@workspace:. doesn't provide @types/mocha (p7fc22), requested by @nomicfoundation/hardhat-toolbox.
➤ YN0002: │ hardhat-simple-storage-fcc@workspace:. doesn't provide @types/node (p89ace), requested by @nomicfoundation/hardhat-toolbox.
➤ YN0002: │ hardhat-simple-storage-fcc@workspace:. doesn't provide ts-node (p9b6d1), requested by @nomicfoundation/hardhat-toolbox and other dependencies.
➤ YN0002: │ hardhat-simple-storage-fcc@workspace:. doesn't provide typescript (p0d7e8), requested by @nomicfoundation/hardhat-toolbox and other dependencies.
➤ YN0086: │ Some peer dependencies are incorrectly met by your project; run yarn explain peer-requirements <hash> for details, where <hash> is the six-letter p-prefixed code.    
➤ YN0000: └ Completed
➤ YN0000: ┌ Fetch step
➤ YN0013: │ A package was added to the project (+ 2.89 MiB).
➤ YN0000: └ Completed in 6s 173ms
➤ YN0000: ┌ Link step
➤ YN0000: │ ESM support for PnP uses the experimental loader API and is therefore experimental
➤ YN0008: │ keccak@npm:3.0.4 must be rebuilt because its dependency tree changed
➤ YN0008: │ secp256k1@npm:4.0.4 must be rebuilt because its dependency tree changed
➤ YN0000: │ [Warning] The runtime detected new information in a PnP file; reloading the API instance (D:\workspace\src\solidity\hh-fcc\hardhat-simple-storage-fcc\.pnp.cjs)       
➤ YN0000: └ Completed in 0s 875ms
➤ YN0000: · Done with warnings in 16s 654ms

Project created

See the README.md file for some example tasks you can run

Give Hardhat a star on Github if you're enjoying it!

     https://github.com/NomicFoundation/hardhat


DEPRECATION WARNING

 Initializing a project with npx hardhat is deprecated and will be removed in the future.
 Please use npx hardhat init instead.

安装后再次执行 yarn hardhat 查看扩展的命令:

> yarn hardhat 
Hardhat version 2.22.19

Usage: hardhat [GLOBAL OPTIONS] [SCOPE] <TASK> [TASK OPTIONS]

GLOBAL OPTIONS:

AVAILABLE TASKS:

  check                 Check whatever you need
  ...

To get help for a specific task run: npx hardhat help [SCOPE] <TASK>
  • 创建合约
// SPDX-License-Identifier: MIT

pragma solidity 0.8.28;

contract SimpleStorage {
    uint256 favoriteNumber;

    struct People {
        uint256 favoriteNumber;
        string name;
    }

    // uint256[] public anArray;
    People[] public people;

    mapping(string => uint256) public nameToFavoriteNumber;

    function store(uint256 _favoriteNumber) public {
        favoriteNumber = _favoriteNumber;
    }

    function retrieve() public view returns (uint256) {
        return favoriteNumber;
    }

    function addPerson(string memory _name, uint256 _favoriteNumber) public {
        people.push(People(_favoriteNumber, _name));
        nameToFavoriteNumber[_name] = _favoriteNumber;
    }
}

  • 编译合约
> yarn hardhat compile
Compiled 1 Solidity file successfully (evm target: paris).
  • 安装 prettier 插件
> yarn add --dev prettier prettier-plugin-solidity
➤ YN0000: · Yarn 4.6.0
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + prettier-plugin-solidity@npm:1.4.2, prettier@npm:3.5.2
➤ YN0000: └ Completed in 0s 484ms
➤ YN0000: ┌ Post-resolution validation
➤ YN0002: │ hardhat-simple-storage-fcc@workspace:. doesn't provide @nomicfoundation/ignition-core (p3e4f1), requested by @nomicfoundation/hardhat-ignition-ethers.
➤ YN0002: │ hardhat-simple-storage-fcc@workspace:. doesn't provide @types/chai (p49220), requested by @nomicfoundation/hardhat-chai-matchers and other dependencies.
➤ YN0002: │ hardhat-simple-storage-fcc@workspace:. doesn't provide @types/mocha (p7fc22), requested by @nomicfoundation/hardhat-toolbox.
➤ YN0002: │ hardhat-simple-storage-fcc@workspace:. doesn't provide @types/node (p89ace), requested by @nomicfoundation/hardhat-toolbox.
➤ YN0002: │ hardhat-simple-storage-fcc@workspace:. doesn't provide ts-node (p9b6d1), requested by @nomicfoundation/hardhat-toolbox and other dependencies.
➤ YN0002: │ hardhat-simple-storage-fcc@workspace:. doesn't provide typescript (p0d7e8), requested by @nomicfoundation/hardhat-toolbox and other dependencies.
➤ YN0086: │ Some peer dependencies are incorrectly met by your project; run yarn explain peer-requirements <hash> for details, where <hash> is the six-letter p-prefixed code.    
➤ YN0000: └ Completed
➤ YN0000: ┌ Fetch step
➤ YN0013: │ 2 packages were added to the project (+ 9.44 MiB).
➤ YN0000: └ Completed in 3s 766ms
➤ YN0000: ┌ Link step
➤ YN0000: │ ESM support for PnP uses the experimental loader API and is therefore experimental
➤ YN0000: └ Completed
➤ YN0000: · Done with warnings in 4s 519ms
  • 部署合约

部署合约脚本:

// imports
const { ethers } = require('hardhat');

async function main() {
    const simpleStorageFactory =
        await ethers.getContractFactory('SimpleStorage');

    console.log('Deploying SimpleStorage...');
    const simpleStorageContract = await simpleStorageFactory.deploy();

    await simpleStorageContract.waitForDeployment();
    console.log(`Deployed contract to: ${simpleStorageContract.target}`);
}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });

> yarn hardhat run ./scripts/deploy.js

hardhat network

hardhat 会提供默认网络, 附带 rpcurlwallet 等必要元件.

module.exports = {
    // defaultNetwork: 'hardhat',
    solidity: '0.8.28',
};

也可以在部署命令中明确指定运行的网络(network):

yarn hardhat run ./scripts/deploy.js --network hardhat
  • 设置网络
# 添加 `dotenv`
yarn add --dev dotenv

hardhat.config.js文件修改:

require('@nomicfoundation/hardhat-toolbox');
require('dotenv').config();

const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL;
const PRIVATE_KEY = '0x' + process.env.PRIVATE_KEY;

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
    defaultNetwork: 'hardhat',
    networks: {
        sepolia: {
            url: SEPOLIA_RPC_URL,
            accounts: [PRIVATE_KEY],
            chainId: 11155111,
        },
    },
    solidity: '0.8.28',
};

查询网络的 chainId

SEPOLIA_RPC_URL 是第三方机构提供的合约部署服务,如 metamask的为https://sepolia.infura.io/v3/YOUR-API-KEY, alchemy 的为 https://eth-sepolia.g.alchemy.com/v2/YOUR-API-KEY.根据实际需求自行申请.

MetaMask developer API-KEY 申请

使用hardhat-deploy 部署合约

hardhat-deploy

  • 安装 chainlink,非必要,只有需要获得代币价格时需要(ETH/USD).
yarn add --dev @chainlink/contracts
  • 安装 hardhat-deploy
yarn add --dev hardhat-deploy

hardhat.config.js中导入:

require("hardhat-deploy")

成功后hardhat命令行会多出deploy任务.

  • 安装 @nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers
yarn add --dev @nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers ethers
  • 部署代码

helper-hardhat-config.js 代码:

const networkConfig = {
    31337: {
        name: "localhost",
    },
    // Price Feed Address, values can be obtained at https://docs.chain.link/data-feeds/price-feeds/addresses
    11155111: {
        name: "sepolia",
        ethUsdPriceFeed: "0x694AA1769357215DE4FAC081bf1f309aDC325306",
    },
}

const developmentChains = ["hardhat", "localhost"]

module.exports = {
    networkConfig,
    developmentChains,
}

00-deploy-mocks.js 代码:

const { network } = require("hardhat")
const { developmentChains } = require("../helper-hardhat-config")

const DECIMALS = "8"
const INITIAL_ANSWER = "200000000000" // 2000

module.exports = async ({ deployments, getNamedAccounts }) => {
    const { deploy, log } = deployments
    const { deployer } = await getNamedAccounts()
    const chainId = network.config.chainId

    // If we are on a local development network, we need to deploy mocks!
    if (developmentChains.includes(network.name)) {
        log("Local network detected! Deploying mocks...")
        await deploy("MockV3Aggregator", {
            contract: "MockV3Aggregator",
            from: deployer,
            log: true,
            args: [DECIMALS, INITIAL_ANSWER],
        })
        log("Mocks Deployed!")
        log("------------------------------------------------")
        log(
            "You are deploying to a local network, you'll need a local network running to interact"
        )
        log(
            "Please run `npx hardhat console` to interact with the deployed smart contracts!"
        )
        log("------------------------------------------------")
    }
}
module.exports.tags = ["all", "mocks"] // deplog 命令行中的 tags

MockV3Aggregator合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

// contracts/src/v0.8/shared/mocks/MockV3Aggregator.sol
import "@chainlink/contracts/src/v0.6/tests/MockV3Aggregator.sol";

  • 部署
> yarn hardhat deploy --tags mocks                                
Compiling 1 file with 0.6.6
Solidity compilation finished successfully
Local network detected! Deploying mocks...
deploying "MockV3Aggregator" (tx: 0x2f60bd4cba5dffe33cd22380f4891cfadb7f13aad763bb084e8a1c3336b892f9)...: deployed at 0x5FbDB2315678afecb367f032d93F642f64180aa3 with 569635 gas
Mocks Deployed!
------------------------------------------------
You are deploying to a local network, you'll need a local network running to interact
Please run `npx hardhat console` to interact with the deployed smart contracts!
------------------------------------------------

执行 yarn hardhat node 会执行所有的部署合约脚本.

与已有合约交互

  • 编写fund.js 与已有合约交互
const { ethers, getNamedAccounts } = require("hardhat")

async function main() {
  const { deployer } = await getNamedAccounts()
  const fundMe = await ethers.getContract("FundMe", deployer)
  console.log(`Got contract FundMe at ${fundMe.address}`)
  console.log("Funding contract...")
  const transactionResponse = await fundMe.fund({
    value: ethers.utils.parseEther("0.1"),
  })
  await transactionResponse.wait()
  console.log("Funded!")
  const fundMeBalance = await fundMe.provider.getBalance(fundMe.address)
  const fundMeBalanceInEther = ethers.utils.formatEther(fundMeBalance)
  console.log(`FundMe balance: ${fundMeBalanceInEther}`)
}

main()
        .then(() => process.exit(0))
        .catch((error) => {
          console.error(error)
          process.exit(1)
        })

  • 启动本地节点,部署FundMe合约
> yarn hardhat node
Nothing to compile
Local network detected! Deploying mocks...
deploying "MockV3Aggregator" (tx: 0x2f60bd4cba5dffe33cd22380f4891cfadb7f13aad763bb084e8a1c3336b892f9)...: deployed at 0x5FbDB2315678afecb367f032d93F642f64180aa3 with 569635 gas
Mocks Deployed!
------------------------------------------------
You are deploying to a local network, you'll need a local network running to interact
Please run `npx hardhat console` to interact with the deployed smart contracts!
------------------------------------------------
chainId:  31337
----------------------------------------------------
Deploying FundMe and waiting for confirmations...
deploying "FundMe" (tx: 0x6a97ceb3049b38b6e4edcd8f4d92794fbbcdb62b3ece28825ff4be7e84e16cae)...: deployed at 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 with 1096977 gas
FundMe deployed at 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
----------------------------------------------------
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/

Accounts
========

WARNING: These accounts, and their private keys, are publicly known.
Any funds sent to them on Mainnet or any other live network WILL BE LOST.

Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)
Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

Account #1: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 (10000 ETH)
Private Key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d

Account #2: 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc (10000 ETH)
Private Key: 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a

Account #3: 0x90f79bf6eb2c4f870365e785982e1f101e93b906 (10000 ETH)
Private Key: 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6

Account #4: 0x15d34aaf54267db7d7c367839aaf71a00a2c6a65 (10000 ETH)
Private Key: 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a

Account #5: 0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc (10000 ETH)
Private Key: 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba

Account #6: 0x976ea74026e726554db657fa54763abd0c3a0aa9 (10000 ETH)
Private Key: 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e

Account #7: 0x14dc79964da2c08b23698b3d3cc7ca32193d9955 (10000 ETH)
Private Key: 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356

Account #8: 0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f (10000 ETH)
Private Key: 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97

Account #9: 0xa0ee7a142d267c1f36714e4a8f75612f20a79720 (10000 ETH)
Private Key: 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6

Account #10: 0xbcd4042de499d14e55001ccbb24a551f3b954096 (10000 ETH)
Private Key: 0xf214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897

Account #11: 0x71be63f3384f5fb98995898a86b02fb2426c5788 (10000 ETH)
Private Key: 0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82

Account #12: 0xfabb0ac9d68b0b445fb7357272ff202c5651694a (10000 ETH)
Private Key: 0xa267530f49f8280200edf313ee7af6b827f2a8bce2897751d06a843f644967b1

Account #13: 0x1cbd3b2770909d4e10f157cabc84c7264073c9ec (10000 ETH)
Private Key: 0x47c99abed3324a2707c28affff1267e45918ec8c3f20b8aa892e8b065d2942dd

Account #14: 0xdf3e18d64bc6a983f673ab319ccae4f1a57c7097 (10000 ETH)
Private Key: 0xc526ee95bf44d8fc405a158bb884d9d1238d99f0612e9f33d006bb0789009aaa

Account #15: 0xcd3b766ccdd6ae721141f452c550ca635964ce71 (10000 ETH)
Private Key: 0x8166f546bab6da521a8369cab06c5d2b9e46670292d85c875ee9ec20e84ffb61

Account #16: 0x2546bcd3c84621e976d8185a91a922ae77ecec30 (10000 ETH)
Private Key: 0xea6c44ac03bff858b476bba40716402b03e41b8e97e276d1baec7c37d42484a0

Account #17: 0xbda5747bfd65f08deb54cb465eb87d40e51b197e (10000 ETH)
Private Key: 0x689af8efa8c651a91ad287602527f3af2fe9f6501a7ac4b061667b5a93e037fd

Account #18: 0xdd2fd4581271e230360230f9337d5c0430bf44c0 (10000 ETH)
Private Key: 0xde9be858da4a475276426320d5e9262ecfc3ba460bfac56360bfa6c4c28b4ee0

Account #19: 0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199 (10000 ETH)
Private Key: 0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e

WARNING: These accounts, and their private keys, are publicly known.
Any funds sent to them on Mainnet or any other live network WILL BE LOST.

web3_clientVersion
  Contract deployment: MockV3Aggregator
  Contract address:    0x5fbdb2315678afecb367f032d93f642f64180aa3
  Transaction:         0x2f60bd4cba5dffe33cd22380f4891cfadb7f13aad763bb084e8a1c3336b892f9
  From:                0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
  Value:               0 ETH
  Gas used:            569635 of 569635
  Block #1:            0xe01b11ac8eb1061844456c9179f1b11e10c1b650ec7769b51aad43c1379a4e10
  Contract deployment: FundMe
  Contract address:    0xe7f1725e7734ce288f8367e1bb143e90bb3f0512
  Transaction:         0x6a97ceb3049b38b6e4edcd8f4d92794fbbcdb62b3ece28825ff4be7e84e16cae
  From:                0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
  Value:               0 ETH
  Gas used:            1096977 of 1096977
  Block #2:            0x51151ed3ba29f736405864c7dbc498bba0f7cf5c7f3d4b522be874ecfc30a0cd
  Contract deployment: FunWithStorage
  Contract address:    0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0
  Transaction:         0x474ff007295b40fb495744377d4689540b1d451396087b6d78cdea7b264186d1
  From:                0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
  Value:               0 ETH
  Gas used:            253434 of 253434
  Block #3:            0x202816719c9340efd77bf8f8e28a0e10ca454c5e51f06c6f994b0e0770d7a095

eth_chainId
eth_accounts
eth_chainId
eth_blockNumber
eth_chainId (2)
eth_estimateGas
eth_getBlockByNumber
eth_feeHistory
eth_sendTransaction
  Contract call:       FundMe#fund
  Transaction:         0x851a9775d49ce122a2322fa4537d5ab27e54bf3deac2702b9c95df149d7e7bc3
  From:                0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
  To:                  0xe7f1725e7734ce288f8367e1bb143e90bb3f0512
  Value:               0.1 ETH
  Gas used:            104466 of 104466
  Block #4:            0x3d67874aa549e60cfe5e9922c02ec18b12d0cf00c137a64b42cd6e1dd4a2df3a

eth_chainId
eth_getTransactionByHash
eth_chainId
eth_getTransactionReceipt
web3_clientVersion
eth_chainId
eth_accounts
eth_chainId
eth_blockNumber
eth_chainId (2)
eth_estimateGas
eth_getBlockByNumber
eth_feeHistory
eth_sendTransaction
  Contract call:       FundMe#withdraw
  Transaction:         0x589d82f64e1cde435396146da7a25c2671729473d878c2b29690057c080f2d11
  From:                0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
  To:                  0xe7f1725e7734ce288f8367e1bb143e90bb3f0512
  Value:               0 ETH
  Gas used:            35622 of 46753
  Block #5:            0xf72bf987b6214a20a957e63f75151cc465533bde7527d70a3de916f3193d9f92

eth_chainId
eth_getTransactionByHash
eth_chainId
eth_getTransactionReceipt
web3_clientVersion
eth_chainId
eth_accounts
eth_chainId
eth_blockNumber
eth_chainId (2)
eth_estimateGas
eth_getBlockByNumber
eth_feeHistory
eth_sendTransaction
  Contract call:       FundMe#fund
  Transaction:         0x2a0941a93c42f250e9441222eae63cb39dc2e290fd240713ed9b2c93e77d1a39
  From:                0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
  To:                  0xe7f1725e7734ce288f8367e1bb143e90bb3f0512
  Value:               0.1 ETH
  Gas used:            104466 of 104466
  Block #6:            0x7f28919a532fdd95a3d21a74327f848d1973a0c356dd4707fc48a0e29f7d9da3

eth_chainId
eth_getTransactionByHash
eth_chainId
eth_getTransactionReceipt
eth_chainId
eth_getBalance
web3_clientVersion
eth_chainId
eth_accounts
eth_chainId
eth_blockNumber
eth_chainId (2)
eth_estimateGas
eth_getBlockByNumber
eth_feeHistory
eth_sendTransaction
  Contract call:       FundMe#fund
  Transaction:         0x851cfbc1b42228e020af0b8693750826f21712d46e436791109f6bfa684953bf
  From:                0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
  To:                  0xe7f1725e7734ce288f8367e1bb143e90bb3f0512
  Value:               0.1 ETH
  Gas used:            70266 of 70266
  Block #7:            0x78e2d81458674078bee923e0a1b445f1424491a74e8d02c6c0b2ebf25731c14e

eth_chainId
eth_getTransactionByHash
eth_chainId
eth_getTransactionReceipt
eth_chainId
eth_getBalance
web3_clientVersion
eth_chainId
eth_accounts
eth_chainId
eth_blockNumber
eth_chainId (2)                                                                                                                                                        
eth_estimateGas
eth_getBlockByNumber
eth_feeHistory
eth_sendTransaction
  Contract call:       FundMe#fund
  Transaction:         0xd77c0f8ad2bf30ec9498e51555f56a7f95c0ec826f065f70a3dccaa47fcb1924
  From:                0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
  To:                  0xe7f1725e7734ce288f8367e1bb143e90bb3f0512
  Value:               0.1 ETH
  Gas used:            70266 of 70266
  Block #8:            0x47a687c961da2c7ac255944d997d3c03e1da53c52307de87a868acbca9690da0

eth_chainId
eth_getTransactionByHash
eth_chainId
eth_getTransactionReceipt
eth_chainId
eth_getBalance
  • 交互
> yarn hardhat run ./scripts/fund.js --network localhost
Got contract FundMe at 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
Funding contract...
Funded!
FundMe balance: 0.3

验证合约

require('@nomicfoundation/hardhat-verify');

const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY;

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
    etherscan: {
        // Your API key for Etherscan
        // Obtain one at https://etherscan.io/
        apiKey: ETHERSCAN_API_KEY,
    },
};

调用浏览器提供的 API 进行验证: https://docs.etherscan.io/sepolia-etherscan/api-endpoints/contracts

通过 hardhat 的插件进行验证: hardhat-verify

自定义验证函数:

// imports
const { run } = require('hardhat');

async function main() {
    // others
    
    // Verify contract
    if (network.config.chainId === 11155111 && process.env.ETHERSCAN_API_KEY) {
        console.log('Waiting for block confirmations...');

        // Not functionable in version 6^ ethers ----->

        await simpleStorageContract.deploymentTransaction().wait(6);
        await verify(simpleStorageContract.target, []);

        //______________________________________________
    }
}

async function verify(contractAddress, args) {
    console.log('Verifying contract...');

    try {
        await run('verify:verify', {
            address: contractAddress,
            constructorArguments: args,
        });
    } catch (error) {
        if (error instanceof Error) {
            if (error.message.toLowerCase().includes('already verified')) {
                console.log('Contract already verified');
            }
            console.error('Failed to verify contract:', error.message || error);
        } else {
            console.error('Unexpected error:', error);
        }
    }
}

合约部署并验证:

> yarn hardhat run ./scripts/deploy.js --network sepolia
Deploying to network: sepolia
Deploying SimpleStorage...
Deployed contract to: 0xDB141Aa5fcccFd638f221e3009ba9E91Ca97eA59
Waiting for block confirmations...
Verifying contract...
Failed to verify contract: A network request failed. This is an error from the block explorer, not Hardhat. Error: Connect Timeout Error
Current favorite number: 0
Updated favorite number: 13

创建 hardhat task

类似golang服务的命令行.

  • 添加任务
const { task } = require('hardhat/config');

task(
    'block-number',
    'Prints the current block number',
    async (_, { ethers }) => {
        const blockNumber = await ethers.provider.getBlockNumber();
        console.log('Current block number: ', blockNumber);
    },
);

// task('block-number', 'Prints the current block number').setAction(
//     // async function blockNumber() {}
//     // const blockNumber = async function() => {}
//     async (_, hre) => {
//         const blockNumber = await hre.ethers.provider.getBlockNumber();
//         console.log(`Current block number: ${blockNumber}`);
//     },
// );

task('accounts', 'Prints the list of accounts', async (taskArgs, hre) => {
    const accounts = await hre.ethers.getSigners();

    for (const account of accounts) {
        console.log(account.address);
    }
});

task('balance', "Prints an account's balance")
    .addParam('account', "The account's address")
    .setAction(async (taskArgs) => {
        const balance = await ethers.provider.getBalance(taskArgs.account);
        console.log(ethers.formatEther(balance), 'ETH');
    });
  • 导入
// hardhat.config.js

require('./tasks/balance');
require('./tasks/accounts');
require('./tasks/block-number');
  • 执行
> yarn hardhat balance --account 0x28c03C558980988F858BD77B0Fd9F94826E99A15 --network sepolia
0.03849461495766039 ETH

> yarn hardhat block-number --network sepolia
Current block number:  7794057

> yarn hardhat accounts --network sepolia
0x28c03C558980988F858BD77B0Fd9F94826E99A15

hardhat 本地节点

使用 default hardhat 的网络配置,在本地启动一个节点(是一个独立的网络).

> yarn hardhat node

配置:

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
    networks: {
        localhost: {
            url: 'http://127.0.0.1:8545',
            // accounts: from hardhat.config
            chainId: 31337,
        },
    },
};

hardhat 控制台

类似 mysql 服务的控制台,可按命令行与合约交互.

> yarn hardhat console                       
Welcome to Node.js v18.17.1.
Type ".help" for more information.
> const simpleStorageFactory = await ethers.getContractFactory('SimpleStorage');
undefined
> const simpleStorageContract = await simpleStorageFactory.deploy();
undefined
> await simpleStorageContract.waitForDeployment();
BaseContract {
  target: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
  interface: Interface {
    fragments: [
      [FunctionFragment],
      [FunctionFragment],
      [FunctionFragment],
      [FunctionFragment],
      [FunctionFragment]
    ],
    deploy: ConstructorFragment {
      type: 'constructor',
      inputs: [],
      payable: false,
      gas: null
    },
    fallback: null,
    receive: false
  },
  runner: HardhatEthersSigner {
    _gasLimit: 30000000,
    address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
    provider: HardhatEthersProvider {
      _hardhatProvider: [LazyInitializationProviderAdapter],
      _networkName: 'hardhat',
      _blockListeners: [],
      _transactionHashListeners: Map(0) {},
      _eventListeners: []
    }
  },
  filters: {},
  fallback: null,
  [Symbol(_ethersInternal_contract)]: {}
}
> await simpleStorageContract.retrieve();
0n
> await simpleStorageContract.store(100);
ContractTransactionResponse {
  provider: HardhatEthersProvider {
    _hardhatProvider: LazyInitializationProviderAdapter {
      _providerFactory: [AsyncFunction (anonymous)],
      _emitter: [EventEmitter],
      _initializingPromise: [Promise],
      provider: [BackwardsCompatibilityProviderAdapter]
    },
    _networkName: 'hardhat',
    _blockListeners: [],
    _transactionHashListeners: Map(0) {},
    _eventListeners: []
  },
  blockNumber: 2,
  blockHash: '0xc447e66f8f77a519c4cafa0b5ade88c79f59a74003cf2823d3c85e06ed18306c',
  index: undefined,
  hash: '0xf9830c6cb21deb814fa991c78fc164b05881b417aa662824cec262215e04e494',
  type: 2,
  to: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
  from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  nonce: 1,
  gasLimit: 30000000n,
  gasPrice: 1769727927n,
  maxPriorityFeePerGas: 1000000000n,
  maxFeePerGas: 2539455854n,
  maxFeePerBlobGas: null,
  data: '0x6057361d0000000000000000000000000000000000000000000000000000000000000064',
  value: 0n,
  chainId: 31337n,
  signature: Signature { r: "0x35ec79d17c634bc3f2d12a86825acbfba55438a9ab96d40606acc7c34227aff9", s: "0x7d2f300f8d874bd5f65d5195c9e9851c3afbc4bb0e985acc2d54a489d04d00ea", yParity: 1, networkV: null },
  accessList: [],
  blobVersionedHashes: null
}
> await simpleStorageContract.retrieve();
100n

测试合约

测试案例

  • 编写测试
const { assert, expect } = require('chai');
const { ethers } = require('hardhat');

// Vanilla Mocha test. Increased compatibility with tools that integrate Mocha.
// describe("SimpleStorage", () => {})
describe('SimpleStorage', function () {
    let simpleStorageFactory;
    let simpleStorageContract;

    beforeEach(async function () {
        // Get the contract factory
        simpleStorageFactory = await ethers.getContractFactory('SimpleStorage');
        simpleStorageContract = await simpleStorageFactory.deploy();
        await simpleStorageContract.waitForDeployment();
    });

    it('Should start with a favorite number of 0', async function () {
        const currentFavoriteNumber = await simpleStorageContract.retrieve();

        // assert.equal(currentFavoriteNumber, 0);
        // expect(currentFavoriteNumber == 10).to.be.false;
        // expect(currentFavoriteNumber == 0).to.be.true;
        expect(currentFavoriteNumber).to.equal(0);
    });

    it('Should update when we call store', async function () {
        const newFavoriteNumber = 13;
        let transactionResponse =
            await simpleStorageContract.store(newFavoriteNumber);
        await transactionResponse.wait(1);

        const currentFavoriteNumber = await simpleStorageContract.retrieve();

        assert.equal(currentFavoriteNumber, newFavoriteNumber);
    });

    it('Should work correctly with the people struct and array', async function () {
        // Add a person
        let expectPersionName = 'Alice';
        let expectFavoriteNumber = '23';
        let transactionResponse = await simpleStorageContract.addPerson(
            expectPersionName,
            expectFavoriteNumber,
        );
        await transactionResponse.wait(1);

        // Check the person
        const person = await simpleStorageContract.people(0);
        assert.equal(person.name, expectPersionName);
        assert.equal(person.favoriteNumber, expectFavoriteNumber);
    });
});

it.only 可限制只执行此标志的测试

  • 执行测试
> yarn hardhat test 


  SimpleStorage
    ✔ Should start with a favorite number of 0
    ✔ Should update when we call store
    ✔ Should work correctly with the people struct and array


  3 passing (522ms)
  
# 匹配关键字测试
> yarn hardhat test --grep store


  SimpleStorage
    ✔ Should update when we call store


  1 passing (493ms)
  • unit test: locally

    • local hardhat
    • forked hardhat
  • integration test: can be done on a testnet(LAST STOP!)

Gas Reporter

安装 hardhat-gas-reporter:

> yarn add hardhat-gas-reporter --dev

配置 gas-reporter, hardhat.config.js 文件:

gasReporter: {
    enabled: true
    // currency: "USD",
    // outputFile: "gas-report.txt",
    // Colors: true,
    // coinmarketcap: process.env.COINMARKETCAP_API_KEY,
    // token: 'MATIC', // 代币网络
}

COINMARKETCAP_API获取,可以将消耗的gas转为对应的代币值.

这样,执行测试时会打印gas消耗报告:

> yarn hardhat test

  SimpleStorage
    ✔ Should start with a favorite number of 0
    ✔ Should update when we call store
    ✔ Should work correctly with the people struct and array

·-------------------------------|----------------------------|-------------|-----------------------------·
|     Solc version: 0.8.28      ·  Optimizer enabled: false  ·  Runs: 200  ·  Block limit: 30000000 gas  │
································|····························|·············|······························
|  Methods                                                                                               │
··················|·············|··············|·············|·············|···············|··············
|  Contract       ·  Method     ·  Min         ·  Max        ·  Avg        ·  # calls      ·  usd (avg)  │
··················|·············|··············|·············|·············|···············|··············
|  SimpleStorage  ·  addPerson  ·           -  ·          -  ·     112546  ·            2  ·          -  │
··················|·············|··············|·············|·············|···············|··············
|  SimpleStorage  ·  store      ·           -  ·          -  ·      43724  ·            2  ·          -  │
··················|·············|··············|·············|·············|···············|··············
|  Deployments                  ·                                          ·  % of limit   ·             │
································|··············|·············|·············|···············|··············
|  SimpleStorage                ·           -  ·          -  ·     562687  ·        1.9 %  ·          -  │
·-------------------------------|--------------|-------------|-------------|---------------|-------------·

  3 passing (2s)

测试覆盖率

安装 solidity-coverage:

> yarn add solidity-coverage --dev

hardhat.config.js 文件:

require('solidity-coverage');

测试覆盖检查:

> yarn hardhat coverage           

Version
=======
> solidity-coverage: v0.8.14

Instrumenting for coverage...
=============================

> SimpleStorage.sol

Compilation:
============

Compiled 1 Solidity file successfully (evm target: paris).

Network Info
============
> HardhatEVM: v2.22.19
> network:    hardhat



  SimpleStorage
    ✔ Should start with a favorite number of 0
    ✔ Should update when we call store
    ✔ Should work correctly with the people struct and array


  3 passing (99ms)

--------------------|----------|----------|----------|----------|----------------|
File                |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
--------------------|----------|----------|----------|----------|----------------|
 contracts\         |      100 |      100 |      100 |      100 |                |
  SimpleStorage.sol |      100 |      100 |      100 |      100 |                |
--------------------|----------|----------|----------|----------|----------------|
All files           |      100 |      100 |      100 |      100 |                |
--------------------|----------|----------|----------|----------|----------------|

> Istanbul reports written to ./coverage/ and ./coverage.json

solidity tools

  • solhint: 是 solidity linter, 可以发现潜在的代码问题.
> yarn solhint contracts/FundMe.sol

contracts/FundMe.sol
   5:1  warning  global import of path @chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)  no-global-import
   6:1  warning  global import of path ./PriceConverter.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)                                                no-global-import
  22:5  warning  Immutable variables name are set to be in capitalized SNAKE_CASE                                                                                                 
                                                    immutable-vars-naming
  23:5  warning  Variable name must be in mixedCase                                                                                                                               
                                                    var-name-mixedcase
  24:5  warning  Variable name must be in mixedCase                                                                                                                               
                                                    var-name-mixedcase
  25:5  warning  Variable name must be in mixedCase                                                                                                                               
                                                    var-name-mixedcase
  53:9  warning  Use Custom Errors instead of require statements                                                                                                                  
                                                    custom-errors
  75:9  warning  Provide an error message for require                                                                                                                             
                                                    reason-string
  75:9  warning  Use Custom Errors instead of require statements                                                                                                                  
                                                    custom-errors
  92:9  warning  Provide an error message for require                                                                                                                             
                                                    reason-string
  92:9  warning  Use Custom Errors instead of require statements                                                                                                                  
                                                    custom-errors

✖ 11 problems (0 errors, 11 warnings)

断点调试

  • 加断点
  • Debug 栏 选择 JavaScript Debug Terminal
  • 在打开的命令行窗口执行 yarn hardhat test

配置命令脚本

package.json 文件:

{
  "name": "hardhat-fund-me-fcc",
  "scripts": {
    "test": "hardhat test",
    "test:staging": "hardhat test --network sepolia",
    "lint": "solhint 'contracts/**/*.sol'",
    "lint:fix": "solhint 'contracts/**/*.sol' --fix",
    "format": "prettier --write .",
    "coverage": "hardhat coverage"
  }
}

配置后测试命令可简化为:

> yarn test                         

  FundeMe
    constructor
chainId:  31337
      √ sets the aggregator address correctly
    fund
      √ Fails if you don't send enough ETH
      √ Updates the amount funded data structure
      √ Adds funder to array of funders
    withdraw
      √ withdraws ETH from a single funder
GasCost: 101613356109152
GasUsed: 77744
GasPrice: 1307025058
      √ is allows us to withdraw with multiple funders
      √ Only allows the owner to withdraw


  7 passing (1s)