solidity中的错误处理

1. require 语句

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

语法

javascript 复制代码
 require(条件, "Error code");

示例

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

特点

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

2. assert 语句

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

语法

scss 复制代码
 assert(条件);

示例

java 复制代码
 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。


🧩 基本语法

scss 复制代码
 revert();
 revert("错误信息");

🧠 工作机制

当调用 revert() 时:

  • 当前函数执行被中止;
  • 所有状态更改(包括调用之前的更改)都会被回滚;
  • 未使用的 gas 会被退还给调用者;
  • 如果有错误信息,会被返回给调用者(前端或调用合约可以读取这个信息)。

🚨 使用场景

  1. 条件检查失败
scss 复制代码
 if (msg.value < price) {
     revert("Insufficient funds");
 }
  1. 配合自定义错误使用(gas 更省):
csharp 复制代码
 error NotOwner();
 ​
 function onlyOwner() public view {
     if (msg.sender != owner) {
         revert NotOwner();
     }
 }

自定义错误

csharp 复制代码
 // 简单错误(不带参数)
 error Unauthorized();
 ​
 // 带参数的错误
 error InsufficientBalance(uint256 available, uint256 required);
 ​
 // 复杂参数的错误
 error TransferFailed(address from, address to, uint256 amount, string reason);

触发自定义错误

scss 复制代码
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 编码:自动生成错误签名,方便客户端解析


💡 例子

ini 复制代码
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 结构

csharp 复制代码
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")
  • 可以获取错误描述字符串
  1. Panic(uint errorCode)
  • 对应 Solidity 的 "panic" 错误(类似断言失败)

  • 常见错误码:

    • 0x01: 断言失败
    • 0x11: 算术运算溢出
    • 0x12: 除以零
    • 0x21: 无效的数组索引
    • 0x31: 分配过多内存
    • 0x32: 调用未初始化的内部函数类型变量
  1. 默认的 catch
  • 捕获所有其他类型的错误
  • 包含原始错误数据(bytes 类型)

自定义修饰符

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

语法

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

示例

ini 复制代码
 contract Owner {
     address public owner;
 ​
     constructor() {
         owner = msg.sender;
     }
 ​
     // 自定义修饰符,仅允许合约所有者调用
     modifier onlyOwner {
         require(msg.sender == owner, "仅合约所有者可调用");
         _;
     }
 ​
     function changeOwner(address newOwner) public onlyOwner {
         owner = newOwner;
     }
 }

特点

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

综合示例

scss 复制代码
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); // 确保余额不为负
    }
}
相关推荐
亿坊电商1 天前
在搭建PHP框架时如何优雅处理错误与异常?
开发语言·php·代码规范
xlp666hub2 天前
C进阶之内存对齐,硬件总线和高并发伪共享的底层原理
面试·代码规范
电子科技圈2 天前
SiFive车规级RISC-V IP获IAR最新版嵌入式开发工具全面支持,加速汽车电子创新
嵌入式硬件·tcp/ip·设计模式·汽车·代码规范·risc-v·代码复审
尘世中一位迷途小书童5 天前
项目大扫除神器:Knip —— 将你的代码库“瘦身”到底
前端·架构·代码规范
UIUV7 天前
JavaScript内存管理与闭包原理:从底层到实践的全面解析
前端·javascript·代码规范
进击的丸子7 天前
跨平台人脸识别 SDK 部署指南
linux·后端·代码规范
于谦9 天前
git提交信息也能自动格式化了?committier快速体验
前端·javascript·代码规范
大怪v10 天前
【Virtual World 04】我们的目标,无限宇宙!!
前端·javascript·代码规范
UIUV11 天前
JavaScript流式输出技术详解与实践
前端·javascript·代码规范