教程 | Solidity 中 revert(), assert() 和 require() 的使用方法

ETHButterfly2018-05-09 22:16:29  阅读 -评论 0  阅读原文

-Photo by Osman Rana-

Solidity 0.4.10 版本发布了新的 assert() , require() 和 revert() 函数,解决了以前代码中有困惑的地方。特别地,新 assert() 和 require() 代码会"确保"提高合约代码逻辑条理清晰,但是也需要知道如何区别使用它们。

本文中,将会:

  1. 解释新函数解决的问题
  2. 讨论 Solidity 编译器如何处理新 assert() 、 require() 和 revert() 调用
  3. 给出使用新代码的最佳实践

为了更好理解,我生成了使用这些新功能的简单合约,用户可以在 remix 上进行测试。

如果只是想看"太长不看版",那么 ethereum stackexchange 上的回答可以解答疑问。

Solidity 的错误处理模式

传统方法:采用 throw 和 if ... throw 模式

例如合约中有一些功能,只能被授权为 拥有者 的地址才能调用。

Solidity 0.4.10之前(以及其后一段时间),这种强制授权处理方式很普遍:

contract HasAnOwner { address owner; function useSuperPowers(){ if (msg.sender != owner) { throw; } // do something only the owner should be allowed to do } }

如果 useSuperPowers() 函数被其它非拥有者调用,此函数将抛出"返回无效操作代码错误",回滚所有状态改变,而且消耗掉剩下的gas(更多关于 gas 与费用的信息可以参考这篇 ethereum 中的文章)。

现在,"throw(抛出)"关键字已经过时了,最终将会被弃用。幸运的是,新函数 assert() 、 require() 和 revert() 提供了同样功能,而且上下文更加干净。

新文法

咱们看看用新代码函数如何处理传统 if ... throw 模式,

这行代码:

if(msg.sender != owner) { throw; }

完全等价于如下三种形式:

  • if(msg.sender != owner) { revert(); }
  • assert(msg.sender == owner);
  • require(msg.sender == owner);


注意assert() 和 require() 例子中的条件声明,是 if 例子中条件块取反,也就是用 ==代替了 != 。



assert()和require()之间的区别


首先,可以将 assert() 想象为一个过于自信的实现方式,即使有错误,也会执行并扣除gas。然而 require() 可以被想象为一个更有礼貌些的实现方式,会发现错误,并且原谅所犯错误(译注:不扣除 gas)。

基于以上理解,以上两个函数真正区别在哪里呢?在拜占庭网络更新前, require() 和 assert() 表现完全一样,但是他们的二进制代码却有略微区别。

  1. assert() 使用 0xfe 操作码引起错误条件
  2. require() 使用 0xfd 操作码引起错误条件

如果在黄皮书中查找这些操作码,会发现找不到。也就是为什么会看到 无效操作码 错误,因为并没有客户端如何处理这些错误的明确定义。

拜占庭网络升级并实现 EIP-140:以太坊虚机回滚指南之后,会解决这个问题。0xfd 操作码的改变将在 REVERT 指南中反映出来。

以下是这一激动人心功能的描述:

在0.4.10版本之后部署了许多合约,其中包括一个暂时不用的新操作代码。现在,它被激活了,就是 REVERT 。

注: throw 和 revert() 都是用 0xfd 操作码。而 0.4.10 之前,throw 就是使用的 0xfe。

REVERT 操作码实现功能


REVERT 碰到无效代码后,仍将回滚所有状态,但是会用两种不同于"无效代码"方式处理:

  1. 允许返回一个数值
  2. 将剩余gas返还调用者

1. 允许返回一个数值

许多智能合约开发者对以前那种无用的无效代码错误很熟悉。幸运的是,很快新代码可以返回一个错误信息,或者代表某种错误类型的数值。

看起来像这样:

revert('Something bad happened');

或者

require(condition, 'Something bad happened');

注:Solidity 暂时还不支持返回变量,但是可以参见这个问题更新

2. 将剩余 gas 返还调用者

目前的合约处理 throws 后会消耗剩余的 gas。尽管可以视为对矿工的慷慨捐助,但是往往会消耗用户大量金钱。

一旦 REVERT 在 EVM 中实现,将会抛弃旧方式转而将剩余 gas 返还用户。


在revert(), assert()和require()中作出选择

那么,如果 revert() 和 require() 都会返还剩余 gas,而且允许返回一个数值,那么为什么还使用 assert() 这种会消耗 gas 的调用呢?

不同点在于输出的二进制代码,引用如下文档以便更清楚解释(我做的着重强调)



require函数用于:
- 确认有效条件,例如输入,
- 确认合约声明变量是一致的
- 从调用到外部合约返回有效值


如果正确使用,分析工具会评估合约并分辨出引起 assert 调用错误的条件和函数。正确函数代码将会避免引起调用错误的 assert 声明;如果发生就意味着合约中存在需要修复的bug。


为了更清楚地解释:require() 声明失败应该被认为是正常和健壮的情况(跟 revert() 一样);而当 assert() 声明失败时,则意味着有些东西失控了,需要修复代码中的问题。

如果遵循以上实践指南,静态分析和正式验证工具可以用于检查合约,发现并证实合约中的隐患,或者确保合约安全无漏洞地运行。

特别地,我会使用如下准则来帮助判断正确使用场景。

以下场景使用 require() :


  • 验证用户输入,即: require(input<20);
  • 验证外部合约响应,即: require(external.send(amount));
  • 执行合约前,验证状态条件,即: require(block.number > SOME_BLOCK_NUMBER) 或者 require(balance[msg.sender]>=amount)
  • 一般地,尽量使用 require 函数
  • 一般地,require 应该在函数最开始的地方使用

在我们的智能合约最佳实践中有很多使用 require() 的例子供参考。

以下场景使用 revert() :


  • 处理与 require() 同样的类型,但是需要更复杂处理逻辑的场景

如果有复杂的 if/else 逻辑流,那么应该考虑使用 revert() 函数而不是require()。记住,复杂逻辑意味着更多的代码。

以下场景使用 assert():


  • 检查 overflow/underflow,即:c = a+b; assert(c > b)
  • 检查非变量(invariants),即:assert(this.balance >= totalSupply);
  • 验证改变后的状态
  • 预防不应该发生的条件
  • 一般地,尽量少使用 assert 调用
  • 一般地,assert 应该在函数结尾处使用

基本上,require() 应该被用于函数中检查条件,assert() 用于预防不应该发生的情况,但不应该使条件错误。

另外,"除非认为之前的检查(用 if 或 require )会导致无法验证 overflow,否则不应该盲目使用 assert 来检查 overflow"——来自于@chriseth

结论

这些函数是安全性检查工具库中很强大的工具。知道如何以及何时使用这些函数不仅能帮助你的代码免受攻击,而且会使代码更加对用户友好,更加面向未来变化。

喜欢这种类型文章吗?

我来自ConsenSys Diligence团队。如果你有 Solidity 和 EVM 方面深入技能,而且对提高智能合约安全性很感兴趣,我们希望你能加入智能合约审查部门(从这里申请)。

如果你看到了这里但还达不到工作描述中的要求,也没关系。只需要在消息中引用此文,并说出你的技能和对以太坊的兴趣即可。

免责声明:以上作者观点并不代表 Consensys AG 的观点。ConsenSys 是一个去中心化社区,采用 ConsenSys Media 作为社区成员自由交流的平台。如果关注 CensenSys 和以太坊,请访问我们的网站。<https://consensys.net/>

作者注:文中的 Remix 代码可以在这个地址中找到。<https://remix.ethereum.org/#gist=c7b647b64d9d2422b...>
或者,你可以在这个 GitHub 地址中找到文中的代码。<https://gist.github.com/maurelian/c7b647b64d9d2422...>


原文链接: https://media.consensys.net/when-to-use-revert-ass...
作者: Maurelian
翻译&校对: 小野于林 & Elisa

小野于林

高性能运算和分布式存储从业者。探索其在区块链中的定位和价值。


本文由作者授权 EthFans 翻译及再出版。


想要一个简明扼要的轻量级区块链 Wiki 吗?想在阅读和翻译区块链相关文章时秒懂术语的意思吗? 这么巧,EthFans 也想!

现在,EthFans 有志于用 GitHub 做一个区块链术语表,采用"术语-翻译-简介"的基本形式,为区块链翻译事业提供 基本翻译参考,进而演化成大家有力的学习工具!

这样一张凝练的术语表,当然需要社区的支持,我们希望大家一起来参与!

成为术语表的贡献者,您可以:

与 EthFans 取得联系后加入贡献者群,与其他贡献者一同学习进步,始终保留相关贡献的智力产权,为有争议的术语定名做贡献。

这是我们编写术语表的基本规则,有意为术语表贡献力量的请先阅读其中的要求:https://github.com/editor-Ajian/List-of-translation-of-crypto-terms-by-EthFans。

这里则是 EthFans 已经编写好的部分词条,大家可以看看基本的形式:https://github.com/editor-Ajian/List-of-translation-of-crypto-terms-by-EthFans/blob/master/total-table.md。

EthFans 期待您的参与!

声明:链世界登载此文仅出于分享区块链知识,并不意味着赞同其观点或证实其描述。文章内容仅供参考,不构成投资建议。投资者据此操作,风险自担。此文如侵犯到您的合法权益,请联系我们kefu@lianshijie.com

参与讨论 (0 人参与讨论)

相关推荐

以太坊ETH发行ERC20代币教程(实战)

以太坊ETH发行ERC20代币教程(实战)

本教程主要参考以太坊官方博客,外加自己的实践。创建代币不需要自己写代码,只要会复制粘贴就可以搞定,这也是以太坊强大之处。下载钱包首先到这里(https://github.com/ethereum/mist/releases)根据自己的操作系统下载相应的钱包。然后,创建一个以太坊账户。(具体的创建过程请见这个中文帖子:http://ethfans.org/topics/78 )。另外,你还需要一点以

开源区块链钱包CASHBOX技术编译教程(一)

1. 全局代理,必要条件 2. 环境准备(window) Android studio 链接:http://www.android-studio.org/ Flutter 链接:https://flutterchina.club/get-started/install/ Rust 链接:https://www.rust-lang.org/zh-CN/tools/install Java 链接:ht

Conflux 开发教程 | 使用 IDE 开发 DApp 的实战操作指南

一、简介 Conflux Studio 是一个帮助开发者快速开发 Conflux 智能合约的集成化开发环境。Conflux DApp 开发教程将使用 Conflux Studio 在 Oceanus 网络下开发一个简单的代币应用 Coin。 通过这个开发教程,你将会学习到如何进行 Conflux 智能合约的编写、调用,配置智能合约的代付以及如何使用 Web 前端项目与智能合约进行

一文读懂Uniswap,附Uniswap使用教程

一文读懂Uniswap,附Uniswap使用教程

一、加密货币交易形式 当我们要进行加密货币交易时,使用最早也是目前使用最多的形式还是中心化交易所,在中心化交易所,我们首先需要注册,然后加密货币也需要存入到交易所,由交易所进行托管,如果要提现加密货币出来,也需要经过交易所审核同意。 虽然中心化交易所有诸多优势,例如交易速度较快、用户不需要管理私钥,降低了用户的使用门槛,但是它的弊端也是显而易见的,用户的加密货币由交易所托管,交易所是有跑路风险的

Acala Mandala 测试网 TC4 版本操作教程

1. Acala 网络三部曲 Acala 网络是基于 Substate 开发的跨链开放式的金融平台,未来将成为 Polkadot 生态中最具代表性的 DeFi 基础设施, Acala 网络将按照以下三部曲发展::  · Mandala 测试网:无风险和价值模拟的多资产 DeFi 游乐场,作为测试网可以初步让更多用户体验 Acala ,并根据测试网络进行后期产品优化与迭代升级 · Karura 先行

优盾钱包:Bitcoin钱包开发之地址生成教程

优盾钱包提供BTC_ETH_USDT_EOS_XRP等主流erc20代币对接交易所钱包充提币_转账支付归集_API/RPC的php/java开发接口。API快捷接入,多币种多地址钱包余额一键归集、私钥冷存储、多级复签、全终端支持。 以Bitcoin为首的加密货币从诞生发展至今,融入了多项先进技术,区块链技术的应用不仅保障了加密货币的流通便利性,更发挥其加密性、不可篡改两大优势,赋

最新波卡提名人教程(Soft Launch 阶段)

这篇文章针对刚刚软启动(Soft Launch)阶段的 Polkadot 网络。在软启动期间,波卡网络在转换为 PoS 之前先作为 PoA 网络。你可以按照本文设置提名人,但验证人选举和奖励还没有开始。如果你想在 Kusama 上提名,也可以查看 Kusama 的提名指南。(本文更新于 2020 年 6 月 1 日) 提名人是 Polkadot staking 系统中的一种参与者。他们可以把 DO

零知识证明 circom 及 snarkjs 入门教程[译]

在本教程里将指导您创建第一个零知识 zkSnark电路。它将介绍各种编写电路的技术,并向您展示如何创建证明并在以太坊[1]上进行链外和链上验证。 1. 安装工具 1.1 先决条件 需要在电脑中安装Node.js,Node.js 的最新的稳定版本(或8.12.0)可以正常工作。不过,如果您安装了当前的最新版本的Node.js(10.12.0),将会看到显着的性能提升。这是因为最新版本本身包含大数库(

麦妖榜
更新日期 2019-09-03
排名用户贡献值
1牛市来了30910
2BitettFan24187
3等待的宿命23810
4区块大康20369
5六叶树20310
6linjm122719429
7天下无双16192
8lizhen00215280
9让时间淡忘14586
10yelanyi050511349
返回顶部 ↑