Solidity初学-0.8新特性
https://www.youtube.com/watch?v=xv9OmztShIw&list=PLO5VPQH6OWdVQwpQfw9rZ67O6Pjfo6q-p
helloworld和溢出问题
contract HelloWorld {
/**
* @dev Prints Hello World string
*/
function print() public pure returns (string memory) {
return "Hello World!";
}
}
contract SafeMath {
function testUnderflow() public pure returns(uint) {
uint x = 0;
x--;
return x; // 0.8版本之前跟C一样会返回最大值,之后会报错
}
function testUncheckedUnderflow() public pure returns(uint) {
uint x = 0;
unchecked {x--;}
return x; // 正常版本
}
}
testUncheckedUnderflow
方法它会返回一个非常大的数字,如果没有unchecked
代码块的话也就是testUnderflow
方法,可以看到右边控制台输出了一个Error occured
0.8新特性:自定义错误
0.8版本除了处理了溢出问题之外,还让开发者可以自定义错误custom error
, 相比于直接返回错误信息字符串要好得多. 返回字符串的话, 是要消耗gas的
contract VendingMachine {
address payable owner = payable(msg.sender);
function withdraw() public {
if (msg.sender != owner)
revert(""); // 2560 gas 并且输入字符串越长它越多
// 可能是版本问题, 加上这段代码会变成infinite gas
// owner.transfer(address(this).balance);
}
}
contract VendingMachine {
address payable owner = payable(msg.sender);
error Unauthorized();
function withdraw() public {
if (msg.sender != owner)
revert Unauthorized(); // 2324 gas, 比上面那种形式少了一些
// owner.transfer(address(this).balance);
}
}
contract VendingMachine {
address payable owner = payable(msg.sender);
error Unauthorized(address caller);
function withdraw() public {
if (msg.sender != owner)
revert Unauthorized(msg.sender);
owner.transfer(address(this).balance);
}
}
理想效果如下
0.8版本之后, error也可以放到合约之外去定义, 这样一来各个合约就都能用到自定义error了. 函数也同理. 甚至可以给其它.sol
文件引入
// HelloWorld.sol
function helper(uint x) view returns (uint){
return x * 2;
}
// Import.sol
pragma solidity ^0.8;
import {Unauthorized, helper as h1} from './HelloWorld.sol';
// 注意不能重名
function helper() view returns(uint){
return 1;
}
contract Import {
//...
}
Create2
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract D {
uint public x;
constructor(uint a) {
x = a;
}
}
contract Create2 {
function getBytes32(uint salt) external pure returns (bytes32) {
return bytes32(salt);
}
function getAddress(bytes32 salt, uint arg) external view returns (address) {
address addr = address(uint160(uint(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
salt,
keccak256(abi.encodePacked(
type(D).creationCode,
arg
))
)))));
return addr;
}
address public deployedAddr;
function createDsalted(bytes32 salt, uint arg) public {
D d = new D{salt : salt}(arg);
deployedAddr = address(d);
}
}
这段Solidity代码包含两个合约:D
和 Create2
。下面是对代码的解释:
-
合约
D
:D
是一个简单的合约,包含一个公共的uint
类型变量x
和一个构造函数。- 构造函数在合约部署时被调用,接受一个参数
a
并将其赋值给变量x
。
-
合约
Create2
:-
Create2
包含两个外部函数和一个公共的地址变量deployedAddr
。 -
函数
getBytes32
:- 接受一个
uint
类型的参数salt
,并返回它的bytes32
表示形式。
- 接受一个
-
函数
getAddress
:- 接受一个
bytes32
类型的参数salt
和一个uint
类型的参数arg
。 - 使用
keccak256
哈希函数构造一个地址,并返回该地址。 - 构造地址的方式是利用 CREATE2 EVM 指令,该指令允许在特定地址上创建合约,而不是在交易之后立即创建。这是为了在合约部署时能够预测合约的地址。
- 构造地址的关键部分是使用
keccak256
对合约创建代码和参数进行哈希,以及指定的salt
。这确保了地址的唯一性。
- 接受一个
-
函数
createDsalted
:- 接受一个
bytes32
类型的参数salt
和一个uint
类型的参数arg
。 - 使用 CREATE2 指令在特定地址上创建一个新的
D
合约实例,传递arg
作为构造函数的参数。 - 将新创建的合约地址存储在
deployedAddr
变量中。
- 接受一个
-
这个合约的主要目的是演示使用 CREATE2 指令在特定地址上创建合约的过程。通过使用 bytes32
类型的 salt
,可以在不同的情况下创建具有相同构造参数的合约,并且每个合约都会有唯一的地址。这在某些场景下可能是有用的,例如在链上创建合约的预测地址,以便在将来进行交互。
可以看到,用123
的byte32
数据和salt=777
,获取了地址0x023...
然后调用createDsalted
方法, 参数为上面的byte32
和salt
, 最终可以获取到一样的地址.
未解决的问题
在测试自定义错误输出的时候, 反复对比教程发现输出不一样, 我的测试并没有返回error. 换方法名或者加payable
都不起作用.