区块链学习第九期
solidity小细节【下篇】
[toc] ---9:error、require、assert
这三种都是抛出异常的方法 但是用法和消耗的 gas 不一样
error 方法 gas 最少,其次是 assert,require 方法消耗 gas 最多!因此,error 既可以告知用户抛出异常的原因,又能省 gas
Error
error 是 solidity 0.8 版本新加的内容,方便且高效(省 gas)地向用户解释操作失败的原因。人们可以在 contract 之外定义异常。下面,我们定义一个 TransferNotOwner 异常,当用户不是代币 owner 的时候尝试转账,会抛出错误:
1 | error TransferNotOwner(); // 自定义error |
在执行当中,error 必须搭配 revert(回退)命令使用。
1 | function transferOwner1(uint256 tokenId, address newOwner) public { |
定义了一个 transferOwner1()函数,它会检查代币的 owner 是不是发起人,如果不是,就会抛出 TransferNotOwner 异常;如果是的话,就会转账。
Require(平常用得很多)
require 命令是 solidity 0.8 版本之前抛出异常的常用方法,目前很多主流合约仍然还在使用它。它很好用,唯一的缺点就是 gas 随着描述异常的字符串长度增加,比 error 命令要高。使用方法:require(检查条件,”异常的描述”),当检查条件不成立的时候,就会抛出异常。
用 require 命令重写一下上面的 transferOwner 函数:
1 | function transferOwner2(uint256 tokenId, address newOwner) public { |
Assert
assert 命令一般用于程序员写程序 debug,因为它不能解释抛出异常的原因(比 require 少个字符串)。它的用法很简单,assert(检查条件),当检查条件不成立的时候,就会抛出异常。
用 assert 命令重写一下上面的 transferOwner 函数:
1 | function transferOwner3(uint256 tokenId, address newOwner) public { |
10:库合约(不同点)
为了提升 solidity 代码的复用性和减少 gas 而存在(就像查字典一样)
特点
- 不能存在状态变量
- 不能够继承或被继承
- 不能接收以太币
- 不可以被销毁
如何在合约中使用
- 利用 using for 指令
1 | // 利用using for指令 |
- 通过库合约名称调用库函数
1 | // 直接通过库合约名调用 |
常用库函数
- String:将 uint256 转换为 String
- Address:判断某个地址是否为合约地址
- Create2:更安全的使用 Create2 EVM opcode
- Arrays:跟数组相关的库函数
11:接收 ETH:receive、fallback
receive
receive()只用于处理接收 ETH。一个合约最多有一个 receive()函数,声明方式与一般函数不一样,不需要 function 关键字:receive() external payable { … }。receive()函数不能有任何的参数,不能返回任何值,必须包含 external 和 payable。
fallback
fallback()函数会在调用合约不存在的函数时被触发。可用于接收 ETH,也可以用于代理合约 proxy contract。fallback()声明时不需要 function 关键字,必须由 external 修饰,一般也会用 payable 修饰,用于接收 ETH:fallback() external payable { … }。
12:发送 ETH:transfer、send、call
transfer
用法是 接收方地址.transfer(发送 ETH 数额)。
- transfer()的 gas 限制是 2300,足够用于转账,但对方合约的
- fallback()或 receive()函数不能实现太复杂的逻辑。
- transfer()如果转账失败,会自动 revert(回滚交易)
send
用法是 接收方地址.send(发送 ETH 数额)。
- send()的 gas 限制是 2300,足够用于转账,但对方合约的
- fallback()或 receive()函数不能实现太复杂的逻辑。
- send()如果转账失败,不会 revert。
- send()的返回值是 bool,代表着转账成功或失败,需要额外代码处理一下。
call
用法是 接收方地址.call{value: 发送 ETH 数额}(“”)。
- call()没有 gas 限制,可以支持对方合约 fallback()或 receive()函数实现复杂逻辑。
- call()如果转账失败,不会 revert。
- call()的返回值是(bool, data),其中 bool 代表着转账成功或失败,需要额外代码处理一下。
代码样例:
1 | // call()发送ETH |
区别
- call 没有 gas 限制,最为灵活,是最提倡的方法;
- transfer 有 2300 gas 限制,但是发送失败会自动 revert 交易,是次优选择;
- send 有 2300 gas 限制,而且发送失败不会自动 revert 交易,几乎没有人用它
13:call 和 delegatecall
最根本的区别就是:
A 调用call改变的是 B 的变量
A 调用delegatecall改变的是 A 的变量
应用场景:当我部署 A 后,代码已经固定不能改变,我只有+1 的方法没有+2 的方法怎么办? 在 B 中实现+2 的方法 在 A 中用 delegatecall 调用即可,这样数据就不会存储在另一个地方
1 | contract A{ |
14:create2 实际应用场景
15:selfdestruct
selfdestruct
selfdestruct 命令可以用来删除智能合约,并将该合约剩余 ETH 转到指定地址。selfdestruct 是为了应对合约出错的极端情况而设计的。它最早被命名为 suicide(自杀),但是这个词太敏感。为了保护抑郁的程序员,改名为 selfdestruct。
如何使用 selfdestruct
selfdestruct(_addr);
其中_addr 是接收合约中剩余 ETH 的地址。
1 | contract DeleteContract { |