Solidity语言学习笔记————26、Assert, Require, Revert 和 Exceptions

本文介绍了Solidity中处理错误的方法,包括assert、require、revert等函数的使用场景及区别。assert用于检测内部错误,require用于确认外部条件,revert用于标记错误。文章还讨论了异常处理机制以及不同类型的异常如何影响智能合约。

Assert, Require, Revert 和 Exceptions

Solidity使用state-reverting异常来处理错误。 这种异常将回滚当前调用(及其所有子调用)状态的所有变化,并将错误标志给调用者。 
函数assertrequire可以用于检查条件,如果条件不满足则抛出异常。 
assert函数只能用于测试内部错误,并检查不变量。 
应该使用require函数来确认input或合约状态变量满足条件,或者验证调用外部合约的返回值。 
如果正确使用,分析工具可以评估合约和函数调用是否assert失败。 正常运行的代码不应该assert失败;如果发生这种情况,则需要修复合约中的bug。

还有另外两种方法可以触发异常:

  • revert函数可用于标记错误并回滚当前的调用。
  • throw关键字也可以用作revert()的替代方法。
注解
从0.4.13版本,throw关键字已被弃用,将来会被淘汰。

当异常发生在子调用中时,它们会自动“冒泡”(即异常被重新抛出)。 
但也有例外:send和底层函数calldelegatecall 和 callcode——在异常情况下返回false,而不是“冒泡”。

警告
如果调用的帐户不存在,calldelegatecall 和 callcode的返回值会是true,EVM就是这么设计的。如果需要,必须在调用之前检查账户是否存在。

捕捉异常目前还做不到。

在下面的示例中,您可以看到如何使用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;
    }
}

在以下情况下会生成assert风格异常:

  • 以过大或负数索引访问数组(比如x[i] ,i >= x.length 或 i < 0)
  • 以过大或负数索引访问固定长度的bytesN
  • 除数或模数为零(例如5/023%0)。
  • 对负数进行位移
  • 将过大或负数转化为枚举类型。
  • 调用内部函数类型的0初始化(zero-initialized)变量。
  • assert的条件为false

在以下情况下会生成require风格异常:

  • 调用throw
  • 调用require并且条件为false
  • 通过消息调用来调用函数,但是它没有正确完成(即,用尽了gas,没有匹配的函数,或者抛出了异常),除非使用底层别的操作callsenddelegatecallcallcode。 底层别的操作不会抛出异常,而是通过返回false来表示失败。
  • 使用new关键字创建合约但是失败。
  • 执行一个外部函数调用,其指向不包含代码的合约。
  • 合约通过public函数接收Ether,但没有payable修饰符。包括构造函数和回退函数。
  • 合约通过一个publicgetter函数接收Ether。
  • 如果.transfer()失败。

在内部,Solidity对require-style异常执行一个revert操作(0xfd指令),并执行一个无效操作(指令0xfe)来抛出一个assert-style异常。 在这两种情况下,这将导致EVM revert对状态所做的所有更改。 revert的原因是没有安全的方式来继续执行,因为没有发生预期的效果。 因为我们要保留交易的原子性,所以最安全的做法是恢复所有的变化,并使整个事务(或至少调用)无效。 请注意,assert风格的异常消耗调用中可用的所有gas,而require风格的异常将不会消耗从Metropolis版本开始的任何gas。


Solidity是用于编写智能合约的编程语言,在区块链开发中应用广泛,而Web.js(如web3.js)则是用于与区块链进行交互的JavaScript库,二者在区块链前端开发中紧密相关。 在岗位方面,Solidity开发主要负责智能合约的开发,从业者除了要了解相关的Solidity语法知识,还需要接触各种dapp,该岗位在上海、深圳、北京较多,其他省份基本没有;而web3.js开发与传统的web2前端开发工程师类似,学习基础的前端知识,多了解一个web3的库即可,主要开发前端js代码,通过js进行合约交互绘制合约界面,EVM开发岗位较多,需要了解EVM底层原理 [^1]。 从技术应用层面看,对于前端开发者而言,Web2应用的开发主要依赖于传统的Web技术,如HTML、CSS、JavaScript等;而Web3应用的开发,如涉及Solidity应用前端,需要掌握更多的区块链技术、去中心化技术以及智能合约编程等方面的知识,同时也需要更加注重用户隐私安全等方面的考虑 [^3]。 在使用web3.js与Solidity合约交互时,web3可通过contract调用部署的合约,这里的合约通常是用Solidity语言编写的。web3本身也有一些api可以直接区块链交互。不过,不同区块链上即便为同一种代币,也是不互相关联的,可看成是两个数据库的数据。并且不同版本的web3方法可能不一样,文档有些又不全,需要类比阅读源码,多次测试 [^2]。 以一个web3博客系统为例,其涉及的技术栈包括Next.js、Polygon、Solidity、The Graph、IPFS、Hardhat等,其中Hardhat是Ethereum开发环境,web3modal可方便快速地连接钱包等 [^4]。 以下是一个简单的使用web3.js调用Solidity合约的示例代码: ```javascript const Web3 = require('web3'); // 假设已经部署的合约地址ABI const contractAddress = 'YOUR_CONTRACT_ADDRESS'; const contractABI = [/* 合约ABI */]; // 初始化web3实例 const web3 = new Web3('YOUR_PROVIDER_URL'); // 获取合约实例 const contract = new web3.eth.Contract(contractABI, contractAddress); // 调用合约的某个方法 contract.methods.someFunction().call() .then(result => { console.log('合约方法调用结果:', result); }) .catch(error => { console.error('调用合约方法时出错:', error); }); ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值