Skip to content

Error Handling#

Example of testing a complex function with multiple branches, including handling errors and ensuring all branches are covered.

contract ComplexFunction {
    error InsufficientBalance();
    error InvalidAmount();
    error Unauthorized();

    function complexTransfer(
        address from,
        address to,
        uint256 amount,
        bytes calldata data
    ) external {
        if (amount == 0) revert InvalidAmount();
        if (balances[from] < amount) revert InsufficientBalance();
        if (!isAuthorized(msg.sender)) revert Unauthorized();

        if (data.length > 0) {
            // Complex path 1
            _handleData(data);
            balances[from] -= amount;
            balances[to] += amount;
        } else {
            // Complex path 2
            balances[from] -= amount;
            balances[to] += amount;
        }
    }
}
@flow()
def flow_complex_transfer(self) -> None:
    # Test invalid amount branch
    with must_revert(ComplexFunction.InvalidAmount) as e:
        self.complex.complexTransfer(
            sender,
            recipient,
            0,
            b""
        )

    # Test insufficient balance branch
    amount = random_int(self._balances[sender] + 1, 2**256 - 1)
    with may_revert(ComplexFunction.InsufficientBalance) as e:
        self.complex.complexTransfer(
            sender,
            recipient,
            amount,
            b""
        )
        self._balances[sender] -= amount
        self._balances[recipient] += amount

    # Test unauthorized branch
    unauthorized = random_account()
    with must_revert(ComplexFunction.Unauthorized) as e:
        self.complex.complexTransfer(
            sender,
            recipient,
            100,
            b"",
            from_=unauthorized
        )

    # Test successful data path
    amount = random_int(0, self._balances[sender])
    data = random_bytes(1, 100)

    self.complex.complexTransfer(
        sender,
        recipient,
        amount,
        data,
        from_=authorized
    )

    self._balances[sender] -= amount
    self._balances[recipient] += amount
    self._validate_data_processed(data)

    # Test successful no-data path
    amount = random_int(0, self._balances[sender])
    self.complex.complexTransfer(
        sender,
        recipient,
        amount,
        b"",
        from_=authorized
    )

    self._balances[sender] -= amount
    self._balances[recipient] += amount

Example of testing complex inputs validation with different error handling scenarios.

// Example of complex inputs validation
contract ComplexInputs {
    error InvalidSignature();
    error ExpiredDeadline();
    error InvalidNonce();

    struct ComplexParams {
        address owner;
        uint256 value;
        uint256 nonce;
        uint256 deadline;
        bytes signature;
    }

    function validateAndExecute(ComplexParams calldata params) external {
        if (params.deadline < block.timestamp) revert ExpiredDeadline();
        if (params.nonce != nonces[params.owner]) revert InvalidNonce();
        if (!_verify(params)) revert InvalidSignature();

        // Execute logic
        _execute(params);
    }
}
@flow()
def flow_validate_complex_inputs(self) -> None:
    # Test expired deadline
    params = self._generate_valid_params()
    params.deadline = random_int(0, block.timestamp - 1)

    with must_revert(ComplexInputs.ExpiredDeadline):
        self.complex.validateAndExecute(params)

    # Test invalid nonce
    params = self._generate_valid_params()
    params.nonce = self._nonces[params.owner] + 1

    with must_revert(ComplexInputs.InvalidNonce):
        self.complex.validateAndExecute(params)

    # Test invalid signature
    params = self._generate_valid_params()
    params.signature = random_bytes(65)

    with must_revert(ComplexInputs.InvalidSignature):
        self.complex.validateAndExecute(params)

    # Test successful path
    params = self._generate_valid_params()
    self.complex.validateAndExecute(params)

    self._nonces[params.owner] += 1
    self._validate_execution(params)