3分钟Solidity: 13.1 Echidna测试框架

20 阅读1分钟

欢迎订阅专栏3分钟Solidity--智能合约--Web3区块链技术必学

Echidna

要获取此内容的最新版本,请访问 Cyfrin.io 上的 Echidna(代码示例)

Echidna是一个快速的智能合约(solidity)模糊测试框架,它是用Haskell语言编写的程序,实现基于以太坊智能合约属性的模糊测试。 它使用基于合约ABI的复杂的grammar-based模糊测试来验证用户定义断言或者Solidity断言。 Echidna在设计时考虑了模块化,可以很容易地被扩展成包含新的变种或者在特定情况下,测试特定合约。

使用 Echidna进行模糊测试的示例。

  1. 将 Solidity 合约保存为 TestEchidna.sol
  2. 在存储合约的文件夹中执行以下命令。
docker run -it --rm -v $PWD:/code trailofbits/eth-security-toolbox

在 Docker 中,您的代码将存储在根目录下的 /code文件夹中。

  1. 查看以下注释并执行 echidna命令。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/*
echidna TestEchidna.sol --contract TestCounter
*/
contract Counter {
    uint256 public count;

    function inc() external {
        count += 1;
    }

    function dec() external {
        count -= 1;
    }
}

contract TestCounter is Counter {
    function echidna_test_true() public view returns (bool) {
        return true;
    }

    function echidna_test_false() public view returns (bool) {
        return false;
    }

    function echidna_test_count() public view returns (bool) {
        // 这里我们在测试Counter.count应该始终<=5。
        // 测试将失败。Echidna足够聪明,会调用Counter.inc()
        // 超过5次。
        return count <= 5;
    }
}

/*
echidna TestEchidna.sol --contract TestAssert --test-mode assertion
*/
contract TestAssert {
    function test_assert(uint256 _i) external {
        assert(_i < 10);
    }

    // 更复杂的例子
    function abs(uint256 x, uint256 y) private pure returns (uint256) {
        if (x >= y) {
            return x - y;
        }
        return y - x;
    }

    function test_abs(uint256 x, uint256 y) external {
        uint256 z = abs(x, y);
        if (x >= y) {
            assert(z <= x);
        } else {
            assert(z <= y);
        }
    }
}

测试时间与发送者

Echidna可以对时间戳进行模糊测试。时间戳范围可在配置中设置,默认为7天。

合约调用者同样可在配置中设置。默认账户为:

  • 0x10000
  • 0x20000
  • 0x30000
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/*
docker run -it --rm -v $PWD:/code trailofbits/eth-security-toolbox
echidna EchidnaTestTimeAndCaller.sol --contract EchidnaTestTimeAndCaller
*/
contract EchidnaTestTimeAndCaller {
    bool private pass = true;
    uint256 private createdAt = block.timestamp;

    /*
    如果Echidna能够调用setFail(),测试将失败
    否则测试将通过
    */
    function echidna_test_pass() public view returns (bool) {
        return pass;
    }

    function setFail() external {
        /*
        如果延迟小于等于最大区块延迟,Echidna可以调用此函数。
        否则,Echidna将无法调用此函数。
        最大区块延迟可以通过在配置文件中指定来延长。
        */
        uint256 delay = 7 days;
        require(block.timestamp >= createdAt + delay);
        pass = false;
    }

    // 默认sender
    // 更改地址以查看测试失败
    address[3] private senders =
        [address(0x10000), address(0x20000), address(0x30000)];

    address private sender = msg.sender;

    // 将 _sender 作为输入,并要求 msg.sender == _sender
    // 以 _sender 为例进行反证
    function setSender(address _sender) external {
        require(_sender == msg.sender);
        sender = msg.sender;
    }

    // 检查默认发件人。发件人应为3个默认账户之一。
    function echidna_test_sender() public view returns (bool) {
        for (uint256 i; i < 3; i++) {
            if (sender == senders[i]) {
                return true;
            }
        }
        return false;
    }
}

Try on Remix