本篇文章介紹 Solidity 中的 Custom Error 用法,包含定義錯誤、用法和錯誤處理的方式。

用法

Solidity v0.8.4 之後新增這個功能,能夠用自訂的錯誤來回傳錯誤原因。在之前我們一般使用以下方式丟出錯誤:

1
2
3
4
5
6
7
require(shouldPass, "MyError");

// 或是

if (!shouldPass) {
revert("MyError");
}

使用 Custom Error 用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 定義自訂錯誤
error MyError();

contract MyContract {
function test() external {
// ...
if (!shouldPass) {
revert MyError();
}
// ...
}
}

Custom Error 也可以帶參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定義自訂錯誤
error MyErrorWithArgs(uint status);

contract MyContract {
function test() external {
// ...
if (case1) {
revert MyErrorWithArgs(0);
} else if (case2) {
revert MyErrorWithArgs(1);
}
// ...
}
}

其實可以下面的用法想成是一樣的

1
2
3
4
5
revert("MyError");

// 相當於
// error Error(string reason);
// revert Error("MyError");

不過實際上 Error 和 Panic 的名稱是特殊名稱,不可以使用。

例外處理

在合約要使用 Try Catch 來捕捉 Custom Error 比較複雜,目前可以用以下方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
contract TryCatch {
MyContract myContract = new MyContract();

function execute() external {
try myContract.callSomeMethod() {
// 成功時進入
} catch (bytes memory reason) {
bytes4 selector = bytes4(reason);
if (selector == bytes4(MyError.selector)) {
// 發生 MyError() 時會進入
} else if (selector == bytes4(MyError.selector)) {
// 發生 MyErrorWithArgs(uint) 時會進入

// 如果想要進一步區分 status
bytes32 hash = keccak256(reason);
if (hash == keccak256(abi.encodeWithSelector(MyErrorWithArgs.selector, 0))) {
// 發生 MyErrorWithArgs(0) 時會進入
} else if (hash == keccak256(abi.encodeWithSelector(MyErrorWithArgs.selector, 1))) {
// 發生 MyErrorWithArgs(1) 時會進入
}
} else {
// 其他情況
}
}
}
}

優缺點

優點

  • 編譯的合約大小比較小。
  • 執行消耗的 Gas 低一點。
  • 可以帶參數。

缺點

  • 需要節點支援:例如本地開發用的 Ganache 不支援,RPC 回傳的內容無法解析是什麼錯誤。
  • 需要區塊瀏覽器支援顯示:目前瀏覽器似乎無法解析這類型錯誤,無法知道是什麼錯誤。
  • web3.js 和 ether.js 支援不夠完整,需要額外處理。

延伸閱讀

使用 Ethers.js 解析 Custom Error
使用 Web3.js 解析 Custom Error