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
都不起作用.