模糊测试进阶,无状态模糊测试和不变式测试

系列文章目录

模糊测试入门与assert()函数



前言

我们继续深入模糊测试,本篇文章介绍不变测试,这是一种将代码进行多个步骤的随机测试。

一、无状态模糊测试与不变模糊测试

无状态模糊测试(Stateless Fuzz Testing)和不变式模糊测试(Stateful Fuzz Testing)是软件测试中的两种高级技术,尤其在智能合约和系统安全性测试中非常重要。以下是这两种测试方法的背景知识:

无状态模糊测试(Stateless Fuzz Testing)

背景:

  • 模糊测试(Fuzz Testing)是一种自动化的软件测试方法,通过自动产生大量异常、随机或边界值的输入数据来测试程序,观察程序是否能够妥善处理这些输入。
  • 无状态模糊测试不考虑系统或合约的当前状态,它专注于函数的独立执行和返回结果。

目的:

  • 识别函数实现中的潜在错误,如溢出、除以零、非法访问等。
  • 确保函数在各种输入下都能稳定运行,不会导致意外崩溃或错误。

方法:

  • 自动生成或选择一组测试输入。
  • 对每个输入执行函数,并监控其行为(如异常、返回值)。

应用场景:

  • 适用于函数行为相对独立,不受外部状态影响的情况。
  • 在智能合约中,用于测试纯函数(Pure Functions)或在隔离环境下的合约函数。

不变式模糊测试(Stateful Fuzz Testing)

背景:

  • 不变式(Invariant)是系统或合约中必须始终为真的条件或属性。
  • 状态ful模糊测试关注在一系列操作或交易后,系统状态是否仍然满足特定的不变式。

目的:

  • 确保合约在经过一系列状态变化后,仍然保持其核心属性和约束。
  • 发现状态相关的逻辑错误,这些错误可能只在特定的状态序列后显现。

方法:

  • 设计测试用例以模拟状态变化,可能包括多次函数调用和交易。
  • 在测试序列的开始和结束时检查不变式是否成立。

应用场景:

  • 适用于需要维护特定状态或顺序的系统。
  • 在智能合约中,用于测试合约的状态依赖函数和长时间运行的合约逻辑。

智能合约测试中的考虑

  • Gas 效率:在智能合约中,测试还需要考虑交易的 Gas 成本,优化测试用例以避免不必要的资源消耗。
  • 安全性:智能合约一旦部署,其逻辑和状态管理对安全性至关重要,模糊测试有助于发现潜在的安全漏洞。
  • 自动化:模糊测试通常需要自动化工具来生成大量测试用例,并监控测试结果。

结合使用

在实践中,无状态和不变式模糊测试往往结合使用,以覆盖不同的测试场景和确保合约的全面测试。无状态测试提供了快速、广泛的覆盖,而不变式测试则深入挖掘状态相关的复杂问题。

通过这两种测试方法,开发者可以提高智能合约的质量和可靠性,减少因逻辑错误或状态管理不当导致的安全风险。

二、举例不变模糊测试的优点

1.代码分析

代码如下(示例):

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import  ssl
ssl._create_default_https_context = ssl._create_unverified_context

这段代码定义了一个名为 StatefulFuzzCatches 的智能合约,它包含两个公共状态变量、一个公共函数和一个修改状态的函数。以下是对合约的详细分析:

  1. 状态变量:

    • myValue: 一个 uint256 类型的公共变量,初始值设为 1。
    • storedValue: 另一个 uint256 类型的公共变量,初始值设为 100。
  2. 不变式(Invariant):

    • 合约的注释中声明了一个不变式:doMoreMathAgain 函数永远不应该返回 0。这意味着无论输入什么值,该函数的返回值都应该是有效的,并且不应该触发任何导致返回 0 的逻辑。
  3. doMoreMathAgain 函数:

    • 这是一个公共函数,接受一个 uint128 类型的参数 myNumber
    • 函数内部执行了一个简单的算术操作:将 myNumber 转换为 uint256 类型,除以 1(这是一个无操作,因为任何数除以 1 都等于其本身),然后与 myValue 相加,得到 response
    • 然后将 response 赋值给 storedValue,并将 response 返回给调用者。
  4. changeValue 函数:

    • 这也是一个公共函数,允许外部调用者更新 myValue 的值。
  5. 潜在问题:

    • 根据不变式的描述,doMoreMathAgain 函数不应该返回 0。然而,如果 changeValue 函数被用来将 myValue 设置为一个可能导致 response 为 0 的值,那么不变式可能会被违反。例如,如果 myValue 被设置为0,而 myNumber 被设置为 0,那么 response 变成 0。还有一个更特殊的例子在于solidity的版本是否会溢出。但我们发现合约十分的聪明,myNumber 是一个 uint128 类型的参数。由于 uint128 可以安全地转换为 uint256 而不会发生溢出,这里使用了 uint256(myNumber) 进行转换。

2.使用模糊测试

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

// INVARIANT: doMoreMathAgain should never return 0
contract StatefulFuzzCatches {
    uint256 public myValue = 1;
    uint256 public storedValue = 100;

    /*
     * @dev Should never return 0
     */
    function doMoreMathAgain(uint128 myNumber) public returns (uint256) {
        uint256 response = (uint256(myNumber) / 1) + myValue;
        storedValue = response;
        return response;
    }

    function changeValue(uint256 newValue) public {
        myValue = newValue;
    }
}

在智能合约测试中,无状态模糊测试(Stateless Fuzz Testing)和不变式模糊测试(Stateful Fuzz Testing)是两种不同的测试方法,它们各自有不同的目标和实现方式:

无状态模糊测试(Stateless Fuzz Testing)

定义:
无状态模糊测试是一种测试方法,它不考虑合约的当前状态,只关注函数的输入和输出。测试用随机或半随机生成的输入数据调用合约函数,以发现潜在的错误或异常行为。

特点:

  • 每次调用都是独立的,不依赖于之前的调用结果。
  • 测试用例之间没有状态依赖。
  • 快速执行,可以大量生成和运行测试用例。

实现方式:

  • 随机生成各种输入数据。
  • 调用合约的函数,并检查返回值和事件日志。
  • 使用断言来检查预期条件是否满足。

示例:

function testFuzzPassesEasyInvariant(uint128 randomNumber) public {
    sfc.doMoreMathAgain(randomNumber);
    assert(sfc.storedValue() != 0);
}

这个测试函数接受一个随机数,调用doMoreMathAgain函数,并断言返回值不为0。
输出

forge test --mt  testFuzzPassesEasyInvariant              
[⠒] Compiling...
No files changed, compilation skipped

Ran 1 test for test/invariant-break/StatefulFuzzCatchesTest.t.sol:StatefulFuzzCatchesTest
[PASS] testFuzzPassesEasyInvariant(uint128) (runs: 1000, μ: 13802, ~: 13805)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 45.85ms (42.85ms CPU time)

Ran 1 test suite in 47.24ms (45.85ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

编译通过并没有找到错误

不变式模糊测试(Stateful Fuzz Testing)

定义:
不变式模糊测试关注合约的状态变化。它模拟合约在一系列交易后的状态,并验证合约的某些关键属性是否始终保持不变。

特点:

  • 测试用例可能会考虑合约的当前状态,并基于此状态生成输入。
  • 可以捕获那些只在特定状态组合下出现的bug。
  • 通常比无状态测试更复杂,需要维护测试的状态。

实现方式:

  • 使用特定的序列或模式来模拟合约的状态变化。
  • 在一系列交易后,验证合约的某个属性是否仍然满足。
  • 使用不变式检查函数来断言关键属性。

targetContract(address(sfc)); 这行代码其目的是将测试合约的地址指向被测试的合约实例,通过一些列的随机执行来测试合约的结果

示例:

function invariant_testMathDoesntReturnZero() public view {
    assert(sfc.storedValue() != 0);
}

输出

[FAIL. Reason: invariant_testMathDoesntReturnZero persisted failure revert]
        [Sequence]
                sender=0x8Ab55842614e60Cf71E9F480BA37245e8e1277Bd addr=[src/invariant-break/StatefulFuzzCatches.sol:StatefulFuzzCatches]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=changeValue(uint256) args=[115792089237316195423570985008687907853269984665640564039457584007913129639934 [1.157e77]]
                sender=0x000000000000000000000000000000000000002A addr=[src/invariant-break/StatefulFuzzCatches.sol:StatefulFuzzCatches]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=doMoreMathAgain(uint128) args=[257949695690360443 [2.579e17]]
 invariant_testMathDoesntReturnZero() (runs: 1, calls: 1, reverts: 1)
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.23ms (485.23µs CPU time)

Ran 1 test suite in 7.00ms (1.23ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/invariant-break/StatefulFuzzCatchesTest.t.sol:StatefulFuzzCatchesTest
[FAIL. Reason: invariant_testMathDoesntReturnZero persisted failure revert]
        [Sequence]
                sender=0x8Ab55842614e60Cf71E9F480BA37245e8e1277Bd addr=[src/invariant-break/StatefulFuzzCatches.sol:StatefulFuzzCatches]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=changeValue(uint256) args=[115792089237316195423570985008687907853269984665640564039457584007913129639934 [1.157e77]]
                sender=0x000000000000000000000000000000000000002A addr=[src/invariant-break/StatefulFuzzCatches.sol:StatefulFuzzCatches]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=doMoreMathAgain(uint128) args=[257949695690360443 [2.579e17]]
 invariant_testMathDoesntReturnZero() (runs: 1, calls: 1, reverts: 1)

可想而知这种测试可以更全面的执行到问题所在,顺便还发现了合约的溢出问题,编写

 function testFuzzPassesEasyInvariant(uint128 randomNumber) public {
        sfc.changeValue(type(uint256).max);
        sfc.doMoreMathAgain(randomNumber);
        

可以得到,

Ran 1 test for test/invariant-break/StatefulFuzzCatchesTest.t.sol:StatefulFuzzCatchesTest
[FAIL. Reason: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=0xfe0f460c00000000000000000000000000000000000000000000000000000000000000e5 args=[229]] testFuzzPassesEasyInvariant(uint128) (runs: 0, μ: 0, ~: 0)
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 23.99ms (22.68ms CPU time)

Ran 1 test suite in 24.99ms (23.99ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/invariant-break/StatefulFuzzCatchesTest.t.sol:StatefulFuzzCatchesTest
[FAIL. Reason: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=0xfe0f460c00000000000000000000000000000000000000000000000000000000000000e5 args=[229]] testFuzzPassesEasyInvariant(uint128) (runs: 0, μ: 0, ~: 0)

Encountered a total of 1 failing tests, 0 tests succeeded`

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值