惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
博客园 - 司徒正美
Last Week in AI
Last Week in AI
Recent Announcements
Recent Announcements
Y
Y Combinator Blog
博客园 - 聂微东
M
MIT News - Artificial intelligence
博客园_首页
Jina AI
Jina AI
博客园 - 叶小钗
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hackread – Cybersecurity News, Data Breaches, AI and More
J
Java Code Geeks
F
Fortinet All Blogs
aimingoo的专栏
aimingoo的专栏
小众软件
小众软件
Vercel News
Vercel News
The Cloudflare Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
N
Netflix TechBlog - Medium
B
Blog
Google DeepMind News
Google DeepMind News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

秋码分享

我是如何解决将 c++ 编译成可以在 node.js 中使用的 *.node,中间出现的一大堆问题的(指纹浏览器基石篇) eSIM Plus 爱沙尼亚手机号彻底翻车?“永久有效”悄然变成了一年! 接码平台 SMS-Activate 余额可以转移到新平台使用,截止日期:2026年1月29日 是时候将 hugo-theme-kiwi 主题提交到 themes.gohugo.io 站点上了 Flux2 刚开源就凉了?Z-Image 本地部署狠狠打了个样 声音的未来:Chatterbox —— 用「夸张度旋钮」提升表现力的开源 TTS 向导 还以为那只是换个背景?Qwen-Image-Edit 在 ComfyUI 中能做到更离谱的事 Windows 结合最新版 ComfyUI 部署阿里最新开源的 Qwen-Image 图像大模型 从零样本到跨场景:Seed-VC语音转换技术的革命性突破 大语音模型轻量化革命:MegaTTS3 如何重新定义文本生成语音的技术边界(windows篇) 竞赛级编程大模型OlympicCoder-7B之本地部署(Windows篇) 阿里开源了端到端全模态大模型Qwen-2.5-Omini-7B之本地部署(windows篇) 语音识别之whisper本地部署(实时语音之开篇) 甭管是个人还是企业都能部署的Mistral-Small3.1,远超同级别的模型 文生音乐开源项目DiffRhythm,8G显存本地部署之Windows篇 阿里QwQ-32B本地部署指南:用Ollama轻松运行320亿参数大模型 基于Qwen2.5大模型的Spark-TTS,零样本语音克隆,CPU可运行之本地部署(Windows篇) 智谱开源了文生图CogView4-6B模型,支持中文提示词之本地部署(Windows篇) 基于歌词生成整首歌的开源AI音乐模型,支持中、英、日、韩等多种语言,本地化部署YuE(windows篇) 阿里云开源的文生视频万相 Wan2.1之本地部署Wan2.1-T2V-1.3B模型 互动式开源AI图像编辑神器,Windows11本地部署 MagicQuill 本地部署Qwen2.5-VL-7B-Instruct多模态视觉大模型(Windows篇) 保持角色一致性的绘本生成AI开源项目之Story-Adapter本地部署Windows篇 本地部署 Stable Diffusion 3.5(最新 ComfyUI记录篇) 谁说Win7安装不了Node.js最新版的呢?都2025年,还不更新系统到Win11 vs code远程调试Linux服务器上的php代码 浏览器定制 | Windows11 编译 Chromium 133.0.6885.0(截稿前Chromium最新版之编译篇[一]) 不说是彻底搞懂,至少让你不再惧怕c/c++指针,以及各种奇葩指针变种 解决windows下php8.x及以上版本,在Apache2.4中无法加载CURL扩展的问题 在 Windows8.1 下编译 Chromium (103.0.5060.68 之三)
玩以太坊链上项目的必备技能(错误处理以及异常-Solidity之旅...
一个游离于山间之上的江湖漫行者。A jungle wanderer who wanders above the mountain · 2022-12-21 · via 秋码分享

它有许多功能来解决在编译时或运行时可能发生的潜在问题。即使语法错误检查发生在编译时,运行时错误也很难捕捉,主要发生在合约执行过程中。一些运行时错误的例子包括除以0的类型错误,数组超出索引错误,等等。

错误处理

作为开发者的我们知道,我们所编写出来的程序难免会出现 bug ,而要做的是捕获异常,给用户抛出一个友好地错误提示。

而在 Solidity 中,根据状态恢复异常来处理错误,该异常将撤销在当前调用中对状态所做的所有修改,与此同时,还向调用者标记错误。

它有许多功能来解决在编译时或运行时可能发生的潜在问题。即使语法错误检查发生在编译时,运行时错误也很难捕捉,主要发生在合约执行过程中。一些运行时错误的例子包括除以0的类型错误,数组超出索引错误,等等。

实际上,Solidity的错误处理确保了原子性这一属性。当一个智能合约调用因错误而终止时,所有的状态变化(即对变量、余额等的改变)都会被恢复,一直到合约调用链。

Solidity 有三种处理错误的方式:requireassert*revert*

require

require(condition);
require(condition, "description");

require 语句声明了运行该函数的先决条件,即它声明了在执行代码之前应该满足的约束条件。它接受一个参数并在评估后返回一个布尔值,它也有一个自定义的字符串信息选项。如果是false,就会产生异常并终止执行。未使用的gas被返回给调用者,状态被逆转为原始状态。以下是require类型的异常被触发的一些情况。

  • 1、当require(条件表达式)被调用时,其条件表达式结果为false
  • 2、当一个被消息调用的函数没有正确结束时。
  • 3、当使用 new 关键字创建一个合约,而这个过程没有正常结束。
  • 4、当一个无代码的合约被定位到一个外部函数时。
  • 5、当使用公共getter方法将以太坊发送到合约时。
  • 6、当.transfer()方法失败时。
    • 6.1、当一个断言被调用时,其结果是假的。
    • 6.2、当一个函数的零初始化变量被调用时。
    • 6.3、当一个大值或负值被转换为一个枚举值时。
    • 6.4、当一个值被零除或模数化的时候。
    • 6.5、当在一个索引中访问一个数组,这个数组太大或者是负数。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract requireContract{

    function requireTest(uint256 _number) pure public {
        require(_number < 100, "number too big");
    }
 }

img

引入require语句的原因是为了使代码更具可读性。require 语句应在以下情况下使用。

  • 1、验证用户输入,正如我们上面所做的。
  • 2、在任何行动之前验证状态条件
  • 3、验证来自外部合同的响应
  • 4、检查溢出和欠溢出的情况

assert

assert语句也可以撤销所有的状态变化,但它会消耗事务中提供的所有气体,即使它没有被使用。这使得它不如revert或require宽松,因此使用频率较低。Assert是为 “真正糟糕的事情 “而存在的,应该只用于内部错误。

assert应该在以下情况下使用。

  • 1、检查常量,如this.balance > totalSupply
  • 2、执行后验证状态
  • 3、溢出和下溢检查,如果revert/require不合适的话

Solidity 在某些情况下自动创建assert型异常。

  • 1、被零除法或模数
  • 2、访问一个超出其界限的数组
  • 3、将过大或负数转换为枚数
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract assertContract{

    function assertDiv(int x,int y) pure public returns(int){
        assert(y == 0);
        return x / y;
    }
 }

img

revert

revert语句将停止执行并撤销所有的状态变化。剩余的gas将被退还给调用者(但到目前为止所使用的gas将消失)。revert允许返回一个值。这可以作为一个错误信息来澄清为什么执行revert语句。

有两种方式来触发revert

revert CustomError(arg1, arg2)
revert("description")

第一种是自定义的error类型(Solidity 0.8.4 出现的新类型)。

使用自定义错误实例通常会比传递一个字符串作为描述在消耗gas方面更便宜。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract revertContract{
    //自定义错误实例
    error WrongNumber(uint256 threshold, uint256 number);
    string public status;
    uint256 myThreshold = 100;

    function revertString(uint256 _number) public{
        status = "error";
        if(_number > myThreshold){
            revert("Number too big");
        }
        status = "success";
    }
    function revertCustomError(uint256 _number) public{
        status = "error";
        if(_number > myThreshold){
            revert WrongNumber({ threshold: myThreshold, number: _number});
        }
        status = "success";
    }

 }

img

img

三种方式处理错误消耗 gas 对比

  • 1、revert 语句,从示例可以看出最终耗费了 46607 gas

img

  • 2、revert 语句与自定义 error 相配合,反而比比上述消耗跟多的gas,它最终消耗了46733 gas

img

  • 3、 我们在示例中使用了assert语句,明显比revert语句消耗gas更少,它最终消耗了22077 gas

img

  • 4、 我们最后来看看equire语句。第一种情况是带了错误描述,而它却又比assert语句少耗费了gas,从示例可以看出它消耗了21898 gas

img

  • 5、而require第二种情况,便是只有条件表达式,可以看出在示例中它以上几种情况消耗gas最少的,它最终消耗了21605 gas

img

从上述的示例可以看出,使用require语句检查错误,最终消耗的gas是最少的,这也证明了很多项目使用require的原因了。

try catch 捕获异常

现在我们可以用它们来处理外部函数调用的失败,而不需要回滚整个事务(被调用函数中的状态变化仍然会被回滚,但调用函数中的变化不会)。

//CharitySplitter.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract CharitySplitter {
    address public owner;
    constructor (address _owner) {
        require(_owner != address(0), "no-owner-provided");
        owner = _owner;
    }
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
import "./CharitySplitter.sol";

contract CharitySplitterFactory {
    mapping (address => CharitySplitter) public charitySplitters;
    uint public errorCount;

    event ErrorHandled(string reason);
    event ErrorNotHandled(bytes reason);

    function createCharitySplitter(address charityOwner) public {
        try new CharitySplitter(charityOwner)
            returns (CharitySplitter newCharitySplitter)
        {
            charitySplitters[msg.sender] = newCharitySplitter;
        } catch {
            errorCount++;
        }
    }
}

img

try 关键词后面必须有一个表达式,代表外部函数调用或合约创建( new ContractName())。

在表达式上的错误不会被捕获(例如,如果它是一个复杂的表达式,还涉及内部函数调用),只有外部调用本身发生的revert 可以捕获。 接下来的 returns 部分(是可选的)声明了与外部调用返回的类型相匹配的返回变量。 在没有错误的情况下,这些变量被赋值,合约将继续执行第一个成功块内代码。 如果到达成功块的末尾,则在 catch 块之后继续执行。

Solidity 根据错误的类型,支持不同种类的捕获代码块:

  • catch Error(string memory reason) { ... }: 如果错误是由 revert("reasonString")require(false, "reasonString") (或导致这种异常的内部错误)引起的,则执行这个catch子句。
  • catch Panic(uint errorCode) { ... }: 如果错误是由 panic 引起的(如: assert 失败,除以0,无效的数组访问,算术溢出等),将执行这个catch子句。
  • catch (bytes memory lowLevelData) { ... }: 如果错误签名不符合任何其他子句,如果在解码错误信息时出现了错误,或者如果异常没有一起提供错误数据。在这种情况下,子句声明的变量提供了对低级错误数据的访问。
  • catch { ... }: 如果你对错误数据不感兴趣,你可以直接使用 catch { ... } (甚至是作为唯一的catch子句) 而不是前面几个catch子句。

有计划在未来支持其他类型的错误数据。 ErrorPanic 字符串目前是按原样解析的,不作为标识符处理。

为了捕捉所有的错误情况,你至少要有子句 catch { ... }catch (bytes memory lowLevelData) { ... }.

returnscatch 子句中声明的变量只在后面的块的范围内有效。