错误处理

错误处理

1. require 语句

require 用于在执行函数之前检查条件是否满足。如果条件不满足,require 会抛出异常并回滚所有状态更改。它通常用于验证用户输入或外部调用的合法性。

语法

require(条件, "Error code");

示例

function transfer(address to, uint256 amount) public {
    require(to != address(0), "无效的接收地址");
    require(amount > 0, "转账金额必须大于0");
    // ... 转账逻辑 ...
}

特点

  • 用于输入验证
  • 抛出异常时会回滚状态
  • 可以附带错误信息

2. assert 语句

assert 用于检查内部逻辑的正确性,通常用于验证合约内部状态是否一致。如果条件不满足,assert 会抛出异常并回滚所有状态更改。它通常用于调试和确保合约逻辑的正确性。

语法

assert(条件);

示例

function divide(uint256 a, uint256 b) public pure returns (uint256) {
    uint256 result = a / b;
    assert(b != 0); // 确保除数不为0
    return result;
}

特点

  • 用于内部逻辑验证
  • 抛出异常时会回滚状态
  • 通常不附带错误信息

3. revert 关键字

revert 是 Solidity 中用于 回退交易(transaction)并恢复状态 的一个关键字。它经常用于错误处理,当某些条件不满足时立即终止执行并返还剩余的 gas。


🧩 基本语法

revert();
revert("错误信息");

🧠 工作机制

当调用 revert() 时:

  • 当前函数执行被中止;

  • 所有状态更改(包括调用之前的更改)都会被回滚;

  • 未使用的 gas 会被退还给调用者;

  • 如果有错误信息,会被返回给调用者(前端或调用合约可以读取这个信息)。


🚨 使用场景

  1. 条件检查失败
if (msg.value < price) {
    revert("Insufficient funds");
}
  1. 配合自定义错误使用(gas 更省):
error NotOwner();

function onlyOwner() public view {
    if (msg.sender != owner) {
        revert NotOwner();
    }
}

自定义错误

// 简单错误(不带参数)
error Unauthorized();

// 带参数的错误
error InsufficientBalance(uint256 available, uint256 required);

// 复杂参数的错误
error TransferFailed(address from, address to, uint256 amount, string reason);

触发自定义错误

function withdraw(uint256 amount) public {
    if (amount > balances[msg.sender]) {
        revert InsufficientBalance(balances[msg.sender], amount);
    }
    // 其他逻辑...
}

优势

  1. Gas 效率:比 require/revert 节省约 50-70% Gas

    • 普通 require: ~45 Gas

    • 自定义错误: ~20 Gas

  2. 参数化错误信息:可以携带任意数量和类型的参数

  3. 类型安全:编译器会检查错误类型和参数匹配

  4. ABI 编码:自动生成错误签名,方便客户端解析


💡 例子

pragma solidity ^0.8.0;

contract Shop {
    address public owner;
    uint256 public price = 1 ether;

    constructor() {
        owner = msg.sender;
    }

    function buy() public payable {
        if (msg.value < price) {
            revert("Not enough ETH sent");
        }
        // 购买逻辑
    }
}

4. 🔍 三者对比

特性 require revert assert
✅ 功能 条件检查,不满足则终止执行 手动触发终止执行,带/不带错误信息 检查程序内部逻辑是否永远成立
🎯 用途 验证输入、权限、调用状态 更灵活地中止执行 用来发现bug不变量失效
🔄 状态回滚 ✅ 会回滚 ✅ 会回滚 ✅ 会回滚
💸 Gas 返还 ✅ 未消耗的 gas 会返还 ✅ 会返还 ❌ 不返还(panic,重大错误)
📢 可带信息 "错误信息" "错误信息"error Type() ❌ 无信息(只有 Panic code)
🧱 推荐使用 外部输入条件、require权限检查等 业务逻辑失败、自定义 error 抛错 测试 / 内部断言,永远不该失败

5. try-catch 语句

Solidity 从 0.6.0 版本开始引入了 try-catch 语句,用于处理外部函数调用和合约创建中的错误。这是 Solidity 中错误处理的重要机制之一。

🚨适用场景

try-catch 只能用于以下两种情况:

  1. 外部函数调用(使用 address 调用或合约实例调用)

  2. 合约创建(使用 new 关键字)

完整的 try-catch 结构

try externalContract.someFunction(arg1, arg2) returns (uint256 result) {
    // 调用成功时执行的代码
    // 可以使用返回值 result
} catch Error(string memory reason) {
    // 当 revert(reasonString) 或 require(false, reasonString) 被调用时
    // 可以访问 reason
} catch Panic(uint errorCode) {
    // 当发生 panic 错误时(如除以零、数组越界等)
    // errorCode 表示错误类型
} catch (bytes memory lowLevelData) {
    // 当错误不符合上述任何类型时
    // 包含低级错误数据
}

错误类型详解

  1. Error(string memory reason)
  • 对应 revert("description") 或 require(false, "description")

  • 可以获取错误描述字符串

2. Panic(uint errorCode)

  • 对应 Solidity 的 "panic" 错误(类似断言失败)

  • 常见错误码:

    • 0x01: 断言失败

    • 0x11: 算术运算溢出

    • 0x12: 除以零

    • 0x21: 无效的数组索引

    • 0x31: 分配过多内存

    • 0x32: 调用未初始化的内部函数类型变量

  1. 默认的 catch 块
  • 捕获所有其他类型的错误

  • 包含原始错误数据(bytes 类型)

自定义修饰符

自定义修饰符(Modifier)用于在函数执行前或执行后添加额外的检查或逻辑。它可以复用代码,并且常用于权限控制或状态检查。

语法

modifier 修饰符名 {
    // 前置逻辑
    _; // 执行函数体
    // 后置逻辑
}

示例

contract Owner {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    // 自定义修饰符,仅允许合约所有者调用
    modifier onlyOwner {
        require(msg.sender == owner, "仅合约所有者可调用");
        _;
    }

    function changeOwner(address newOwner) public onlyOwner {
        owner = newOwner;
    }
}

特点

  • 可以复用代码
  • 常用于权限控制
  • 可以在函数执行前后添加逻辑

综合示例

contract Bank {
    mapping(address => uint256) public balances;

    // 自定义修饰符,检查余额是否足够
    modifier hasSufficientBalance(uint256 amount) {
        require(balances[msg.sender] >= amount, "余额不足");
        _;
    }

    // 存款函数
    function deposit() public payable {
        require(msg.value > 0, "存款金额必须大于0");
        balances[msg.sender] += msg.value;
    }

    // 取款函数
    function withdraw(uint256 amount) public hasSufficientBalance(amount) {
        balances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
        assert(balances[msg.sender] >= 0); // 确保余额不为负
    }
}

posted @ 2025-08-12 23:47  Lucas_coming  阅读(16)  评论(0)    收藏  举报