不变量测试是一种软件测试方法,专注于验证系统在各种条件下某些属性或状态始终保持不变。在区块链和DeFi应用中,这种测试方法尤为重要,因为智能合约一旦部署到链上就无法更改,而且直接管理资产。不变量测试帮助开发者确保无论用户如何与合约交互,关键的安全性和功能性特性始终得到保持。
不变量测试与传统的单元测试不同:
ERC20是以太坊上最广泛使用的代币标准之一。它定义了转账、授权等基本功能。接下来我们以ERC20为例来了解,如何对协议的关键属性进行不变量测试(资料完全来自Trail of Bits开源学习仓库)。
function test_ERC20_constantSupply() public virtual { require(!isMintableOrBurnable); assertEq(initialSupply, totalSupply(), "Token supply was modified");}这个测试确保在没有铸造或销毁机制的代币中,总供应量始终保持不变。这是代币经济模型的基本保证。
2. test_ERC20_userBalanceNotHigherThanSupply - 任何用户的余额都不应大于代币的总供应量。
function test_ERC20_userBalanceNotHigherThanSupply() public { assertLte( balanceOf(msg.sender), totalSupply(), "User balance higher than total supply" );}这个不变量确保单个用户的余额永远不能超过代币的总供应量,这是逻辑上的限制,违反此规则将表明存在严重漏洞。
3. test_ERC20_usersBalancesNotHigherThanSupply - 用户余额总和不应大于代币的总供应量。
4. test_ERC20_zeroAddressBalance - 零地址的代币余额应为零。
function test_ERC20_zeroAddressBalance() public { assertEq( balanceOf(address(0)), 0, "Address zero balance not equal to zero" );}零地址(0x0)是一个特殊地址,它不应该持有任何代币。这个测试确保零地址的余额始终为零,防止代币被意外"烧毁"到一个无法访问的地址。
转账相关不变量
5. test_ERC20_transferToZeroAddress - 不应允许向零地址进行transfer转账。
function test_ERC20_transferToZeroAddress() public { uint256 balance = balanceOf(address(this)); require(balance > 0);
bool r = transfer(address(0), balance); assertWithMsg(r == false, "Successful transfer to address zero");}这个测试验证向零地址的转账应该被拒绝,这是一个重要的安全机制,防止代币被永久锁定。
6. test_ERC20_transferFromToZeroAddress - 不应允许向零地址进行transferFrom转账。
7. test_ERC20_selfTransfer - 自我transfer转账不应破坏账户记录。
function test_ERC20_selfTransfer(uint256 value) public { uint256 balance_sender = balanceOf(address(this)); require(balance_sender > 0);
bool r = this.transfer(address(this), value % (balance_sender + 1)); assertWithMsg(r == true, "Failed self transfer"); assertEq( balance_sender, balanceOf(address(this)), "Self transfer breaks accounting" );}这个测试确保向自己转账不会意外改变余额,这是确保账户系统一致性的重要保障。
8. test_ERC20_selfTransferFrom - 自我transferFrom转账不应破坏账户记录。
9. test_ERC20_transferMoreThanBalance - 不应允许转账金额超过账户余额的transfer操作。
10. test_ERC20_transferFromMoreThanBalance - 不应允许转账金额超过账户余额的transferFrom操作。
11. test_ERC20_transferZeroAmount - 零金额的transfer转账不应破坏账户记录。
12. test_ERC20_transferFromZeroAmount - 零金额的transferFrom转账不应破坏账户记录。
13. test_ERC20_transfer - 有效的transfer转账应正确更新账户记录。
function test_ERC20_transfer(address target, uint256 amount) public { require(target != address(this)); uint256 balance_sender = balanceOf(address(this)); uint256 balance_receiver = balanceOf(target); require(balance_sender > 2); uint256 transfer_value = (amount % balance_sender) + 1;
bool r = this.transfer(target, transfer_value); assertWithMsg(r == true, "transfer failed"); assertEq( balanceOf(address(this)), balance_sender - transfer_value, "Wrong source balance after transfer" ); assertEq( balanceOf(target), balance_receiver + transfer_value, "Wrong target balance after transfer" );}这个不变量测试确保转账后,发送方余额减少,接收方余额增加,且变动金额与转账金额完全一致。
14. test_ERC20_transferFrom - 有效的transferFrom转账应正确更新账户记录。
15. test_ERC20_setAllowance - 调用approve时应正确设置授权额度。
function test_ERC20_setAllowance(address target, uint256 amount) public { bool r = this.approve(target, amount); assertWithMsg(r == true, "Failed to set allowance via approve"); assertEq( allowance(address(this), target), amount, "Allowance not set correctly" );}这个测试验证approve函数应该正确设置授权金额。
16. test_ERC20_setAllowanceTwice - 连续两次调用approve时应正确更新授权额度。
17. test_ERC20_spendAllowanceAfterTransfer - 调用transferFrom后,授权额度应正确更新。
function test_ERC20_spendAllowanceAfterTransfer( address target, uint256 amount) public { require(target != address(this) && target != address(0)); require(target != msg.sender); uint256 balance_sender = balanceOf(msg.sender); uint256 current_allowance = allowance(msg.sender, address(this)); require(balance_sender > 0 && current_allowance > balance_sender); uint256 transfer_value = (amount % balance_sender) + 1;
bool r = this.transferFrom(msg.sender, target, transfer_value); assertWithMsg(r == true, "transferFrom failed");
// Some implementations take an allowance of 2**256-1 as infinite, and therefore don't update if (current_allowance != type(uint256).max) { assertEq( allowance(msg.sender, address(this)), current_allowance - transfer_value, "Allowance not updated correctly" ); } }}这个测试确保在使用transferFrom后,授权额度应该减少,除非使用的是特殊的"无限授权"值。
burn时,用户余额和总供应量应正确更新。19. test_ERC20_burnFrom - 调用burnFrom时,用户余额和总供应量应正确更新。
20. test_ERC20_burnFromUpdateAllowance - 调用burnFrom时,授权额度应正确更新。
21. test_ERC20_mintTokens - 铸造后,用户余额和总供应量应正确更新。
22. test_ERC20_pausedTransfer - 启用暂停状态时,不应允许进行代币transfer转账。
23. test_ERC20_pausedTransferFrom - 启用暂停状态时,不应允许进行代币transferFrom转账。
24. test_ERC20_setAndIncreaseAllowance - 调用increaseAllowance时,授权额度应正确更新。
25. test_ERC20_setAndDecreaseAllowance - 调用decreaseAllowance时,授权额度应正确更新。
不变量测试通过系统地测试ERC20代币的关键属性,不仅验证了基本功能是否正常工作,更重要的是确保了在各种边缘情况下系统的完整性和安全性。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。