Foundry 作弊码
本章学习什么是作弊码,以及如何使用作弊码来测试智能合约。
代码:github.com/hitadao/solidity
在大多数时候,仅仅通过输出信息来测试智能合约是不够的,有的时候还需要随时更改区块链的状态。
为了操纵区块链的状态,Foundry
附带了一组作弊码 Cheatcodes
。 作弊码在测试时非常有用,可以显著地提高测试效率。
比如:合约的某项功能需要在合约部署 1 个小时后生效。这种情况下,我们就不能等待 1 个小时才进行测试,而是直接通过作弊码,直接跳过 1 小时。
再比如,需要模拟某个特定的巨鲸地址给我们转了一笔账。由于我们没有这个地址的私钥,无法进行测试。这时候,我们就可以通过作弊码模拟巨鲸转账。
在 Foundry
项目中,可以通过 Forge
标准库的 vm
库合约,轻松使用作弊码。
示例合约
下面,我们为只能由智能合约所有者调用的函数,编写一个测试用例:
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import "forge-std/Test.sol"; error Unauthorized(); contract OwnerOnly { address public constant owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; uint256 public count; function increment() external { if (msg.sender != owner) { revert Unauthorized(); } count++; } } contract OwnerOnlyTest is Test { OwnerOnly ownerOnly; function setUp() public { ownerOnly = new OwnerOnly(); } function testIncrementAsOwner() public { ownerOnly.increment(); assertEq(ownerOnly.count(), 1); } }
我们来运行测试 forge test
,发现无法通过测试。
[⠆] Compiling... No files changed, compilation skipped Ran 1 test for test/1.t.sol:OwnerOnlyTest [FAIL. Reason: Unauthorized()] testIncrementAsOwner() (gas: 5446) Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 936.52µs (78.21µs CPU time) Ran 1 test suite in 289.68ms (936.52µs CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests) Failing tests: Encountered 1 failing test in test/1.t.sol:OwnerOnlyTest [FAIL. Reason: Unauthorized()] testIncrementAsOwner() (gas: 5446) Encountered a total of 1 failing tests, 0 tests succeeded
这是因为被测试的函数 increment,它的调用者地址必须是合约所有者 0x5B38...ddC4。
我们在测试用例合约 OwnerOnlyTest 的函数 setUp 中,创建了合约实例 ownerOnly。所以,ownerOnly 的所有者是 OwnerOnlyTest,而不是特定地址 0x5B38...ddC4。
由于我们没有 0x5B38...ddC4 的私钥,所以无法使用这个地址来创建 OwnerOnly 合约的实例。
在这种情况下,为了测试函数 increment() 的功能,我们就可以使用作弊码,伪装测试者的身份是 0x5B38...ddC4。
使用作弊码
下面,我们使用作弊码,模拟调用 increment 函数的地址为 0x5B38...ddC4,编写一个测试用例:
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import "forge-std/Test.sol"; error Unauthorized(); contract OwnerOnly { address public constant owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; uint256 public count; function increment() external { if (msg.sender != owner) { revert Unauthorized(); } count++; } } contract OwnerOnlyTest is Test { OwnerOnly ownerOnly; function setUp() public { ownerOnly = new OwnerOnly(); } function testIncrementAsOwner() public { vm.prank(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4); ownerOnly.increment(); assertEq(ownerOnly.count(), 1); } }
我们来运行测试 forge test
,测试顺利通过。
[⠆] Compiling... [⠢] Compiling 1 files with 0.8.21 [⠰] Solc 0.8.21 finished in 1.86s Compiler run successful! Ran 1 test for test/1.t.sol:OwnerOnlyTest [PASS] testIncrementAsOwner() (gas: 31636) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 12.05ms (2.65ms CPU time) Ran 1 test suite in 320.19ms (12.05ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
关于 vm
支持的所有作弊码,我们会在后面的内容中详细列出。