Skip to content

Latest commit

 

History

History
148 lines (113 loc) · 5.94 KB

File metadata and controls

148 lines (113 loc) · 5.94 KB

15. 異常

Solidity 三種拋出異常的方法:errorrequireassert,並比較三種方法的gas消耗。

errorsolidity 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

讓我們比較一下三種拋出異常的gas消耗,透過remix控制台的Debug按鈕,能查到每次函數呼叫的gas消耗分別如下: (使用0.8.17版本編譯)

  • error方法gas消耗:24457 (加入參數後gas消耗:24660)
  • require方法gas消耗:24755
  • assert方法gas消耗:24473 我們可以看到,error方法gas最少,其次是assert,require方法消耗gas最多!

因此,error既可以告知用戶拋出異常的原因,又能省gas,大家要多用! (注意,由於部署測試時間的不同,每個函數的gas消耗會有所不同,但是比較結果會是一致的。)

16. 函數重載

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; 
 }

17. 庫合約站在巨人的肩膀上

庫合約是一種特殊的合約,為了提升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:跟數組相關的庫合約

18. Import

import 導入文件基本上和 javascript 的引入一樣,也可以直接使用 github的部分