异常处理 #

一、异常处理概述 #

Solidity提供了三种异常处理方式:

方式 用途 Gas退还 错误类型
require 输入验证、条件检查 Error(string)
assert 内部不变量检查 Panic(uint256)
revert 复杂条件、自定义错误 Error/自定义

二、require #

2.1 基本用法 #

solidity
contract RequireDemo {
    // 基本验证
    function divide(uint256 a, uint256 b) public pure returns (uint256) {
        require(b != 0, "Division by zero");
        return a / b;
    }
    
    // 地址验证
    function transfer(address to, uint256 amount) public pure {
        require(to != address(0), "Invalid recipient");
        require(amount > 0, "Amount must be positive");
    }
    
    // 余额验证
    mapping(address => uint256) public balances;
    
    function withdraw(uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        balances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }
}

2.2 使用场景 #

solidity
contract RequireUseCases {
    address public owner;
    bool public paused;
    mapping(address => uint256) public balances;
    
    constructor() {
        owner = msg.sender;
    }
    
    // 1. 输入验证
    function setValue(uint256 value) public pure {
        require(value > 0, "Value must be positive");
        require(value <= 100, "Value too large");
    }
    
    // 2. 权限检查
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
    
    // 3. 状态检查
    modifier whenNotPaused() {
        require(!paused, "Contract paused");
        _;
    }
    
    // 4. 外部调用结果检查
    function callExternal(address target, bytes memory data) public returns (bool) {
        (bool success, bytes memory result) = target.call(data);
        require(success, "External call failed");
        return abi.decode(result, (bool));
    }
    
    // 5. 时间检查
    function claim() public view {
        require(block.timestamp >= 1700000000, "Too early");
    }
}

2.3 require无消息 #

solidity
contract RequireNoMessage {
    // 可以省略错误消息
    function check(bool condition) public pure {
        require(condition);  // 失败时显示空字符串
    }
    
    // 推荐:总是提供有意义的错误消息
    function checkBetter(bool condition) public pure {
        require(condition, "Condition not met");
    }
}

三、assert #

3.1 基本用法 #

solidity
contract AssertDemo {
    uint256 public constant MAX_SUPPLY = 1000000;
    uint256 public totalSupply;
    
    // 断言不变量
    function mint(uint256 amount) public {
        totalSupply += amount;
        assert(totalSupply <= MAX_SUPPLY);  // 不应该超过最大供应量
    }
    
    // 断言内部一致性
    mapping(address => uint256) public balances;
    uint256 public totalBalance;
    
    function deposit() public payable {
        balances[msg.sender] += msg.value;
        totalBalance += msg.value;
        
        // 断言内部一致性
        assert(address(this).balance == totalBalance);
    }
    
    function withdraw(uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        
        balances[msg.sender] -= amount;
        totalBalance -= amount;
        
        payable(msg.sender).transfer(amount);
        
        // 断言内部一致性
        assert(address(this).balance == totalBalance);
    }
}

3.2 assert触发Panic错误 #

solidity
contract PanicErrors {
    // Panic错误代码
    // 0x00: 保留
    // 0x01: assert失败
    // 0x11: 算术溢出(0.7.x及以下)
    // 0x12: 除以零或取余零
    // 0x21: 越界访问数组
    // 0x22: 访问负索引数组
    // 0x31: 调用空函数
    // 0x32: 类型转换失败
    // 0x41: 存储空间不足
    
    function demonstratePanic() public pure {
        assert(false);  // 触发Panic(0x01)
    }
}

3.3 require vs assert #

solidity
contract RequireVsAssert {
    mapping(address => uint256) public balances;
    uint256 public totalSupply;
    
    // require:验证外部输入
    function transfer(address to, uint256 amount) public {
        require(to != address(0), "Invalid recipient");  // 外部输入验证
        require(balances[msg.sender] >= amount, "Insufficient balance");  // 外部状态验证
        
        balances[msg.sender] -= amount;
        balances[to] += amount;
    }
    
    // assert:验证内部不变量
    function internalCheck() public view {
        uint256 sum = 0;
        // 假设我们遍历所有余额...
        assert(sum == totalSupply);  // 内部不变量
    }
}

四、revert #

4.1 基本用法 #

solidity
contract RevertDemo {
    // 简单revert
    function simpleRevert() public pure {
        revert("Something went wrong");
    }
    
    // 条件revert
    function conditionalRevert(bool shouldRevert) public pure {
        if (shouldRevert) {
            revert("Condition triggered revert");
        }
    }
    
    // 复杂条件
    function complexCondition(
        uint256 a,
        uint256 b,
        uint256 c
    ) public pure returns (uint256) {
        if (a == 0) {
            revert("a cannot be zero");
        }
        if (b == 0) {
            revert("b cannot be zero");
        }
        if (c == 0) {
            revert("c cannot be zero");
        }
        
        return (a / b) + c;
    }
}

4.2 revert vs require #

solidity
contract RevertVsRequire {
    // 使用require
    function withRequire(uint256 value) public pure {
        require(value > 0, "Value must be positive");
        require(value < 100, "Value too large");
        require(value % 2 == 0, "Value must be even");
    }
    
    // 使用revert(复杂条件)
    function withRevert(uint256 value) public pure {
        if (value <= 0) {
            revert("Value must be positive");
        }
        if (value >= 100) {
            revert("Value too large");
        }
        if (value % 2 != 0) {
            revert("Value must be even");
        }
    }
    
    // 混合使用
    function mixed(uint256 value) public pure {
        require(value > 0, "Value must be positive");
        
        if (value >= 100) {
            revert("Value too large");
        }
    }
}

五、自定义错误 #

5.1 定义错误 #

solidity
contract CustomErrors {
    // 定义错误
    error InsufficientBalance(uint256 available, uint256 required);
    error InvalidAddress(address addr);
    error TransferFailed();
    error Unauthorized(address caller);
    error ContractPaused();
    
    mapping(address => uint256) public balances;
    address public owner;
    bool public paused;
    
    constructor() {
        owner = msg.sender;
    }
}

5.2 使用自定义错误 #

solidity
contract CustomErrorsUsage {
    error InsufficientBalance(uint256 available, uint256 required);
    error InvalidAddress(address addr);
    error Unauthorized(address caller);
    
    mapping(address => uint256) public balances;
    address public owner;
    
    constructor() {
        owner = msg.sender;
    }
    
    // 使用自定义错误
    function withdraw(uint256 amount) public {
        if (balances[msg.sender] < amount) {
            revert InsufficientBalance(balances[msg.sender], amount);
        }
        
        balances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }
    
    // 权限检查
    modifier onlyOwner() {
        if (msg.sender != owner) {
            revert Unauthorized(msg.sender);
        }
        _;
    }
    
    // 地址验证
    function transfer(address to, uint256 amount) public {
        if (to == address(0)) {
            revert InvalidAddress(to);
        }
        
        // 转账逻辑
    }
}

5.3 自定义错误优势 #

solidity
contract CustomErrorAdvantages {
    // 传统方式:字符串错误(消耗更多Gas)
    function traditionalError(uint256 value) public pure {
        require(value > 0, "Value must be greater than zero");
    }
    
    // 自定义错误:更省Gas
    error ValueMustBePositive();
    
    function customError(uint256 value) public pure {
        if (value <= 0) {
            revert ValueMustBePositive();
        }
    }
    
    // 带参数的自定义错误
    error ValueOutOfRange(uint256 value, uint256 min, uint256 max);
    
    function withParameters(uint256 value) public pure {
        if (value < 10 || value > 100) {
            revert ValueOutOfRange(value, 10, 100);
        }
    }
}

六、try/catch #

6.1 基本用法 #

solidity
interface IExternalContract {
    function someFunction(uint256 value) external returns (uint256);
}

contract TryCatchDemo {
    IExternalContract public externalContract;
    
    event OperationSuccess(uint256 result);
    event OperationFailed(string reason);
    event OperationFailedBytes(bytes data);
    
    constructor(address _contract) {
        externalContract = IExternalContract(_contract);
    }
    
    function callExternal(uint256 value) public returns (uint256) {
        try externalContract.someFunction(value) returns (uint256 result) {
            emit OperationSuccess(result);
            return result;
        } catch Error(string memory reason) {
            // 捕获require/revert错误
            emit OperationFailed(reason);
            return 0;
        } catch (bytes memory data) {
            // 捕获其他错误(包括自定义错误)
            emit OperationFailedBytes(data);
            return 0;
        }
    }
}

6.2 try/catch示例 #

solidity
contract TryCatchExample {
    struct Result {
        bool success;
        uint256 value;
        string errorMessage;
    }
    
    ExternalContract public target;
    
    constructor(address _target) {
        target = ExternalContract(_target);
    }
    
    function safeCall(uint256 value) public returns (Result memory) {
        try target.process(value) returns (uint256 result) {
            return Result(true, result, "");
        } catch Error(string memory reason) {
            return Result(false, 0, reason);
        } catch {
            return Result(false, 0, "Unknown error");
        }
    }
}

contract ExternalContract {
    function process(uint256 value) external pure returns (uint256) {
        require(value > 0, "Value must be positive");
        require(value < 100, "Value too large");
        return value * 2;
    }
}

6.3 try/catch限制 #

solidity
contract TryCatchLimitations {
    // try/catch只能用于外部调用
    // 不能用于内部函数调用
    
    function internalCall() public pure returns (uint256) {
        // 错误:不能对内部函数使用try/catch
        // try internalFunction() { } catch { }
        
        return internalFunction();
    }
    
    function internalFunction() internal pure returns (uint256) {
        require(false, "Always fails");
        return 0;
    }
    
    // 只能用于external调用
    ExternalContract public external;
    
    function externalCall() public returns (uint256) {
        try external.process() returns (uint256 result) {
            return result;
        } catch {
            return 0;
        }
    }
}

七、实际应用示例 #

7.1 安全转账 #

solidity
contract SafeTransfer {
    error TransferFailed(address to, uint256 amount);
    error InsufficientBalance(uint256 available, uint256 required);
    
    mapping(address => uint256) public balances;
    
    event Transfer(address indexed from, address indexed to, uint256 amount);
    
    function transfer(address to, uint256 amount) public {
        if (balances[msg.sender] < amount) {
            revert InsufficientBalance(balances[msg.sender], amount);
        }
        
        balances[msg.sender] -= amount;
        balances[to] += amount;
        
        emit Transfer(msg.sender, to, amount);
    }
    
    function withdraw(uint256 amount) public {
        if (balances[msg.sender] < amount) {
            revert InsufficientBalance(balances[msg.sender], amount);
        }
        
        balances[msg.sender] -= amount;
        
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        if (!success) {
            revert TransferFailed(msg.sender, amount);
        }
    }
    
    receive() external payable {
        balances[msg.sender] += msg.value;
    }
}

7.2 访问控制 #

solidity
contract AccessControl {
    error Unauthorized(address caller, string role);
    error ContractPaused();
    
    address public owner;
    mapping(address => bool) public admins;
    bool public paused;
    
    constructor() {
        owner = msg.sender;
    }
    
    modifier onlyOwner() {
        if (msg.sender != owner) {
            revert Unauthorized(msg.sender, "owner");
        }
        _;
    }
    
    modifier onlyAdmin() {
        if (!admins[msg.sender] && msg.sender != owner) {
            revert Unauthorized(msg.sender, "admin");
        }
        _;
    }
    
    modifier whenNotPaused() {
        if (paused) {
            revert ContractPaused();
        }
        _;
    }
    
    function setAdmin(address user, bool status) public onlyOwner {
        admins[user] = status;
    }
    
    function setPaused(bool _paused) public onlyAdmin {
        paused = _paused;
    }
    
    function sensitiveOperation() public onlyAdmin whenNotPaused {
        // 敏感操作
    }
}

7.3 代币合约 #

solidity
contract Token {
    error InsufficientBalance(uint256 available, uint256 required);
    error InsufficientAllowance(uint256 available, uint256 required);
    error InvalidAddress(address addr);
    error ZeroAmount();
    
    string public name = "MyToken";
    string public symbol = "MTK";
    uint8 public decimals = 18;
    uint256 public totalSupply;
    
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;
    
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    
    constructor(uint256 _totalSupply) {
        totalSupply = _totalSupply;
        balanceOf[msg.sender] = _totalSupply;
        emit Transfer(address(0), msg.sender, _totalSupply);
    }
    
    function transfer(address to, uint256 amount) public returns (bool) {
        if (to == address(0)) revert InvalidAddress(to);
        if (amount == 0) revert ZeroAmount();
        if (balanceOf[msg.sender] < amount) {
            revert InsufficientBalance(balanceOf[msg.sender], amount);
        }
        
        balanceOf[msg.sender] -= amount;
        balanceOf[to] += amount;
        
        emit Transfer(msg.sender, to, amount);
        return true;
    }
    
    function approve(address spender, uint256 amount) public returns (bool) {
        if (spender == address(0)) revert InvalidAddress(spender);
        
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }
    
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public returns (bool) {
        if (from == address(0)) revert InvalidAddress(from);
        if (to == address(0)) revert InvalidAddress(to);
        if (amount == 0) revert ZeroAmount();
        if (balanceOf[from] < amount) {
            revert InsufficientBalance(balanceOf[from], amount);
        }
        if (allowance[from][msg.sender] < amount) {
            revert InsufficientAllowance(allowance[from][msg.sender], amount);
        }
        
        balanceOf[from] -= amount;
        balanceOf[to] += amount;
        allowance[from][msg.sender] -= amount;
        
        emit Transfer(from, to, amount);
        return true;
    }
}

八、最佳实践 #

8.1 选择正确的异常处理方式 #

solidity
contract BestPractices {
    // require:验证外部输入和条件
    function validateInput(uint256 value) public pure {
        require(value > 0, "Value must be positive");
    }
    
    // assert:验证内部不变量
    function checkInvariant() public view {
        assert(address(this).balance >= 0);  // 永远为真
    }
    
    // revert:复杂条件或自定义错误
    function complexValidation(uint256 value) public pure {
        if (value <= 0) {
            revert("Value must be positive");
        }
        if (value > 100) {
            revert("Value too large");
        }
    }
    
    // 自定义错误:节省Gas
    error InvalidValue(uint256 value);
    
    function withCustomError(uint256 value) public pure {
        if (value <= 0 || value > 100) {
            revert InvalidValue(value);
        }
    }
}

8.2 提供有意义的错误消息 #

solidity
contract MeaningfulErrors {
    // 不推荐:模糊的错误消息
    function bad() public pure {
        require(false, "Error");
    }
    
    // 推荐:明确的错误消息
    function good() public pure {
        require(false, "Transfer failed: insufficient balance");
    }
    
    // 推荐:使用自定义错误
    error TransferFailed(address from, address to, uint256 amount, string reason);
}

8.3 错误处理模式 #

solidity
contract ErrorPatterns {
    // 模式1:提前检查
    function earlyCheck(uint256 value) public pure returns (uint256) {
        require(value > 0, "Value must be positive");
        require(value < 100, "Value too large");
        
        return value * 2;
    }
    
    // 模式2:检查-生效-交互
    mapping(address => uint256) public balances;
    
    function withdraw(uint256 amount) public {
        // 检查
        require(balances[msg.sender] >= amount, "Insufficient balance");
        
        // 生效
        balances[msg.sender] -= amount;
        
        // 交互
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        require(success, "Transfer failed");
    }
}

九、总结 #

异常处理要点:

方式 用途 Gas退还 推荐场景
require 条件验证 输入验证、权限检查
assert 不变量检查 内部一致性检查
revert 复杂条件 复杂逻辑、自定义错误
自定义错误 节省Gas 生产环境

使用建议:

  • 使用require验证外部输入
  • 使用assert验证内部不变量
  • 使用自定义错误节省Gas
  • 提供有意义的错误消息
  • 遵循检查-生效-交互模式

接下来,让我们学习函数!

最后更新:2026-03-27