Solidity
三種拋出異常的方法:error
,require
和assert
,並比較三種方法的gas消耗。
error
是solidity 0.8.4
版本新加的內容,方便且有效率(省gas)地向使用者解釋操作失敗的原因,同時還可以在拋出異常的同時攜帶參數,幫助開發者更好地調試。人們可以在contract
之外定義異常。下面,我們定義一個TransferNotOwner異常,當用戶不是代幣owner的時候嘗試轉賬,會拋出錯誤:
error TransferNotOwner(); // 自定义error
我們也可以定義一個攜帶參數的異常,來提示嘗試轉帳的帳戶地址
error TransferNotOwner(address sender); // 自定义的带参数的error
🧑💻 在執行當中,error
必須搭配 revert
(回退)指令使用。
function transferOwner1(uint256 tokenId, address newOwner) public {
if(_owners[tokenId] != msg.sender){
revert TransferNotOwner();
// revert TransferNotOwner(msg.sender);
}
_owners[tokenId] = newOwner;
}
我們定義了一個transferOwner1()
函數,它會檢查代幣的owner
是不是發起人,如果不是,就會拋出TransferNotOwner
異常;如果是的話,就會轉帳。
🧑💻 require
命令是solidity 0.8版本之前拋出異常的常用方法,目前許多主流合約仍然還在使用它。
它很好用,唯一的缺點就是gas隨著描述異常的字串長度增加,比error指令高。
- 使用方法:require(检查条件,"异常的描述"),當檢查條件不成立的時候,就會拋出例外。
我們用require命令重寫上面的transferOwner1函數:
function transferOwner2(uint256 tokenId, address newOwner) public {
require(_owners[tokenId] == msg.sender, "Transfer Not Owner");
_owners[tokenId] = newOwner;
}
assert
命令一般用於程式設計師寫程序debug,因為它不能解釋拋出異常的原因(比require少個字串)。它的用法很簡單,assert
(检查条件),當檢查條件不成立的時候,就會拋出異常。
我們用assert
命令重寫上面的transferOwner1
函數:
function transferOwner3(uint256 tokenId, address newOwner) public {
assert(_owners[tokenId] == msg.sender);
_owners[tokenId] = newOwner;
}
error
的指令是以 function 名稱來代替錯誤訊息的呈現,與 require 相比,error
的 gas 消耗較低,因為它不會將錯誤訊息寫入到區塊鏈中。
讓我們比較一下三種拋出異常的gas
消耗,透過remix控制台的Debug按鈕,能查到每次函數呼叫的gas消耗分別如下: (使用0.8.17版本編譯)
error
方法gas
消耗:24457 (加入參數後gas消耗:24660)require
方法gas
消耗:24755assert
方法gas
消耗:24473 我們可以看到,error方法gas最少,其次是assert,require方法消耗gas最多!
因此,error既可以告知用戶拋出異常的原因,又能省gas,大家要多用! (注意,由於部署測試時間的不同,每個函數的gas消耗會有所不同,但是比較結果會是一致的。)
Solidity
中允許函數進行重載(overloading),即名字相同但輸入參數類型不同的函數可以同時存在,他們被視為不同的函數。注意,Solidity不允許修飾器(modifier)重載。
例子:
function saySomething() public pure returns(string memory){
return("Nothing");
}
function saySomething(string memory something) public pure returns(string memory){
return(something);
}
可以看到 function name 是一樣的但是沒有錯(參數不同)實現多載
但有一點要注意的
🧑💻 在呼叫重載函數時,會把輸入的實際參數和函數參數的變數類型做成匹配。 如果出現多個符合的重載函數,則會報錯。下面這個例子有兩個叫f()的函數,一個參數為uint8
,另一個為uint256
:
function f(uint8 _in) public pure returns (uint8 out) {
out = _in;
}
function f(uint256 _in) public pure returns (uint256 out) {
out = _in;
}
我們調用f(50),因為50既可以被轉換為uint8,也可以被轉換為uint256,因此會報錯。
注意下面這個雖然會錯誤,但是選擇棄還是不一樣喔(參數不同)
function f(uint8 _in) public pure returns (uint8 out) {
out = _in;
}
function f(uint256 _in) public pure returns (uint256 out) {
out = _in;
}
庫合約是一種特殊的合約,為了提升Solidity
程式碼的複用性和減少gas
而存在。庫合約是一系列的函數合集,由大神或專案方創作,咱們站在巨人的肩膀上,會用就行了。
(就是把一些常用的函數寫成庫合約,供大家調用)
他和普通合約主要有以下幾點不同:
- 不能存在狀態變數
- 不能繼承或被繼承
- 不能接收以太幣
- 不可以被銷毀
- 利用using for指令
指令using A for B;可用於附加庫合約(從庫A)到任何類型(B)。新增指令後,庫A中的函數會自動加入為B類型變數的成員,可以直接呼叫。注意:在呼叫的時候,這個變數會被當作第一個參數傳遞給函數:
// 利用using for指令
using Strings for uint256;
function getString1(uint256 _number) public pure returns(string memory){
// 库合约中的函数会自动添加为uint256型变量的成员
return _number.toHexString();
}
- 透過庫合約名稱呼叫函數
// 直接通过库合约名调用
function getString2(uint256 _number) public pure returns(string memory){
return Strings.toHexString(_number);
}
這幾個 library很好用
- Strings:將uint256轉換為String
- Address:判斷某個地址是否為合約地址
- Create2:更安全的使用Create2 EVM opcode
- Arrays:跟數組相關的庫合約
import
導入文件基本上和 javascript 的引入一樣,也可以直接使用 github的部分