00:00
好,接下来我们看一下回退函数,回退函数其实我们已经说过好多次了,这里就是再把概念和一些理论性的东西再给大家强调一遍吧。回退函数名字叫fallback,这个有些地方可能它可能不叫回退函数,有可能会叫。呃,这个我也不太确定啊,但是我觉得如果他要叫回调函数肯定是不对的,对吧?对,有可能叫回溯函数,对,对,回溯函数应该是见过的,所以大家如果看到回退函数或者回溯函数其实差不多的东西,呃,它呢是合约里面非常特殊的一个函数,可以说它比构造函数constructor还要特殊,构造函数我们还好理解一些,对吧。因为在别的编程语言里面,很多面对对象都有constructor,就是他在一开始创建对象实例的时候就会调用这个方法。但是回退函数是个什么东西呢?首先它的定义就是没有名字的,没有参数,也不能有返回值,简单说就是直接进来之后就是方式,一个括号,然后啥都没有,最多后面有一个函数类型。
01:09
什么都没有别的。那它主要是干什么用呢?它主要是在什么时候会被调用,会执行呢?大家注意看啊,下面这个非常重要,如果在一个合约的调用中没有其他函数。与给定的函数标志符匹配。这是什么意思?也就是说我们去调用函数的时候,其实是像。以太坊系统里面发了一笔交易,对吧,那个交易的数据里边,前面定义了一个函数标志符,大家还记得吧?对,那个数据里面前面是函数,函数标志符,后面是参数,它们拼在一起拼成一个32。为呃,32位的字符字符串,所以大家注意,如果我们给定的函数标志符是在这个合约里面的话,它找到了对应的函数,那肯定就会直接去掉了,但要没有呢?
02:07
没有的时候,它就会执行到回退函数里面来。所以大家注意啊,就是它相当于是一个为什么叫回退函数呢?就好像就是说一个默认的选项,或者说一个默认的异常处理,或者说在没有别的事情处理的时候要调用的一个东西。有点这个意思。所以大家注意啊,就是for back在什么情况下会不会被执行,它的函数标志符找不到的时候,没有匹配的时候就会执行。也就是说我们前面在这个这里面定义了一个F,如果我们现在是直接去点这个去调用的,如果我们用发送交易,我们在命令行里边。在guest的console里面去,去构建自己,构建一个交易发给他的话。
03:00
我们把标志符不要定义成F,我们定义成别的,定义成一个G,它里边没有,那发给它会出现什么状况呢?会执行它的回退函数,当然这里它没有定义回退函数,所以所以说回退函数也没有什么效果,但是假如说它有回退函数的话,那就会有一些动作,好,呃,接下来还有一个。应该说是更重要的一个应用啊,就是。假如说合约收到了以太币,而且没有任何数据的时候。这是什么情况,这就是纯转账对吧,假如说我们给一个合约纯转账。的时候。就会调用它的回退函数。这就是为什么我们在水龙头合约里面必须把回馈函数定义成配合。因为我们给他纯转账的时候,其实触发的是它的回退函数。
04:01
如果他不是配偶部的,我们就不能转账,所以是这样一个机制。李太保是这样,把纯转账的这个状况跟就是普通的这种函数调用给合约发送消息。的这种状况统一在一起,用一个回退函数的方式统一在一起,但是这个可能就会有一点问题啊,就是。因为我们我们说就是如果我们给一个账户直接去发币的时候,就会调用它的回退函数。那这个动作就就非常危险了,大家可以想到啊。会有什么样的问题呢?因为我们这是向一个合约去发币。去发币,你不知道它里边到底会执行什么的,除非你真正的看过他的代码,对吧,正常来讲,假如你发错了,你你发到了一个合约地址上,你不知道他会是执行什么样的操作。假如说它的回退函数里边是有一些恶意代码,那它是可以获取到你的地址的。
05:05
因为你去调用它的话,它的回退函数里面的message.sender就是你对吧。所以说。他就有可能在里边去去对你的地址做一些特殊的操作。所以这就是我们所说的这个危险的所在,当然我们一般的用户账户的话就还好,因为我们账户本来也没什么别的操作,他没有私钥也转不了我们的币,对吧,但是呢,如果是我们用合约去调合约的时候。就要注意了。合约给合约调用,用transfer方法去给他发币的时候,会调用到它的回退函数。那这个时候它里边的回退函数就有可能反过来再调用你的函数。这一点就是很多合约所谓的一个安全漏洞。
06:01
他为什么会有安全漏洞呢?就是他在自己的合约里边给别人去发币,给给别的合约去,给别人去,就是他本意是想要给别人去transfer一个仪态,但是别人有可能恶意的。我给你的账户,不是我自己的账户,给我给了你一个我部署的合约的账户。这个合约里面,我把它的回退函数做一点手脚。做一点什么手脚呢,我让你我触发他的这个就是某一某一种机制,我让你重复的再去回退去调用。这种调用如果触发成功。在某种情况下,特定的情况下,就能够让他不停的去发力,那这个机制不是说就是凭空这么说的,当然一般的大家可能找不到这样的机会。但是有人真的就找到这样的机会了,就是我们之前给大家介绍的以太坊历史上最大的一次分分叉和最大的一次攻击,对吧,The dog。
07:03
The door当时的攻击就是the door的合约里边有一个地方出现了这样的漏洞。他在合约里边去给别人转译,调用了transfer方法,然后他没有判定别人的这一个合约,就是他transfer的那个地址,到底是什么样的一个地址。那最终的结果呢,就是黑客他自己写了一份合约,然后他注册这个道的这些账号之后。触发到了他的那一个函数,触发到那个函数里边transfer以太要往他的这个合约账户里面转以太,然后就调用到了它里边自己写好的那个回推函数。而他又找到了,就是它里边其他的一些漏洞,就是当然就是里边是具体就是大家就不用了解太太清楚了,就是他有一些就是做溢出的这些攻击啊,或者是怎么样,它触发到了那个临界点,就导致那边的循就就相当于是变成一个循环了,就是你会不停的去给那边发力,然后那边的回退函数又反过来让你这边又回退回去继续发,然后继续回退继续发,所以就用这种方式让the到里边,只要他一攻击,他一调用你那个方式让你发到了他的那个回推函数那里,他就可以不行无限的从这里提B,直到它的盖耗尽。
08:23
因为我们知道以太防里边是有盖限制的嘛,你不可能无限,就最后就是盖耗尽的时候停止,所以它的攻击方式是什么,他肯定不会等到盖耗尽,因为我们知道运行过程当中gas耗尽,那就回退了,对吧,所以他就把gas和它的这个回回退的这个函数调用的方式,调用的次数,它限制到一定程度,他就自己专门算过。我的盖go这个回退调用多少次,然后他就这样每一次攻击,然后让你一次一次的往出发,当时就是大家都很绝望,就是你看到那个他把合约布上去,然后直接掉这边的合约,就看到不停的在往过发币,不停的在往过发币,每次攻击就是这么去做的,所以大家可以想象得到,就是这个里面确实是有坑啊,这种机制确实有坑。
09:14
好,接下来就是啊,最后还有一个就是说,通常只有很少的gas是可以用来完成回退函数调用的,所以说一般for back里边不会放太多的功能,一般都要很廉价才行啊好,这里是一个回退函数的例子,大家看一下看一下就知道了啊,我们看一下现在。嗯,首先应该还来得及把这讲完对吧?呃,我们看一下这个合约是什么意思啊,它首先定义了一个SIM的合约,然后里边直接就定义了一个,大家看这是什么东西方。什么东西都没有一个括号。回退函数对吧,它就定义了一个回退函数,然后后面它是一个外部类型。
10:01
也就是说可以给他转币的。可以从外部调用给他转币,好,那下面有一个test test test类型呢,也定义了一个回退函数,但是它不是payable的,也是一个外部类型,它里面呢做了一个操作,X等于一,就是它的回归函数的做法是修改了自己的状态变量,大家看它是这样,就在一旦调用到了这个。回归函数的时候,它就X变成了一。下面我们再看这个call啊,Call干了一个什么事情?Color定义了一个方式,叫做call test,就是要靠上面的这个test合约好,那它的参数就是test。所以他是把合约当成一个参数传了进来。那这个过程当中相当于它的合约,就是会,大家应该能想到就是会new一个test对吧,这就相当于test test等于new一个test类型。
11:02
就是这样的一个过程啊,就是传递参数的过程,其实就是这样的一个过程,呃,然后下面我们看就是它的这个public returns是一个布尔型的变量,接下来。木耳型的一个success,大家看啊,这个这个写法,这个写法其实跟ES里面,这应该是ES6还是ES7里面的写法啊,跟那个很像,大家看木耳型的一个success定义出来,然后逗号,然后把它括起来,等于后边addressed text。就是把这个合约的地址拿出来,点com,又用到了这个非常底层的一个函数啊点com,所以这个是要他就他就是要直接发出我们所说的自定义的那种消息了,对吧,自定义的函数调用。不是我们直接定义好的那些函数,所以它大家可以看到啊,它后面发的是什么呢?是non existing function,就是没有定义的一个函数,他把这个签名,然后发出去了。
12:08
所以它就相当于是调用了test里边未定义的一个函数,那相当于会掉掉到哪里去呢?肯定就会掉到回退函数对吧,所以这个时候。大家可以看到,它会把这个结果给到一个布尔型的success里边。那为什么后面还有逗号呢?那是因为这个号的结果不仅仅是一个success,还有别的。一些参数,所以说他只取第一个参数,然后把这个参数拿出来,这个输给success出或者false,接下来他require success,也就是说必须得是true。那接下来呢,就是又做了一个事情,叫地址类型,Payable的一个地址类型,定义了一个test payable。它等于address,大家看啊,这个就是我们前面说的,怎么样把一个普通的地址类型转换成一个payable的地址类型的,因为它接下来是准备要要发币的,对吧。
13:13
它发币是要调到这个,呃,就是想要去调这个回退函函数,看能不能去发币的这个状态的,所以大家可以看到啊,本身这个test。Test,它并不是一个payable的练习。我们把它的地址拿出来,强制类型转换成一个U160,因为它是160位的嘛,通过这个做一个中间转换。然后再通过address强制。类型转换,把它转换成一个address payable类型。这就这样的一个转换方式啊,大家可以就就看一下这个我觉得不需要我们掌握或者说去实现,大家看一下就可以,最后它返回一个payable点三,那就是要给这个地址去发币。
14:05
所以是这样一个过程。那大家可以可以想一下,就是这个过程应该它会有一个什么样的结果呢?我们就不手敲了啊,它会有一个什么样的结果,大家猜一下。哎,这里直接报错了啊。看一下。好,下面大家看到这里又有错。哦呃,这个是这个是我们现在编译器的问题啊,大家如果要是网络比较好的话,可以去试一下这个address payable的这个这个类型,因为address payable这个类型的区分,我们前面跟大家说过,是从0.5.0之后才引入的,对吧。
15:04
所以现在我这个4.25的话,他他会报错,这个他不认这个数据类型,我如果要是不给这个payable的话,他就他就没这个错了,所以说他不认这个数据类型,这个我们现在可能测试不了,这个大家下来之后可以去用0.5.0尝试着测一下这个这个合约,看看他的行为到底是什么样的,好那我们就先先跳过吧,主要就是让大家理解回退函数,就在什么情况下会调用回退函数,首先就是上边这个调用了一个根本不存在的函数的时候。这个调用一个根本不存在的方式,就是就是用这种方式调,就是a BI code with nature签名的一个一个abi的这个写法啊,然后就是还有一种情况,在什么情况下会调回推函函数呢?就是转B的时候会调它的回推函数。所以大家在这里面去确认一下这个。
16:03
好,接下来我们已经50多了,我们把后面的快速讲完啊,接下来还有很重要的一个东西叫事件,那就是我们已经见过的这个event了,呃,我们尽管见过,但是我们没有用,没有没有真实的,就是把它用起来啊,在之后我们会把它用起来。它到底是个什么东西呢?它是EVM提供的一种日志基础设施,所以说它是日志系统的一部分,它主要是用用来做记录的操作记录。然后就是形成,形成日志存储在我们整个这个仪态坊里边,它可以用来实现一些基本的交互功能,比如说就是通知UI返回函数调用结果。所以我们可以看到,就是在我们的控制台这里返回的这些东西。其实都是日志系统给我们返回来的,就都是他这套系统来做的,所以大家可以认为这,呃,当然大家把这个认为是一个事件也可以啊,这就是捕获了这一个就是函数调用事件,所以大家可以这么理解,另外就是说在定义的事件触发的时候呢,我们就是我们做了什么事情,我们就是直接把事件存储到EVM的交易日志里边,日志呢,它是区块链里边的一种特殊的数据结构,它跟合约是关联在一起的。
17:28
它和合约的存储会合并在一起,然后存到我们的区块链里面,所以这其实就是我们的合约的一部分。大家可以认为。也是我们的永久存储。那只要某个区块可以访问,那么它相关的日志就可以访问,但是在合约里边不允许我们直接访问日志数据。所以这也是为了安全性和一些就是性能上的一些考量,做了做了限制,就是不可以直接访问在合约里面访问日志和时间。
18:04
它只能就是相当于去发布一个事件对吧,然后去写进日志,呃,那通过日志呢,它就可以实现简单的,这个就是简单支付验证PV,我们所说的大家可以想到,它既然是一个log嘛,所有的日志,那所有的交易都可以顺着一个一个方向去把它追溯起来,我们都可以把它查询出来,那就可以做简单支付验证了。好,那接下来我们就看一下。诶,应该有一页是不是给删掉了,好吧,就是应该有一页是我们那个日志怎么样去用对吧?好,这个我们就就不去说了,应该我们在之前昨天的时候已经有一页跟大家说过,就是用WEB3的方式去去调用,对吧,外部去放就是监听我们的日志,然后去,呃,监听我们的事件,然后去访问整个区块链的日志,拿出我们想要的这个结果来,这就是事件这一部分。
19:00
最后我们再说说这个异常处理,这就是我们所说的之前经常说到的这个assert require。那它俩的区别呢?呃,这这里就是跟大家强调一起,就是扫体异常处理,最大的不同就是说直接回退,所有的状态都撤回到上一个状态,我们这个交易相当于就失效了,只有盖扣掉,别的东西都不生效。所以在这里边,我们的assert和require,它是用于检测判断条件必须为真。而另外还有一个异常处理的函数叫reward reward是什么呢?它就直接抛出异常了,就相当于我们的throw,在在早一些的版本里边也是有throw这一个这个方法的,但现在就是已经弃用了,大家不要用就用reward就可以,它是会标记一个错误,并并且把当前的这个调用返回去。那assert assert和require的差别在哪里呢?就是assert是。
20:03
断言,我确定这个一定是真的,如果错了的话,那就是程序出bug,所以它一般是应用于测试内部错误,用来检查常量,会用到那REQUIRE2什么时候用的块,是说我要求你满足什么样的条件,那一般会检查那个外部的一些输入的状态啊,或者去检查我们现在的就是合约里边的状态变量啊,一般会用require这样的东西来检查。好,最后一点时间,我们再说一下这个单位吧,单位就更简单了,Solidity里边的单位,呃,我这个列多了啊,这个不应该有这么多项。Solid里边的单位其实就是最主要的就是eer对吧,另外还有几个单位是我们可以直接用的,就是尾和芬尼,还有Z。这四个单位是我们可以可以直接用的,这里面列的是他们这个国际单位制的这些转换,但是这个什么香浓和这个loveless babage这几个是不能用的,在里边是不支持的,大家可以自己敲啊,看一下先把这个注了好。
21:18
我们自己敲一个,比方说哎,这里其实已经写出来了,对吧?呃,这里直接二空格一,这就表示转两个以态,我们就不用费劲儿的,因为它默认单位是尾嘛,我们费劲儿后面打18个零肯定就没必要了,直接21ER就可以,这个就很方便,当然我们也可以二五,这也是对的,或者还有什么呢,我们可以诶大家看已经显示出来了啊Z。这个单位也是可以的,Z这个单位是什么呢?是千分之,不是百万分之一的仪态。或者说是十的12次位。是这个量级啊,另外还有一个单位是分离,分离是是千分之一,千分之一个以态,所以大家注意这几个是直接可以打出来的,那比方说像那个芭比是大家看这个,这个就打不出来啊,它没有没有提示大家如果打的话会报错的,所以稍微注意一下这几个单位,另外还有一个大家可能稍微注意一下,就是这个时间,时间单位在扫里边默认的时间单位有这么六个,Second,秒,Minutes,分钟,Hours 10days,天,Weeks,星期,Years,年,那他们的换算单位啊,他们基本换算就是。
22:39
大家看啊,一默认的单位就是second,不用写就是second,这跟尾尾是一样的,那一就是等于1SECOND那一分钟60秒,一一小时60分,这就不说了啊,一天24小时,一星期七天,这不用说了,一年是365天,就是一个years是365个days,这个注意就是没有没有闰年的这个问问题在里面定义死的,这就是一个整数,整数的关系。
23:10
呃,大家注意就是这些是不能直接用在变量后面的啊,我们可以说1SECOND 20second 30days,但是我们不能说啊,我定义一个A,然后我写一个a second不能这么写。所以它相当于就是单位吧,是跟常数放在一起的,作为一个常量来用的,呃,最后就是说,假如你想用这个时间单位来就是换算成时间的话,你怎么算呢?大家看下面给给了一个标准的方法啊,就是说比方说你给定一个start,然后呢,给定一个days after,就是说几天之后对吧。对,就是几天之后,所以它的计算其实就是说我们用。Days after,然后乘以1DAYS。
24:02
这就换算成了秒对不对,那start其实和和这里面这个我们所说的关键词now都是系统里边的这个秒数,它是一个整数。就是我们当前的这个秒,那在计算的时候,我们直接用这个start也是一个描述,加上days after乘以days。那就可以转换当前的描述。就是我们判断当前时间是不是已经超过这个after了呢?Start加上这个大于等于是不是判断,呃,是不是大于等于就可以判断出来啊,这个大家下来之后可以自己再试一试啊。好,那已经到五点了,那大家今天我们就到这里吧,就是如果要是本来我想着有时间我们可以自己去试一下,这个大家感兴趣,就是下来之后自己敲一敲就可以,因为这个不难单位嘛,对吧。
我来说两句