智能合约语言 Solidity 教程 – 错误处理

智能合约语言 Solidity 教程 – 错误处理

电报联系方式

什么是错误处理

错误处理涉及到程序在出现错误时的应对方式,Solidity在这方面与常见编程语言有所不同。Solidity采用回退状态的方式来处理异常情况。如果发生异常,将会撤销当前调用以及所有子调用所做的状态更改,并向调用者返回一个错误标识。值得注意的是,Solidity不支持像其他编程语言中的”try…catch…”这种异常捕捉机制。因此,捕捉异常是不可能的,也就意味着没有类似try…catch…的语法结构可供使用。

区块链可以被视为一个全球共享的分布式事务性数据库。”全球共享” 意味着每个网络参与者都有权读取和写入数据库的记录。但如果要对数据库中的内容进行修改,就必须进行一种特殊的操作,即事务。事务确保对数据库的修改(例如同时修改两个值)要么完全应用,要么一点都不应用。Solidity的错误处理机制旨在确保每个函数调用都是事务性的,这有助于维护数据的一致性和可靠性。

如何处理

Solidity为条件检查提供了两个函数,即assert和require。当条件不满足时,这两个函数均可用于抛出异常。通常,assert用于测试内部错误,而require用于验证输入变量或合同状态变量是否满足条件,以及验证调用外部合约返回值。

正确使用assert有助于利用Solidity分析工具,该工具能够检测智能合约中的错误并帮助发现逻辑错误的bug。这进一步提高了智能合约的安全性和可靠性。

除了使用assert和require这两个函数来进行条件检查,还有两种方式可以引发异常:

  1. 使用revert函数:revert函数可以被用来标记错误并回滚当前调用的状态。
  2. 使用throw关键字:在Solidity 0.4.13版本之后,throw关键字已被弃用,并将来会被移除。

需要注意的是,当在子调用中发生异常时,异常会自动向上传播,影响父级调用。然而,也存在一些特例,如send以及底层的函数调用如call、delegatecall和callcode,当它们引发异常时,将返回false,而不会像其他调用一样向上传播异常。

注意:在一个不存在的地址上调用底层的函数call,delegatecall,callcode 也会返回成功,所以我们在进行调用时,应该总是优先进行函数存在性检查。

在下面通过一个示例来说明如何使用require来检查输入条件,以及assert用于内部错误检查:

pragma solidity ^0.4.0;

contract Sharer {
function sendHalf(address addr) public payable returns (uint balance) {
require(msg.value % 2 == 0); // 仅允许偶数
uint balanceBeforeTransfer = this.balance;
addr.transfer(msg.value / 2); // 如果失败,会抛出异常,下面的代码就不是执行
assert(this.balance == balanceBeforeTransfer – msg.value / 2);
return this.balance;
}
}

一、首先打开Remix,贴入代码,点击创建合约。如下图:

智能合约语言 Solidity 教程 - 错误处理

二、运行测试1:附加1wei (奇数)去调用sendHalf,这时会发生异常,如下图:

智能合约语言 Solidity 教程 - 错误处理三、运行测试2:附加2wei 去调用sendHalf,运行正常。

四、运行测试3:附加2wei以及sendHalf参数为当前合约本身,在转账是发生异常,因为合约无法接收转账,错误提示上图类似。

assert类型异常

assert类型的异常在以下情况下会自动触发:

  1. 数组访问越界或使用负的索引值,例如,当尝试访问 x[i] 时,如果 i >= x.lengthi < 0
  2. 对定长的bytesN进行访问越界,或者使用负的索引值。
  3. 当被除数为0时,如 5/023 % 0
  4. 当对一个二进制数进行左移或右移,并使用负的移动值,例如 5 << i,其中 i 为负数。
  5. 当尝试将整数显式转换为枚举类型,并转换的值过大或为负数时,将抛出异常。
  6. 如果尝试调用未初始化的内部函数类型的变量。
  7. 当assert语句的参数为false时,会触发assert异常。

require类型异常

  1. 使用 throw 关键字明确抛出异常。
  2. 在调用 require 时,如果参数表达式为 false,将引发异常。
  3. 当通过消息调用函数,但在调用的过程中未能正确完成,例如,gas不足,没有匹配到对应的函数,或被调用的函数出现异常。值得注意的是,底层操作如call、send、delegatecall或callcode不会抛出异常,而是通过返回false来表示失败。
  4. 在使用 new 创建一个新合约时,如果由于第三点所述的原因未能正常完成,将抛出异常。
  5. 当调用外部函数时,被调用的对象不包含合约代码时,会触发异常。
  6. 如果合约没有 payable 修饰符的 public 函数用于接收以太币,包括构造函数和回退函数,当尝试向这些函数发送以太币时,异常将发生。
  7. 如果尝试通过一个 public 的获取函数(getter function)来接收以太币,异常将被触发。
  8. 当使用 transfer() 执行失败时,将引发异常。

当发生require类型的异常时,Solidity将执行一个回退操作(指令0xfd)来撤销所有状态更改。类似地,当发生assert类型的异常时,Solidity将执行一个无效操作(指令0xfe)。这是因为在这两种情况下,期望的结果没有发生,因此不能继续执行交易以确保安全性。在区块链中,交易必须具备原子性,即要么所有操作都被执行,要么一点改变都不会发生。因此,任何异常情况都需要回滚到初始状态,确保整个交易没有任何影响。这有助于维护数据的一致性和可靠性。

开发联系:DEXDAO

 

© 版权声明

相关文章

暂无评论

暂无评论...