资讯详情

Dapp-Learning 实战指南:使用 Waffle + ethers.js 为 ERC20 智能合约编写单元测试与链上部署

📅 2026/10/12 1:57:13 | 华诺云谱 👁 阅读
Dapp-Learning 实战指南:使用 Waffle + ethers.js 为 ERC20 智能合约编写单元测试与链上部署
示例工程区块链【免费下载链接】Dapp-LearningDapp learning project for developers at all stages. Becoming and cultivating sovereign individuals. Nonprofit organization.项目地址https://gitcode.com/gh_mirrors/da/Dapp-Learning点击查看免费下载本指南以 Dapp-Learning 仓库中的basic/06-ethersjs-waffle样例为蓝本完整讲解 Waffle 智能合约测试库的工程化用法从安装依赖、编译合约、编写基于 Mocha Chai 的单元测试到借助 ethers.js 连接 Infura 将合约部署到 Sepolia 测试网。读完本文你将掌握一套可直接复用的本地测试 → 链上部署开发闭环并理解 Waffle 内置的 MockProvider、solidity 匹配器与 Chai 断言如何协同工作。一、Waffle 是什么面向 ethers.js 的智能合约测试库Waffle 是一款深度适配 ethers.js 的智能合约测试框架它并不是一个独立的测试运行器而是基于 Node.js 生态中成熟的Mocha测试框架与Chai断言库之上构建的封装层。Dapp-Learning 项目在 docs/develop-tools.md 中将其列为开发者工具箱中的智能合约最先进的测试框架之一。Waffle 为开发者带来的核心价值可以归纳为三点零配置的本地模拟链内置MockProvider无需启动 Ganache 或 Hardhat Network 即可在内存中模拟以太坊节点测试速度极快。合约专用断言匹配器通过use(solidity)扩展 Chai提供emit、reverted、calledOnContract等面向合约语义的断言能力。与 ethers.js 无缝集成deployContract接收 ethers.js 的 Wallet 与合约 JSONABI bytecode部署与交互方式与生产环境完全一致。本样例位于仓库 basic/06-ethersjs-waffle是 Dapp-Learning 入门系列中的第 6 个实战项目紧随前序 ethers.js 基础样例05-ethersjs-erc20之后帮助开发者从会用 ethers.js 发交易过渡到会为合约写测试。二、样例目录结构与依赖清单样例工程的文件组织非常清晰共包含 6 个文件/目录路径作用contract/SimpleToken.sol被测合约一个实现了 ERC20 全部接口、支持铸造mint、暂停pause与销毁burn的标准代币合约test/simpleTokenTest.js针对 SimpleToken 各接口的单元测试脚本index.js外部部署脚本对应生产环境中测试通过后的真实上链操作ethers v6 写法index-v5.js与 index.js 功能等价、基于 ethers v5 API 的部署脚本waffle.jsonWaffle 编译配置指定编译器、源码目录与产物输出目录package.json依赖与脚本定义其中build/test命令即日常开发入口package.json 中的核心依赖如下{ scripts: { build: waffle, test: export NODE_ENVtest mocha --timeout 10000 }, dependencies: { chai: ^4.2.0, dotenv: ^10.0.0, ethereum-waffle: ^3.4.4, ethers: ^5.1.4, mocha: 8.4.0, solc: 0.8.0, web3: ^1.3.5 } }几点值得注意build: waffle直接执行waffle命令Waffle 会自动读取同目录下的waffle.json配置进行合约编译若没有配置文件也会按默认规则寻找contracts目录。test: export NODE_ENVtest mocha --timeout 10000通过环境变量标记测试模式并显式设置 Mocha 超时时间为 10000ms避免测试网/本地模拟环境下偶发超时导致误报失败。依赖同时包含ethers v5^5.1.4与web3这说明该样例可同时演示两种主流 JS 库与 Waffle 的配合方式index.js与index-v5.js分别对应 ethers v6 与 v5 两种写法。solc锁定为0.8.0与 waffle.json 中的compilerVersion保持一致保证编译结果可复现。三、被测合约 SimpleToken.sol一个完整 ERC20 实现contract/SimpleToken.sol 是一份由 Hardhat v2.3.0 扁平化flatten后的合约文件包含IERC20、IERC20Metadata、ERC20、ERC20Burnable、ERC20Pausable、ERC20PresetMinterPauser以及最终的SimpleToken合约。也就是说它并非一个最小示例而是一份全家桶式的标准代币合约ERC20 基础能力totalSupply、balanceOf、transfer、allowance、approve、transferFrom并额外提供increaseAllowance/decreaseAllowance缓解经典的 approve 竞态问题铸造与销毁通过继承ERC20PresetMinterPauser获得mint、burn能力暂停机制pause/unpause用于紧急情况下冻结转账。关键的SimpleToken合约定义在文件末尾第 3287-3307 行contract SimpleToken is ERC20PresetMinterPauser { uint8 private _decimals; uint256 public INITIAL_SUPPLY 10000 * (10 ** uint256(18)); function decimals() public view override returns (uint8) { return _decimals; } constructor(string memory name, string memory symbol, uint8 decimals, uint256 initial_supply) public ERC20PresetMinterPauser(name, symbol) { _decimals decimals; INITIAL_SUPPLY initial_supply * (10 ** uint256(decimals)); _mint(msg.sender, INITIAL_SUPPLY); } }构造函数接收 4 个参数语义如下参数类型含义样例值namestring代币名称HEHEsymbolstring代币符号HHdecimalsuint8小数位数决定最小精度10 ** decimals0测试中或1部署脚本中initial_supplyuint256初始发行量未乘精度时的整数枚数100000000注意两点实现细节第一真实铸造量由initial_supply * (10 ** decimals)计算得出第 3303 行第二铸造对象是msg.sender即部署合约的账户自动获得全部初始代币。这正是测试中部署者余额等于 100000000这一断言成立的根本原因。四、读懂核心测试脚本 simpleTokenTest.jstest/simpleTokenTest.js 是整个样例的灵魂它演示了 Waffle 测试的全部关键 API。逐行拆解如下。4.1 初始化引入 Waffle 与 Chai 匹配器const { use, expect } require(chai); const fs require(fs); const { deployContract, MockProvider, solidity } require(ethereum-waffle); const SimpleToken require(../build/SimpleToken.json); const { ethers } require(ethers); const Web3 require(web3); use(solidity);use(solidity)第 9 行是启用合约专用断言的关键一步注册后 Chai 才能识别to.emit(...)、to.be.reverted、to.be.calledOnContract(...)等匹配器。require(../build/SimpleToken.json)依赖yarn build的编译产物该 JSON 内同时包含 ABI 与 bytecode供部署使用。同时引入 ethers 与 Web3说明 Waffle 测试环境对两者都保持开放兼容。4.2 MockProvider内存中的以太坊节点describe(SimpleToken, () { const [wallet, walletTo] new MockProvider().getWallets(); let token; beforeEach(async () { token await deployContract(wallet, SimpleToken, [HEHE, HH, 0, 100000000]); });new MockProvider().getWallets()第 12 行会返回一组预置了 ETH 余额的测试钱包第一个wallet是默认部署者walletTo是转账接收方。beforeEach中每次测试前都重新deployContract保证用例之间状态完全隔离、互不干扰。deployContract(wallet, SimpleToken, [...])的三个参数分别是ethers 钱包交易签名者、编译产物 JSON、构造函数参数数组与SimpleToken(HEHE, HH, 0, 100000000)一一对应。4.3 用例一初始余额与转账it(Assigns initial balance, async () { expect(await token.balanceOf(wallet.address)).to.equal(100000000); }); it(Transfer adds amount to destination account, async () { await token.transfer(walletTo.address, 7); expect(await token.balanceOf(walletTo.address)).to.equal(7); });第一个用例验证部署者铸造后自动持有全部初始代币100000000因 decimals 为 0 所以无精度缩放第二个用例验证转账后接收方余额精确增加7。这里直接用 Chai 原生to.equal即可因为涉及的是普通数值/地址断言。4.4 用例二事件断言——emit 与 withArgsit(Transfer emits event, async () { await expect(token.transfer(walletTo.address, 7)) .to.emit(token, Transfer) .withArgs(wallet.address, walletTo.address, 7); });这是 Waffle 最具代表性的能力在交易上等待并断言事件。to.emit(token, Transfer)校验转账交易触发了Transfer事件.withArgs(...)进一步校验事件的 indexed 参数from、to与普通参数value完全匹配。4.5 用例三异常断言——revertedit(Can not transfer above the amount, async () { await expect(token.transfer(walletTo.address, 1007100000000)).to.be.reverted; });向接收方转账1007100000000远超部署者余额ERC20 内部require(senderBalance amount)必然失败见 SimpleToken.sol 第 345 行 附近测试断言该交易 revert。这是负面路径测试的标配写法。4.6 用例四调用追踪——calledOnContract 系列it(Calls totalSupply on SimpleToken contract, async () { await token.totalSupply(); expect(totalSupply).to.be.calledOnContract(token); }); it(Calls balanceOf with sender address on SimpleToken contract, async () { await token.balanceOf(wallet.address); expect(balanceOf).to.be.calledOnContractWith(token, [wallet.address]); });最后两个用例展示了 Waffle 的调用追踪匹配器calledOnContract(token)断言某函数确实被调用过calledOnContractWith(token, [wallet.address])进一步要求调用参数精确等于[wallet.address]。这类断言对验证合约内部交互逻辑例如某地址是否被正确传入非常有用。五、waffle.json编译配置详解Waffle 的编译行为完全由 waffle.json 控制内容如下{ compilerType: solcjs, compilerVersion: 0.8.0, sourceDirectory: ./contract, outputDirectory: ./build, nodeModulesDirectory: ./node_modules }各字段说明字段取值含义compilerTypesolcjs使用 npm 安装的 solc-js 编译器而非 solc 二进制compilerVersion0.8.0编译器版本必须与 package.json 中solc: 0.8.0保持一致sourceDirectory./contract合约源码目录注意是单数contract不是常见的contractsoutputDirectory./build编译产物输出目录测试脚本通过require(../build/SimpleToken.json)读取nodeModulesDirectory./node_modules提供依赖查找路径用于解析 import 的第三方合约库由于sourceDirectory指向./contract单数目录执行yarn build后会在./build下生成与合约同名的SimpleToken.json其中abi与bytecode字段是后续测试与部署的输入。六、完整操作步骤从零跑通测试6.1 安装依赖yarn install # node 版本 v20.11.0样例在 Node v20.11.0 环境下验证通过。若尚未安装 yarn可按文档给出的方式安装适用于 VMWare / RHEL 系sudo wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo sudo yum install yarn安装完成后用yarn --version验证版本。6.2 编译合约yarn build该命令等价于直接执行waffle根据 waffle.json 将contract/SimpleToken.sol编译到build/目录产出包含 ABI 与 bytecode 的SimpleToken.json。6.3 配置环境变量cp .env.example .env然后编辑.env配置两个变量模板见 .env.examplePRIVATE_KEYxxxxxxxxxxxxxxxx INFURA_IDyyyPRIVATE_KEY部署账户的私钥用于 index.js 中构建签名钱包INFURA_IDInfura 项目 ID用于连接 Sepolia 测试网 RPC。6.4 执行单元测试yarn test该命令等价于export NODE_ENVtest mocha --timeout 10000Mocha 会自动发现并执行test/目录下所有测试脚本本样例只有simpleTokenTest.js一个实际开发中可为不同合约编写多个脚本放在 test 目录下即可一并执行。全部通过时6 个用例初始余额、转账、事件、超额 revert、totalSupply 调用追踪、balanceOf 调用追踪均显示为绿色 passing。6.5 运行链上部署脚本node index.js # index.js 中 let address xxxxxxx 修改成自己的地址如文档所述index.js 第 24 行的let address需替换为你要查询/转账的目标地址。此脚本对应单元测试通过后的真实生产操作部署 SimpleToken 并执行一笔转账。七、部署脚本 index.js 源码解读index.js 展示了 ethers v6 风格下的完整部署流程可拆成四步。第一步构建 Provider 与 Walletconst web3Provider new ethers.InfuraProvider( sepolia, process.env.INFURA_ID ); const wallet new ethers.Wallet(privateKey, web3Provider);使用ethers.InfuraProvider(sepolia, INFURA_ID)第 12-15 行直连 Infura 的 Sepolia 测试网脚本中注释保留了另外两种等价方案ethers.Web3Provider(web3)包装 Web3 的 HttpProvider以及ethers.JsonRpcProvider(https://sepolia.infura.io/v3/ INFURA_ID, sepolia)。new ethers.Wallet(privateKey, web3Provider)将私钥与 Provider 绑定成可签名的钱包。第二步EIP-1559 动态 Gas 报价async function getGasPrice () { return await web3Provider.getFeeData().then(async function (res) { let maxFeePerGas res.maxFeePerGas; let maxPriorityFeePerGas res.maxPriorityFeePerGas; return { maxFeePerGas, maxPriorityFeePerGas }; }); }getFeeData()第 29-41 行从节点拉取当前 EIP-1559 费用建议返回maxFeePerGas每单位 Gas 的最高总费用与maxPriorityFeePerGas给矿工/验证者的小费上限并打印到控制台。这样发起交易时无需手写固定 Gas 价能自动适配网络拥堵情况。第三步用 ContractFactory 部署const simpletoken new ethers.ContractFactory( SimpleToken.abi, SimpleToken.bytecode, wallet ); token await simpletoken.deploy(HEHE, HH, 1, 100000000); await token.waitForDeployment(); console.log(token.target);ethers.ContractFactory(abi, bytecode, wallet)第 58-62 行是常用合约工厂实例的标准创建方式deploy(HEHE, HH, 1, 100000000)传入构造函数四参数ethers v6 中waitForDeployment()等待交易上链确认部署后的合约地址通过token.target获取v5 中对应token.address与token.deployed()见 index-v5.js。第四步发起转账并校验余额tx await token.transfer(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, ethers.parseEther(0.00000000001), option); console.log(hash, tx.hash); let bal await token.balanceOf(wallet.address); console.log(bal.toString());转账金额用ethers.parseEther换算注意 SimpleToken 的 decimals 由构造参数决定此处演示的是通用写法并把 EIP-1559 的option作为交易覆盖项传入最后回查部署者余额验证代币确实铸造到账。八、跨平台注意事项与常见坑原文档针对实际运行中的环境差异给出了两条非常实用的经验这里完整保留并补充说明。8.1 找不到 yarn 命令在 VMWare 等精简 Linux 环境下yarn install可能提示找不到 yarn。按文档步骤通过 yum 源安装即可sudo wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo sudo yum install yarn装完执行yarn --version确认安装成功。8.2 Windows 下yarn test报找不到命令Windows 的 cmd/PowerShell 不支持export关键字需要把 package.json 中test脚本的export改为set修改前scripts: { build: waffle, test: export NODE_ENVtest mocha --timeout 10000 }修改后scripts: { build: waffle, test: set NODE_ENVtest mocha --timeout 10000 }这也是跨平台开发中环境变量注入的经典差异点POSIX shell 用export VAR... Windows shell 用set VAR... 。九、进阶阅读路径本样例在 Dapp-Learning 入门链路中的位置与延伸方向若想对比web3.js 与 ethers.js的差异可先学习前序样例 basic/05-ethersjs-erc20若想掌握更主流的 Hardhat 测试工作流同样基于 Mocha Chai但使用 hardhat 网络可继续学习 basic/07-hardhatWaffle 的更完整用法自定义 matcher、fixture 复用、chai 插件扩展等可参考其官方文档深入研究ethers.js 的 API 细节v6 的InfuraProvider/ContractFactory/waitForDeployment与 v5 的providers.InfuraProvider/deployed差异可对照本样例的 index.js 与 index-v5.js 两份脚本学习。十、小结本文以 basic/06-ethersjs-waffle 为完整案例系统梳理了 Waffle 测试库从配置到实战的完整链路waffle.json决定编译方式MockProvider提供免节点的本地测试环境deployContract完成合约实例化solidity匹配器赋予emit/reverted/calledOnContract等合约级断言能力最后通过ContractFactoryInfuraProvider将同一份合约无缝部署到 Sepolia 测试网。这套测试驱动 一键上链的流程是 Dapp-Learning 为开发者构建的标准化 DApp 开发闭环可直接迁移到任何基于 ethers.js 的合约项目中。赞分享示例工程区块链【免费下载链接】Dapp-LearningDapp learning project for developers at all stages. Becoming and cultivating sovereign individuals. Nonprofit organization.项目地址https://gitcode.com/gh_mirrors/da/Dapp-Learning点击查看免费下载相关推荐Dapp-Learning 实战使用 ethers.js 部署与调用 ERC20 合约含事件监听与 mempool 监控Dapp Learning 实战使用 ethers.js 部署与调用 ERC20 合约含事件监听与 mempool 监控 导读 本文以 Dapp Lear示例工程区块链Dapp-Learning 实战使用 Truffle 框架完成 ERC20 合约的编译、测试、部署与本地调试全流程Dapp Learning 实战使用 Truffle 框架完成 ERC20 合约的编译、测试、部署与本地调试全流程 本指南以 Dapp Learning 仓库示例工程区块链使用 Web3.js 编译并部署智能合约到 Sepolia 测试网Dapp-Learning 实战教程使用 Web3.js 编译并部署智能合约到 Sepolia 测试网Dapp Learning 实战教程 本文以 Dapp Learning 仓库 basic/示例工程区块链上一篇PaddleSeg 配置文件完全解读从模块化结构到 _base_ 继承机制下一篇基于YOLOv10的智能游戏瞄准助手终极AI瞄准解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑