FundMe 合约编写及高覆盖率测试【Foundry 开发】

FundMe 合约编写及高覆盖率测试

本文目标是完善 FundMe.sol 合约,并为该合约进行高覆盖率测试。

FundMe 合约编写

首先,我们需要写出 FundMe.sol 合约:

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

import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import {PriceConverter} from "./PriceConverter.sol";

error NotOwner();

contract FundMe {
	using PriceConverter for uint256;
	
	mapping(address => uint256) private s_addressToAmountFunded;
	address[] private s_funders;
	address private immutable i_owner;
	uint256 public constant MINIMUM_USD = 5e18;
	AggregatorV3Interface private s_priceFeed;
	
	modifier onlyOwner {
		if (msg.sender != i_owner) revert NotOwner();
		_;
	}
	
	constructor(address priceFeed) {
		i_owner = msg.sender;
		s_priceFeed = AggregatorV3Interface(priceFeed);
	}
	
	function fund() public payable {
		require(msg.value.getConversionRate(s_priceFeed)) >= MINIMUM_USD, "You need to spend more ETH!";
		s_addressToAmountFunded[msg.sender] += msg.value;
		s_funder.push(msg.sender);
	}
	
	function withdraw() public onlyOwner {
		for (
			uint256 i = 0;
			i < s_funders.length;
			i++
		) {
			address funder = s_funders[i];
			s_addressToAmountFunded[funder] = 0;
		}
		
		s_funders = new address[](0);
		
		(bool callSuccess, ) = payable(msg.sender).call{ value: address(this).balance } ("");
		require(callSuccess, "Call failed");
	}
	
	/* Getter Functions */
	
	function getAddressToAmountFunded(address fundingAddress) external view returns(uint256) {
		return s_addressToAmountFunded[fundingAddress];
	}
	
	function getFunder(uint256 index) public view returns(address) {
		return s_funders[index];
	}
	
	function getOwner() public view returns(address) {
		return i_owner;
	}
	
	function getPriceFeed() public view returns(AggregatorV3Interface) {
		return s_priceFeed;
	}
	
	function getVersion() public view returns (uint256) {
		return s_priceFeed.version();
	}
}

该 FundMe 合约可以大致分为以下五个部分:

  1. 导入和声明
    这部分包括合约所需要的库的导入和合约的版本声明。在 FundMe 合约中,这包括 pragma solidity ^0.8.19,以及从其他文件中导的接口和库。
  2. 状态变量和类型定义
    这部分包含合约的状态变量和类型定义。在 FundMe 合约中,这包括:s_addressToAmountFunded, s_funders, i_owner, MINIMUM_USD, 以及 s_priceFeed 这五个存储变量的定义。
  3. 构造函数和修饰符
    这部分包括构造函数和修饰符的定义。在 FundMe 合约中,这包括 constructor(address priceFeed)modifier onlyOwner
  4. 主要功能函数
    这部分包括合约的主要功能函数。在 FundMe 合约中,这包括 fund()withdraw()
  5. Getter 函数
    这部分包括所有的 getter 函数,这些函数用来读取合约的状态变量。在 FundMe 中,这包括 getAddressToAmountFunded, getFunder, getOwner, getPriceFeed, 和 getVersion 函数。

FundMe 合约测试

在测试的过程中,我们要将测试重心将放在「状态变量和类型定义」以及「主要功能函数」这两个部分上。

测试合约 FundMe.t.sol 代码如下:

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

/* 导入和声明 */

import "forge-std/Test.sol";
import "../src/FundMe.sol";
import "../script/FundMe.s.sol";

contract FundMeTest is Test {
	/* 状态变量和类型定义 */
	FundMe fundMe;
	address immutable USER = makeAddr("Corror");
	uint256 constant TRANSFER_AMOUNT = 10 ether;
	uint256 constant ZERO_BALANCE = 0 ether;
	
	/* 环境设置函数和修饰符 */
	
	function setUp() public {
		FundMeScript fundMeScript = new FundMeScript();
		fundMe = fundMeScript.run();
	}
	
	modifier funded() {
		hoax(USER);
		fundMe.fund{value: TRANSFER_AMOUNT};
		_;
	}
	
	/* 状态变量测试 */
	
	function testMINIMUM_USD() public {
		assertEq(fundMe.MINIMUM_USD(), 5e18);
	}
	
	function testI_owner() public {
		assertEq(fundMe.getOwner(), msg.sender);
	}
	
	function testGetVersion() public {
		assertEq(fundMe.getVersion(), 4);
	}
	
	/* 主要功能函数测试 */
	
	/* 1. fundMe() 函数测试 */
	
	function testFundRevertWithBelowMinimumTransactionAmount() public {
		hoax(USER);
		vm.expectRevert();
		fundMe.fund{value: 0}();
	}
	
	function testFundUpdatesAmountFundedOnSuccess() funded public {
		assertEq(fundMe.getAddressToAmountFunded(USER), TRANSFER_AMOUNT);
	}
	
	function testFundAddFunderOnSuccess() funded public {
		assertEq(fundMe.getFunder(0), USER);
	}
	
	/* 2. 测试 withdraw() 函数 */
	
	function testWithdrawRevertWhenCallerIsNotOwner() funded public {
		vm.prank(USER);
		vm.expectRevert();
		fundMe.withdraw();
	}
	
	function testWithdrawSingerFund() funded public {
		hoax(fundMe.getOwner(), ZERO_BALANCE);
		fundMe.withdraw();
		
		assertEq(fundMe.getOwner().balance, TRANSFER_AMOUNT);
	}
	
	function testWithrawMultipulFunds() public {
		// 准备:Arrange
		uint160 fundersAmount = 10;
		uint160 initialFunderIndex = 1;
		for (
			uint160 fundersIndex=initialFunderIndex; 
			funderIndex<=fundersAmount; 
			fundersIndex++
		) {
			hoax(address(fundersIndex));
			fundMe.fund(value: TRANSFER_AMOUNT)();
		}
		
		// 执行:Act
		hoax(fundMe.getOwner(), 0);
		fundMe.withdraw();
		
		// 断言:Assert
		assertEq(fundMe.getOwner().balance, fundersAmount * TRANSFER_AMOUNT);
	}
}

覆盖率检测

写出测试合约后,我们可以通过 Foundry 命令检查测试覆盖率:

forge coverage

在代码正确的情况下,覆盖率应该为 93.75%。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值