Foundry 模拟合约
本文目标:实现在本地网络中,部署模拟喂价合约,并成功将其部署和测试。
当我们在测试 Chainlink 喂价函数时,我们需要从区块链上的喂价合约中读取数据。
在上一篇文章中,我们提到如何在不同的区块链网络中,进行 Chainlink 喂价函数的测试。而这一次,我们将说明如何在本地网络中进行喂价函数的测试。
本地测试的思路
在本地测试喂价函数时,需要完成以下几个步骤:
- 环境配置:比如创建
.env
环境变量。 - 创建目标合约:写下我们想要部署和测试的合约
FundMe.sol
。 - 创建模拟喂价合约:由于本地网络上不存在模拟喂价合约,因此我们需要先创建它,然后将它部署上去。
- 获取模拟喂价合约的地址,然后部署并测试目标合约。
由于 Chainlink 喂价合约只存在于 Mainnet, Sepolia, Goerli 上面,因此当我们尝试从本地网络上读取喂价数据时,会发现没有相应的喂价合约为我们提供数据。
因此,我们需要在本地网络测试喂价函数之前,先本地部署一个模拟喂价合约(Mock Contract),这个合约的功能和数据类似于真实的喂价合约,不同之处是其中的数据是由我们控制的。
项目目录
为了在本地网络上完成喂价函数的测试,我们需要用到以下代码结构:
$ tree -I '.github|broadcast|cache|lib|out|.env|.gitignore|gitmodules|foundry.toml'
.
├── script
│ ├── FundMe.s.sol
│ └── HelperConfig.s.sol
├── src
│ ├── FundMe.sol
│ └── PriceConverter.sol
└── test
├── FundMe.t.sol
└── mocks
└── MockV3Aggregator.sol
4 directories, 6 files
其中,src
文件夹用来存放核心的合约文件,其中的 FundMe.sol
和 PriceConverter
代码,可以通过该链接查看。
本地测试的步骤
1. 环境配置
这一步可以参考[[Foundry 环境配置]],然后添加 FundMe.sol
, PriceConverter.sol
文件到 src
文件夹。
2. 创建模拟喂价合约
首先,我们需要在 test
文件夹中新建 mocks
文件夹,用于存储所有用于测试的模拟合约。
然后,创建模拟喂价合约 MockV3Aggregator.sol
,它可以产生类似于真实喂价合约的数据,其代码可以通过该链接查看。
3. 部署模拟喂价合约
在创建了模拟喂价合约后,我们需要让部署脚本有能力在检测到部署网络为本地网络时,自当将这一模拟喂价合约部署,并获取该合约的信息。
方法是在 HelperConfig.s.sol
文件中,添加以下代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Script.sol";
import "../test/mocks/MockV3Aggregator.sol";
// 创建配置合约,用于确认网络 ID,并获取模拟喂价合约的信息
contract HelperConfig is Script {
// 该结构体用于存储模拟喂价合约的数据
NetworkConfig public networkConfig;
// 提供模拟喂价合约的构造函数所需的参数
uint8 public constant DECIMALS = 8; // 小数位数
int256 public constant INITIAL_ANSWER = 2000e8; // 初始答案的值
struct NetworkConfig {
address priceFeedContractAddress;
}
// 该构造函数仅在本地网络环境中触发
constructor() {
if (block.chainid == 31337) { // 31337 是本地网络的 ID
networkConfig = createOrGetAnvilConfig(); // 将模拟喂价合约的信息存储在结构体中
}
}
// 部署模拟喂价合约,并获取其信息
function createOrGetAnvilConfig() public returns(NetworkConfig memory) {
// 如果检测到模拟喂价合约已部署于本地网络,那么直接返回存储了模拟喂价合约的数据的结构体
if (networkConfig.priceFeedContractAddress != address(0)) {
return networkConfig;
}
// 将下一个语句内容广播
vm.broadcast();
// 部署模拟喂价合约,并获取该合约的实例
MockV3Aggregator mockPriceFeed = new MockV3Aggregator(DECIMALS, INITIAL_ANSWER);
// 将模拟喂价合约实例的地址,存储在结构体中
NetworkConfig memory anvilConfig = NetworkConfig({
priceFeedContractAddress: address(mockPriceFeed)
});
// 返回结构体
return anvilConfig;
}
}
在以上代码中,我们创建了 HelperConfig
合约,它主要用于配置模拟的喂价合约,根据当前的区块链网络,获取对应的喂价合约的地址。
其中,NetworkConfig
结构体用于存储所需的喂价数据,在这里我们仅包含一个成员变量——喂价合约的地址 priceFeedContractAddress
。
接下来我们创建了该结构体的实例 networkConfig
, 它负责存储当前区块链网络下的喂价合约的数据。
接下来,我们通过函数 createOrGetAnvilConfig()
判断 Anvil 网络中是否已经存在模拟喂价合约,如果不存在,则部署模拟喂价合约,并将该合约的地址传递给 networkConfig
结构体。
最后,我们通过构造函数 constructor()
来识别当前区块链网络,并选择相应的喂价合约。如果区块链网络 ID 为 31337,即处于本地网络时,我们需要通过 createOrGetAnvilConfig()
函数获取本地网络上模拟喂价函数的地址。
4. 关联模拟喂价合约
到目前为止,我们已经有能力通过 HelperConfig
合约在本地网络上部署模拟喂价合约了。
接下来,我们只需要通过 HelperConfig
合约的实例,获取到模拟喂价合约的地址,就可以成功部署合约了。
我们可以为部署脚本 FundMe.s.sol
添加以下代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Script.sol";
import "../src/FundMe.sol";
import "./HelperConfig.s.sol";
contract FundMeScript is Script{
function run() external returns(FundMe) {
HelperConfig helperConfig = new HelperConfig();
(address ethUsdPriceFeedAddress) = helperConfig.networkConfig();
vm.broadcast();
FundMe fundMe = new FundMe(ethUsdPriceFeedAddress);
return fundMe;
}
}
5. 测试喂价函数
接下来,我们可以完成喂价函数的测试代码 FundMe.t.sol
:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import "forge-std/Test.sol";
import "../src/FundMe.sol";
import "../script/FundMe.s.sol";
contract FundMeTest is Test {
FundMe fundMe;
function setUp() public {
FundMeScript fundMeScript = new FundMeScript();
fundMe = fundMeScript.run();
}
function testGetVersion() public {
assertEq(fundMe.getVersion(), 4);
}
}
6. 执行测试命令
最后,可以执行 Foundry 的测试命令:
forge test --match-contract FundMeTest --match-test testGetPrice
正确情况下,测试结果为:
Running 1 test for test/FundMe.t.sol:FundMeTest
[PASS] testGetVersion() (gas: 10702)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.66ms