Hardhat入门:现代Solidity开发者的必备工具

Hardhat是一个功能强大、灵活且易于扩展的Solidity开发框架,广泛用于以太坊智能合约的开发、测试、调试和部署。它的设计目标是提升开发效率、支持复杂项目,并与现代开发工具无缝集成。Hardhat简介Hardhat是什么?Hardhat是一个JavaScript开发的以太

Hardhat 是一个功能强大、灵活且易于扩展的 Solidity 开发框架,广泛用于以太坊智能合约的开发、测试、调试和部署。它的设计目标是提升开发效率、支持复杂项目,并与现代开发工具无缝集成。

Hardhat 简介

Hardhat 是什么?
Hardhat 是一个 JavaScript 开发的以太坊开发环境,支持 Solidity 智能合约的编译、测试、调试和部署。它通过模块化的设计和丰富的插件生态,为开发者提供了高效的工作流。与 Truffle 和 Foundry 等工具相比,Hardhat 的优势在于:

  • 灵活性:支持自定义任务、脚本和插件。
  • 调试工具:内置强大的调试功能,如 console.log 和堆栈跟踪。
  • 测试支持:与 Mocha/Chai 集成,支持单元测试和覆盖率分析。
  • 网络支持:内置 Hardhat Network,模拟以太坊环境,加速开发。
  • 社区活跃:拥有广泛的插件生态和活跃的社区支持。

适用场景

  • 智能合约开发(ERC20、ERC721、DeFi 协议等)。
  • 本地测试和调试。
  • 部署到主网、测试网或自定义网络。
  • 集成 CI/CD 和自动化工作流。

与其他工具的对比

特性 Hardhat Truffle Foundry
语言 JavaScript/TypeScript JavaScript Rust
测试框架 Mocha/Chai Mocha/Chai Rust-based
速度 中等 非常快
调试 强大(console.log、堆栈跟踪) 中等 有限
插件生态 丰富 丰富 较少
学习曲线 中等 中等 较陡峭

选择建议

  • Hardhat 适合需要灵活配置和强大调试功能的开发者。
  • Truffle 适合对迁移(migration)系统有需求的传统项目。
  • Foundry 适合追求极致性能的开发者,但需熟悉 Rust。

安装与环境搭建

安装 Node.js 和 npm

Hardhat 基于 Node.js 运行,需安装 Node.js(建议版本 16 或更高)和 npm。

  1. 安装 Node.js

    • Windows/Mac:从 Node.js 官网 下载安装包。
    • Linux:
      sudo apt update
      sudo apt install nodejs npm
    • 验证:
      node -v
      npm -v
  2. 全局安装 Hardhat

    npm install -g hardhat
    • 验证:
      npx hardhat --version

创建 Hardhat 项目

  1. 初始化项目

    mkdir my-hardhat-project
    cd my-hardhat-project
    npx hardhat init

    Hardhat 会提示选择项目类型:

    • 选择 Create a JavaScript project(适合大多数开发者)。
    • 选择是否安装推荐的依赖(如 @openzeppelin/contracts)。
  2. 项目结构: 初始化后,项目结构如下:

    my-hardhat-project/
    ├── contracts/           # Solidity 合约代码
    │   └── Lock.sol
    ├── scripts/             # 部署和交互脚本
    │   └── deploy.js
    ├── test/                # 测试用例
    │   └── Lock.js
    ├── hardhat.config.js    # Hardhat 配置文件
    ├── package.json         # Node.js 项目依赖
    └── README.md
  3. 安装常用依赖

    npm install @openzeppelin/contracts @nomicfoundation/hardhat-toolbox
    • @openzeppelin/contracts:提供标准化的安全合约(如 ERC20、ERC721)。
    • @nomicfoundation/hardhat-toolbox:集成测试、调试和部署工具。

配置 Hardhat

hardhat.config.js 是 Hardhat 的核心配置文件,定义编译器版本、网络和插件。

示例配置:


require("@nomicfoundation/hardhat-toolbox");

module.exports = {
  solidity: {
    version: "0.8.20",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },
  networks: {
    hardhat: {}, // 本地 Hardhat Network
    sepolia: {
      url: "https://sepolia.infura.io/v3/YOUR_INFURA_KEY",
      accounts: ["YOUR_PRIVATE_KEY"]
    }
  },
  etherscan: {
    apiKey: "YOUR_ETHERSCAN_API_KEY"
  }
};
  • Solidity 版本:指定编译器版本(如 0.8.20)。
  • 优化器:启用优化器以减少 Gas 成本。
  • 网络:配置本地网络(hardhat)和测试网(如 Sepolia)。
  • Etherscan:用于验证合约源码。

验证安装

运行以下命令确保环境正常:

npx hardhat compile

成功后,artifacts/cache/ 文件夹将生成编译产物。


核心功能

编译合约

Hardhat 使用 npx hardhat compile 编译 Solidity 代码,生成 ABI 和字节码。

  • 多版本支持: 在 hardhat.config.js 中配置多个 Solidity 版本:

    solidity: {
    compilers: [
      { version: "0.8.20" },
      { version: "0.7.6" }
    ]
    }
  • 错误检查: Hardhat 提供详细的编译错误信息,便于调试。

测试合约

Hardhat 集成了 Mocha/Chai 测试框架,支持单元测试和集成测试。

  1. 编写测试用例: 在 test/ 文件夹中创建测试文件。

    示例(测试 ERC20 代币):

    
    const { expect } = require("chai");
    
    describe("Token", function () {
     let Token, token, owner, addr1;
    
     beforeEach(async function () {
       Token = await ethers.getContractFactory("Token");
       [owner, addr1] = await ethers.getSigners();
       token = await Token.deploy("MyToken", "MTK", 1000000);
       await token.deployed();
     });
    
     it("should assign initial supply to owner", async function () {
       expect(await token.balanceOf(owner.address)).to.equal(1000000);
     });
    
     it("should transfer tokens correctly", async function () {
       await token.transfer(addr1.address, 100);
       expect(await token.balanceOf(addr1.address)).to.equal(100);
       expect(await token.balanceOf(owner.address)).to.equal(999900);
     });
    });
    
  2. 运行测试

    npx hardhat test
  3. 覆盖率分析

    npx hardhat coverage

    生成覆盖率报告,显示测试覆盖的代码行。

调试合约

Hardhat 提供强大的调试工具,如 console.log 和堆栈跟踪。

  • 使用 console.log: 在 Solidity 代码中添加调试输出:

    import "hardhat/console.sol";
    
    contract Debug {
      function debugValue(uint256 value) public view {
          console.log("Value is:", value);
      }
    }
  • 运行调试: 使用 npx hardhat node 启动本地节点,然后运行脚本查看输出:

    npx hardhat run scripts/debug.js --network hardhat

部署合约

Hardhat 支持通过脚本部署合约到本地或远程网络。

  1. 编写部署脚本: 在 scripts/ 文件夹中创建 deploy.js

    
    async function main() {
     const Token = await ethers.getContractFactory("Token");
     const token = await Token.deploy("MyToken", "MTK", 1000000);
     await token.deployed();
     console.log("Token deployed to:", token.address);
    }
    
    main().catch((error) => {
     console.error(error);
     process.exitCode = 1;
    });
    
  2. 部署到本地网络

    npx hardhat run scripts/deploy.js --network hardhat
  3. 部署到测试网: 配置 hardhat.config.js 中的网络,然后运行:

    npx hardhat run scripts/deploy.js --network sepolia
  4. 验证合约: 使用 Etherscan 插件验证合约源码:

    npx hardhat verify --network sepolia CONTRACT_ADDRESS "MyToken" "MTK" 1000000

Hardhat Network

Hardhat Network 是一个内置的以太坊模拟器,支持快速测试和调试。

  • 启动节点

    npx hardhat node

    提供 20 个测试账户,每个账户有 10000 ETH。

  • 连接前端: 配置 MetaMask 使用 Hardhat Network(URL:http://127.0.0.1:8545)。


插件生态

Hardhat 的插件生态丰富,支持扩展功能。以下是常用插件:

  1. @nomicfoundation/hardhat-toolbox: 集成测试、调试和部署工具。

    npm install @nomicfoundation/hardhat-toolbox
  2. @openzeppelin/hardhat-upgrades: 支持可升级合约。

    import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
    
    contract MyContract is UUPSUpgradeable {
       function initialize() public initializer {}
    }
  3. hardhat-gas-reporter: 分析 Gas 消耗。

    npm install hardhat-gas-reporter

    配置:

    module.exports = {
     gasReporter: {
       enabled: true,
       currency: "USD"
     }
    };
  4. hardhat-etherscan: 验证合约源码。

    npm install @nomiclabs/hardhat-etherscan
  5. hardhat-waffle: 增强测试功能,支持 Waffle。

    npm install @nomiclabs/hardhat-waffle

实践

项目结构

组织项目以提高可维护性:

my-hardhat-project/
├── contracts/              # 合约代码
│   ├── interfaces/         # 接口定义
│   ├── libraries/          # 库合约
│   └── Token.sol
├── scripts/                # 部署和交互脚本
│   ├── deploy.js
│   └── interact.js
├── test/                   # 测试用例
│   ├── unit/               # 单元测试
│   └── integration/        # 集成测试
├── tasks/                  # 自定义任务
│   └── balance.js
├── hardhat.config.js
└── package.json

编写高质量测试

  • 覆盖所有场景: 测试正常、边界和错误情况。

    it("should revert on zero transfer", async function () {
    await expect(token.transfer(addr1.address, 0)).to.be.revertedWith("Zero amount");
    });
  • 使用快照: 在 Hardhat Network 中使用快照加速测试:

    let snapshotId;
    
    beforeEach(async function () {
    snapshotId = await ethers.provider.send("evm_snapshot", []);
    });
    
    afterEach(async function () {
    await ethers.provider.send("evm_revert", [snapshotId]);
    });

Gas 优化

  • 启用优化器

    solidity: {
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
    }
  • 分析 Gas 消耗: 使用 hardhat-gas-reporter 查看每个函数的 Gas 成本。

安全实践

  • 使用 OpenZeppelin 库

    import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    
    contract Token is ERC20 {
      constructor(string memory name, string memory symbol, uint256 initialSupply)
          ERC20(name, symbol)
      {
          _mint(msg.sender, initialSupply);
      }
    }
  • 防止重入攻击: 使用 ReentrancyGuard

    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
    
    contract Secure is ReentrancyGuard {
      function withdraw() public nonReentrant {
          // 逻辑
      }
    }
  • 静态分析: 使用 Slither 检查漏洞:

    npx hardhat compile
    slither .

自定义任务

创建自定义 Hardhat 任务以自动化流程:


task("balance", "Prints an account's balance")
  .addParam("account", "The account's address")
  .setAction(async (taskArgs, hre) => {
    const balance = await hre.ethers.provider.getBalance(taskArgs.account);
    console.log("Balance:", hre.ethers.utils.formatEther(balance));
  });

运行:

npx hardhat balance --account 0x123...

综合案例:部署 ERC20 代币

以下是一个完整的 ERC20 代币开发、测试和部署流程。

  1. 合约代码

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

/// @title A secure ERC20 token contract Token is ERC20, Ownable, ReentrancyGuard { constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) Ownable(msg.sender) { _mint(msg.sender, initialSupply * 10**decimals()); }

function transfer(address to, uint256 amount)
    public
    override
    nonReentrant
    returns (bool)
{
    require(to != address(0), "Invalid address");
    require(amount > 0, "Zero amount");
    return super.transfer(to, amount);
}

function burn(uint256 amount) public onlyOwner {
    _burn(msg.sender, amount);
}

}


2. **测试代码**:

 ```javascript

const { expect } = require("chai");

describe("Token", function () {
  let Token, token, owner, addr1;

  beforeEach(async function () {
    Token = await ethers.getContractFactory("Token");
    [owner, addr1] = await ethers.getSigners();
    token = await Token.deploy("MyToken", "MTK", 1000000);
    await token.deployed();
  });

  it("should assign initial supply to owner", async function () {
    expect(await token.balanceOf(owner.address)).to.equal(1000000n * 10n**18n);
  });

  it("should transfer tokens correctly", async function () {
    await token.transfer(addr1.address, 100n * 10n**18n);
    expect(await token.balanceOf(addr1.address)).to.equal(100n * 10n**18n);
  });

  it("should revert on invalid transfer", async function () {
    await expect(token.transfer(addr1.address, 0)).to.be.revertedWith("Zero amount");
    await expect(token.transfer(ethers.constants.AddressZero, 100)).to.be.revertedWith("Invalid address");
  });

  it("should allow owner to burn tokens", async function () {
    await token.burn(100n * 10n**18n);
    expect(await token.balanceOf(owner.address)).to.equal(999900n * 10n**18n);
  });

  it("should revert burn for non-owner", async function () {
    await expect(token.connect(addr1).burn(100)).to.be.revertedWith("Ownable: caller is not the owner");
  });
});
  1. 部署脚本

async function main() { const Token = await ethers.getContractFactory("Token"); const token = await Token.deploy("MyToken", "MTK", 1000000); await token.deployed(); console.log("Token deployed to:", token.address); }

main().catch((error) => { console.error(error); process.exitCode = 1; });


4. **运行流程**:
   - 编译:`npx hardhat compile`
   - 测试:`npx hardhat test`
   - 部署:`npx hardhat run scripts/deploy.js --network sepolia`
   - 验证:`npx hardhat verify --network sepolia CONTRACT_ADDRESS "MyToken" "MTK" 1000000`

---

### 高级功能

#### 交互脚本
与已部署的合约交互:
```javascript

async function main() {
  const tokenAddress = "0xYOUR_CONTRACT_ADDRESS";
  const Token = await ethers.getContractFactory("Token");
  const token = await Token.attach(tokenAddress);
  const balance = await token.balanceOf("0xYOUR_ADDRESS");
  console.log("Balance:", ethers.utils.formatEther(balance));
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1);
});

运行:

npx hardhat run scripts/interact.js --network sepolia

自定义网络

添加自定义网络(如 Polygon):

networks: {
  polygon: {
    url: "https://polygon-rpc.com",
    accounts: ["YOUR_PRIVATE_KEY"]
  }
}

TypeScript 支持

将 Hardhat 项目转换为 TypeScript:

  1. 安装 TypeScript 依赖:

    npm install --save-dev ts-node typescript @types/chai @types/mocha @types/node
  2. 重命名配置文件为 hardhat.config.ts

    import { HardhatUserConfig } from "hardhat/config";
    import "@nomicfoundation/hardhat-toolbox";
    
    const config: HardhatUserConfig = {
     solidity: "0.8.20",
     networks: {
       hardhat: {},
       sepolia: {
         url: "https://sepolia.infura.io/v3/YOUR_INFURA_KEY",
         accounts: ["YOUR_PRIVATE_KEY"]
       }
     }
    };
    
    export default config;
  3. 转换测试文件为 .ts(如 Token.test.ts)。


常见问题与解决

  1. 编译错误
    • 问题:Solidity 版本不匹配。
    • 解决:在 hardhat.config.js 中指定正确的编译器版本。
  2. 测试失败
    • 问题:Gas 不足或网络错误。
    • 解决:检查网络配置或增加 Gas 限制:
      await token.transfer(addr1.address, 100, { gasLimit: 100000 });
  3. 部署失败
    • 问题:私钥或 RPC URL 错误。
    • 解决:验证 hardhat.config.js 中的网络配置。
  4. 调试困难
    • 问题:错误信息不清晰。
    • 解决:启用 console.log 或使用 Hardhat 的 --show-stack-traces 选项。
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
天涯学馆
天涯学馆
0x9d6d...50d5
资深大厂程序员,12年开发经验,致力于探索前沿技术!