3. 使用Foundry进行单元测试
任务说明
本案例将使用Foundry框架为智能合约编写并运行单元测试。目标合约是一个简单的ERC20代币(含铸造与销毁功能),测试将覆盖代币的初始化、转账、授权与铸造销毁逻辑,并通过Foundry的测试框架验证合约行为是否符合预期。
合约实现
首先创建代币合约 src/MyToken.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract MyToken {
string public name = "MyToken";
string public symbol = "MTK";
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(uint256 initialSupply) {
_mint(msg.sender, initialSupply);
}
function transfer(address to, uint256 amount) external returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}
function approve(address spender, uint256 amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
uint256 allowed = allowance[from][msg.sender];
require(allowed >= amount, "allowance exceeded");
if (allowed != type(uint256).max) {
allowance[from][msg.sender] = allowed - amount;
}
_transfer(from, to, amount);
return true;
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
function burn(uint256 amount) external {
_burn(msg.sender, amount);
}
function _transfer(address from, address to, uint256 amount) internal {
require(to != address(0), "transfer to zero address");
uint256 bal = balanceOf[from];
require(bal >= amount, "insufficient balance");
balanceOf[from] = bal - amount;
balanceOf[to] += amount;
emit Transfer(from, to, amount);
}
function _mint(address to, uint256 amount) internal {
require(to != address(0), "mint to zero address");
totalSupply += amount;
balanceOf[to] += amount;
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal {
uint256 bal = balanceOf[from];
require(bal >= amount, "burn exceeds balance");
balanceOf[from] = bal - amount;
totalSupply -= amount;
emit Transfer(from, address(0), amount);
}
}
测试代码
创建测试文件 test/MyToken.t.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {MyToken} from "../src/MyToken.sol";
contract MyTokenTest is Test {
MyToken public ;
address public alice = address(0xA11CE);
address public bob = address(0xB0B);
function setUp() public {
= new MyToken(1000 ether);
vm.prank(address(this));
.transfer(alice, 500 ether);
}
function test_InitialSupply() public view {
assertEq(.totalSupply(), 1000 ether);
assertEq(.balanceOf(address(this)), 500 ether);
assertEq(.balanceOf(alice), 500 ether);
}
function test_Transfer() public {
vm.prank(alice);
.transfer(bob, 200 ether);
assertEq(.balanceOf(alice), 300 ether);
assertEq(.balanceOf(bob), 200 ether);
}
function test_TransferInsufficientBalance() public {
vm.prank(alice);
vm.expectRevert("insufficient balance");
.transfer(bob, 501 ether);
}
function test_ApproveAndTransferFrom() public {
vm.prank(alice);
.approve(address(this), 100 ether);
.transferFrom(alice, bob, 100 ether);
assertEq(.balanceOf(bob), 100 ether);
assertEq(.allowance(alice, address(this)), 0);
}
function test_Mint() public {
.mint(bob, 50 ether);
assertEq(.balanceOf(bob), 50 ether);
assertEq(.totalSupply(), 1050 ether);
}
function test_Burn() public {
vm.prank(alice);
.burn(100 ether);
assertEq(.balanceOf(alice), 400 ether);
assertEq(.totalSupply(), 900 ether);
}
function test_BurnExceedsBalance() public {
vm.prank(alice);
vm.expectRevert("burn exceeds balance");
.burn(501 ether);
}
}
SVG:测试执行流程与断言关系
运行与验证
在项目根目录执行以下命令:
forge build
forge test -vvv
预期输出中所有测试均显示 [PASS],例如:
[PASS] test_ApproveAndTransferFrom()
[PASS] test_Burn()
[PASS] test_BurnExceedsBalance()
[PASS] test_InitialSupply()
[PASS] test_Mint()
[PASS] test_Transfer()
[PASS] test_TransferInsufficientBalance()
Suite result: ok. 7 passed; 0 failed; 0 skipped
判定标准:若7个测试全部通过且无失败,则说明代币合约的初始化、转账、授权、铸造与销毁逻辑均符合预期;若任一测试失败,则根据 forge test -vvv 输出的具体断言信息定位并修复合约或测试代码。