solidity中的Error和Modifier详解

异常

写智能合约经常会出bug,solidity中的异常命令帮助我们debug。

Error

error是solidity 0.8.4版本新加的内容,方便且高效(省gas)地向用户解释操作失败的原因,同时还可以在抛出异常的同时携带参数,帮助开发者更好地调试。人们可以在contract之外定义异常。

在Solidity中,异常处理是确保智能合约安全性和正确性的关键步骤。Solidity提供了几种主要方法来处理异常,包括error、require和assert。以下是这些方法的详细讲解:

  1. require:用于检查条件是否为真,如果条件为假,则会抛出异常并回滚交易。
  2. assert:用于检查不应该为假的条件,用于捕捉代码中的严重错误。
  3. revert:用于在特定条件下回滚交易,可以提供错误消息。
  4. 自定义错误:从 Solidity 0.8.4 开始,引入了自定义错误类型,用于节省 Gas 并提供更加具体的错误信息。 

1. require 语句

require 语句用于在函数执行之前声明前提条件,即在执行代码之前必须满足的约束。它接受一个参数,并在评估后返回布尔值,还有一个可选的自定义字符串消息。如果为false,则会引发异常并终止执行。未使用的gas会返回给调用者,状态也会回滚到原始状态。require 常用于以下场景:

•  验证输入参数或外部合约调用结果。
•  检查调用方是否具有足够的权限。
•  验证输入数据的合法性。 

pragma solidity ^0.5.0;
contract requireStatement {function checkInput(uint _input) public view returns(string memory) {require(_input >= 0, "invalid uint8");require(_input <= 255, "invalid uint8");return "Input is Uint8";}
}

 2. assert 语句

assert 语句用于检查代码逻辑中的不变量,即程序在任何时候都应该满足的条件。如果assert失败,意味着代码中存在致命的错误。assert 通常用于捕捉代码中的严重错误,特别是不应该发生的逻辑错误。当它失败的时候会回滚交易,但是不会消耗太多的Gas费用,因为它用于内部错误

pragma solidity ^0.5.0;
contract assertExample {uint x = 0;function increment() public {x += 1;assert(x > 0); // 确保 x 永远大于 0}
}

3. revert语句

revert用于在特定条件下回滚交易,可以提供错误消息。它与require类似,但revert不消耗Gas来存储错误信息。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;contract Example{function checkCoundition(uint a) public pure{require(a>10,"Error: a must be greater than 10");//如果a不大于10,交易将会被回滚,并且会显示错误信息if(a==20){revert("Error:a cannot be 20");}//如果a为20,交易将会被回滚,并且会显示错误信息}
}

4. 自定义错误

从Solidity 0.8.4开始,引入了自定义错误类型,用于节省Gas并提供更加具体的错误信息。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;contract Example {error InvalidNumber(uint value);function checkNumber(uint a) public pure {if (a <= 10) {revert InvalidNumber(a);}// 如果a不大于10,将使用自定义错误类型回滚交易}
}

在这个例子中,我们定义了一个名为InvalidNumber的自定义错误类型,它接受一个uint参数。在checkNumber函数中,如果a不大于10,我们使用revert关键字和自定义错误类型来回滚交易,并提供具体的错误信息。
自定义错误类型的好处是,它们允许合约的用户更容易地识别和处理特定的错误情况,同时减少了合约的Gas消耗。 

这里不回将错误信息存储在交易日志当中,因此更节省Gas费用。

构造函数

  • 构造函数是使用 constructor 关键字声明的一个可选函数;
  • 构造函数只在合约部署时调用一次,并用于初始化合约的状态变量;
  • 如果没有显式定义的构造函数,则由编译器创建默认构造函数。

声明语法

构造函数声明语法如下:

constructor(<paramslist>) <Access Modifier> {// todo
} 

例如,下面的合约声明了一个构造函数,用于对状态变量进行初始化。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;// 构造函数
contract Simple {string str;// 声明构造函数,并初始化状态变量constructor() {                 str = "hello simple";}// 定义一个函数返回状态变量的值function getValue() public view returns(string memory) {return str;}
}

继承的构造函数

如果父合约没有定义构造函数,则调用默认构造函数,如果在父合约中定义了构造函数,并且有一些参数,则子合约需要提供所有参数。有两种方法来调用父合约的构造函数:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;contract Base{uint data;//构造函数constructor(uint _data){data=_data;}
}//继承合约(直接初始化)
contract Derived is Base(2){//构造函数constructor(){}//定义一个函数访问父合约的状态变量function getData() external view returns(uint){uint result =data**2;return result;}
}
//调用合约
contract Caller{//创建子合约对象Derived c =new Derived();//通过子合约对象访问父合约和子合约的函数function getResult() public view returns(uint){return c.getData();}
}

间接初始化

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;contract Base{string str;//构造函数constructor(string memory _str){str =_str;}
}//继承合约(间接初始化)contract Derived is Base{//构造函数constructor(string memory _info) Base(_info){}//定义一个函数访问父合约的状态变量function getStr() external view returns(string memory){return str;}}//调用合约contract Caller{Derived c =new Derived("Hello Constructor");//通过子合约对象访问父合约和子合约的函数function getResult() public view returns(string memory){return c.getStr();}}

 Modifier

modifier可以改变函数的行为。可以被继承和重写。

其实modifier被用于最多的是行为检查这样可以使得减少检查代码的复用以及让代码看起来更简介易懂。比如,检查调用者是否有权限执行这个函数,传入的参数是否有错误等等。

// 定义了一个名为NoteBook的智能合约
contract NoteBook {// 声明了一个公共的字符串变量record,用于存储NoteBook的内容// 由于是公共的,所以它可以在合约外部被读取string public record; // 声明了一个address类型的变量owner,用于存储NoteBook的拥有者的地址address owner; // 构造函数,它在合约部署时执行一次constructor() {// 在合约部署时,将msg.sender(部署者地址)赋值给owner变量owner = msg.sender;}// changeRecord函数用于修改NoteBook的内容// 参数_record是一个新的字符串,用于更新record变量function changeRecord(string memory _record) public isOwner {// 更新record变量为新的值record = _record;}// 定义了一个名为isOwner的modifier(函数修改器)// 这个修改器用于检查调用者是否是NoteBook的拥有者modifier isOwner {// require函数用于断言一个条件,如果条件为false,则触发异常// 这里检查msg.sender(当前调用者的地址)是否等于owner// 如果不是,则返回错误信息"You are not the owner of this NoteBook"require(msg.sender == owner, "You are not the owner of this NoteBook");// 如果检查通过,则执行后面的_;// _是modifier中的一个特殊符号,表示原函数的执行_;}
}

这里的 _ 表示在 require 语句执行并且条件满足后,控制流将跳转到被 isOwner 修改的函数的主体部分。换句话说,_ 是一个占位符,它告诉编译器在成功通过修改器的条件检查后,继续执行函数的剩余部分。

例如,如果在 changeRecord 函数中使用 isOwner 修改器:

function changeRecord(string memory _record) public isOwner {record = _record;
}

当 changeRecord 函数被调用时,首先会执行 isOwner 修改器中的代码。如果 msg.sender 不等于 owner,require 语句会触发一个异常,函数执行停止,并且状态回滚。如果 msg.sender 等于 owner,则执行 _ 之后的代码,即 record = _record;,这将更新 record 变量的值。
总结来说,_ 在函数修改器中是一个指示编译器继续执行函数主体的指令。

modifier对函数参数的操作

执行函数时有时候也会对函数的参数有所要求,为了让函数内的代码更简洁我们便可以写在modifier中。那如何对函数参数进行检查呢?这个和函数的操作一样,调用时传参便可。看如下例子:

// 这个合约可以执行运算
contract Operation{// 除法运算function division(uint256 opt1, uint256 opt2) public checkZero(opt2) pure returns(uint256){return opt1 / opt2;}// 检查除数是否为0modifier checkZero(uint256 divisor) {require (divisor != 0, "divisor can't be 0");_;}
}

在以上代码中我们需要做的是检查除法运算中的除数是否为0,若是0则中止运行,并给予提示。代码简单就不啰嗦了。

当然modifier还可以对storage中的变量进行检查

modifier的执行顺序

一个函数可能需要做多个检查,那么我们可以写多个modifier,调用时只需将每个modifier以空格隔开。而检查顺序也就是modifier们的排列顺序

但还有一种可能会迷惑大家的写法:

contract modifierOder {address owner;uint256 a;constructor() {owner = msg.sender;}function test(uint num) public checkPara(num) returns(uint256) {a = 10;return a;}// 修改a modifier checkPara(uint number) {a = 1;_;a = 100;}}

如以上代码所示:在 _后又有一句代码a = 100 。函数执行完return后,后面的代码则不再执行,但是在modifier中,执行完函数体 _ 还会接着执行 a = 100 这条语句。所以尽管函数返回的a 的值为10,但是最后a的值变成了100。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/58916.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode hot100【LeetCode 139. 单词拆分】java实现

LeetCode 139. 单词拆分 题目描述 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict&#xff0c;判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明&#xff1a; 拆分时可以重复使用字典中的单词。你可以假设字典中没有重复的单词。 示例 1&am…

etcd-python

etcd 分布式键值(key-value)数据库 基于go语言实现 分布式系统中提供强一致性、高可用性的组件 etcd内部采用raft协议作为一致性算法 用来存储少量重要的数据 pip install etcd3 import etcd # 连接etcd&#xff08;可以加用户名密码&#xff09; etcd etcd3.client(host192…

前端学习-盒子模型(十八)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 盒子模型组成 边框 语法 边框简写 代码示例 表格的细线边框 语法 内边距 内边距复合写法 外边距 外边距典型应用 外边距合并 清除内外边距 总结 前…

LeetCode 第422场个人周赛

目录 Q1. 检查平衡字符串 原题链接 思路分析 AC代码 Q2. 到达最后一个房间的最少时间 I 原题链接 思路分析 AC代码 Q3. 到达最后一个房间的最少时间 II 原题链接 思路分析 AC代码 Q4. 统计平衡排列的数目 原题链接 思路分析 AC代码 Q1. 检查平衡字符串 原题链接…

力扣题解(大礼包)

638. 大礼包 已解答 中等 相关标签 相关企业 在 LeetCode 商店中&#xff0c; 有 n 件在售的物品。每件物品都有对应的价格。然而&#xff0c;也有一些大礼包&#xff0c;每个大礼包以优惠的价格捆绑销售一组物品。 给你一个整数数组 price 表示物品价格&#xff0c;其中…

GHuNeRF: Generalizable Human NeRF from a Monocular Video

研究背景 研究问题&#xff1a;这篇文章要解决的问题是学习一个从单目视频中泛化的人类NeRF模型。尽管现有的泛化人类NeRF已经取得了令人印象深刻的成果&#xff0c;但它们需要多视图图像或视频&#xff0c;这在某些情况下可能不可用。此外&#xff0c;一些基于单目视频的人类…

为啥学习数据结构和算法

基础知识就像是一座大楼的地基&#xff0c;它决定了我们的技术高度。而要想快速做出点事情&#xff0c;前提条件一定是基础能力过硬&#xff0c;“内功”要到位。 想要通关大厂面试&#xff0c;千万别让数据结构和算法拖了后腿 我们学任何知识都是为了“用”的&#xff0c;是为…

离线安装Vue2开发环境

在外网进行Vue2开发后&#xff0c;需要转到内网开发&#xff0c;无法在线依赖库安装&#xff0c;需要迁移node_modules。 1.内外网开发电脑安装同样版本的nodejs 我本地安装的node-v16.17.1-x64.msi&#xff0c;所以在内网环境也要按照node-v16.17.1-x64.msi。 在外网环境使用…

hadoop面试题

一、单项选择题 1、目前&#xff0c;Hadoop的最高版本是哪个&#xff08; A &#xff09; A、Hadoop3.x B、Hadoop2.x C、Hadoop4.x D、Hadoop1.x 2、大数据的4V特征是指? &#xff08; B &#xff09; A、数据量大(Volume)、类型繁多(Variety)、价值密度低(Va…

初探Flink的序列化

Flink中的序列化应用场景 程序通常使用(至少)两种不同的数据表示形式[2]&#xff1a; 1. 在内存中&#xff0c;数据保存在对象、结构体、列表、数组、哈希表和树等结构中。 2. 将数据写入文件或通过网络发送时&#xff0c;必须将其序列化为字节序列。 从内存中的表示到字节序列…

运维人员常用的Linux命令汇总

运维人员常用的Linux命令汇总 一.文件和目录 cd命令&#xff0c;用于切换当前目录&#xff0c;它的参数是要切换到的目录的路径&#xff0c;可以是绝对路径&#xff0c;也可以是相对路径。 cd /home 进入 / home 目录 cd .. 返回上一级目录 cd ../.. …

【ESP32】ESP-IDF开发 | I2C控制器+I2C主从收发例程

1. 简介 I2C&#xff08;Inter-Integrated Circuit&#xff09;&#xff0c;是由Philips公司在1980年代初开发的一种半双工的同步串行总线&#xff0c;它利用一根时钟线和一根数据线在连接总线的两个器件之间进行信息的传递&#xff0c;为设备之间数据交换提供了一种简单高效的…

软考:案例题分析1101

22年第一题&#xff1a;架构设计与评估 分析文字&#xff0c;识别需求和质量属性&#xff1f;这里需要记忆质量属性有那些&#xff0c;区分需求和质量属性&#xff0c;能区分出质量属性之间的区别。 我的回答&#xff1a; 差距分析&#xff1a; 根据题目中功能的特点&#xff…

提高交换网络可靠性之端口安全配置

转载请注明出处 此实验为配置交换机端口安全&#xff0c;当非法设备接入接口时自动触发安全措施 1.查看PC1和PC2的MAC地址&#x1f447; 2.交换机改名为S1&#xff0c;同时启用端口安全 3.配置允许接入设备 4.设置违规处理方式&#xff1a;即违规则关闭端口 5.查看安全端口相关…

nodejs入门教程19:nodejs dns模块

引入方式 在Node.js中&#xff0c;使用dns模块前需要先通过require函数引入它&#xff1a; const dns require(dns);核心方法 1. dns.lookup(hostname[, options], callback) 功能&#xff1a;将域名解析为IP地址。参数&#xff1a; hostname&#xff1a;要查询的主机名。…

UE5 第三人称学习之动画 control rig

这个东西和建模软件里有的是一个东西&#xff0c;然后IK就是你动脚&#xff0c;他帮你算出小腿大腿该怎么动&#xff0c;FK就是你自己动了大腿&#xff0c;摆小腿&#xff0c;然后再摆脚 就是给每一根骨骼搞一个控制器&#xff0c;给他一个容易选中和操作更明显的图形作为控制…

宏处理将多个excel文件的指定sheet页合并到一个excel文件中

背景了解&#xff1a;有个同事问我&#xff1a;现在他要处理一千多个文件&#xff0c;每个excel文件都有3个sheet页签&#xff0c;想把所有的excel文件的第二个sheet页签复制一份放到一个新的excel文件中。如果是手动去操作一个个文件的复制&#xff0c;也没什么不可&#xff0…

Linux系列-进程的概念

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 这篇文章&#xff0c;我们主要分析一下进程。 之前&#xff0c;我们讲过了冯诺依曼体系架构&#xff0c; 我们常见的计算机&#xff0c;像笔记本&#xff0c;或者不常见的计算机…

SQL优化经验大全(表设计优化,索引优化,索引创建规则、索引失效场景,sql语句优化,主从复制,分库分表)面试题

目录 1.表的设计优化 2.索引优化 2.1 索引创建的规则 2.2 索引失效的场景 3.SQL语句优化 4.主从复制、读写分离 5.分库分表 5.1.怎么判断项目是需要分库还是要分表&#xff1f; 5.2 分库分表有哪些拆分方案&#xff1f; 5.2.1 垂直分库 5.2.2 垂直分表 5.2.3 水平分…

【VMware】使用笔记

一、安装 win11支持16.2以上版本&#xff0c;其他版本不兼容 安装参考&#xff1a; 二、设置 1、蓝屏设置 参考&#xff1a;win11打开VMware虚拟机蓝屏解决_win11vmware蓝屏-CSDN博客 2、VMwareTool配置 第一步&#xff1a;移除“open-vm-tools” sudo apt-get autoremo…