說明

你也許不會想到,函式名稱也會對 gas 消耗造成影響,事實上,在最糟的情況下,甚至會有上千 gas 的差距。我們來看看下面程式:

1
2
3
4
contract Test {
function b() public {
}
}

上面程式執行 b() 會消耗 125 gas,接著改成下面:

1
2
3
4
5
6
7
contract Test {
function a() public {
}

function b() public {
}
}

這次執行 b() 變成消耗 147 gas,一樣的空函式怎麼消耗增加了呢?試著執行 a() 會發現只消耗 125 gas。原來在智能合約中,函式存在前後順序,排序越後面的會消耗越多,每差一個順位就會多 22 gas。此時你可能會想把它改成下面:

1
2
3
4
5
6
7
contract Test {
function b() public {
}

function a() public {
}
}

但結果卻沒有變化,b() 一樣消耗 147 gas,因為函式的排序是依據 Method ID。在這個例子中,他們的 Method ID 如下:

1
2
a: 0x0dbe671f
b: 0x4df7e3d0

依據這個規則我們再多加了一個函式:

1
2
3
4
5
6
7
8
9
10
contract Test {
function a() public {
}

function b() public {
}

function f() public {
}
}

他們的 Method ID 如下:

1
2
3
a: 0x0dbe671f
b: 0x4df7e3d0
f: 0x26121ff0

排序之後我們可以得到 a < f < b 的順序,猜猜這次執行 b() 會花多少 gas?沒錯,答案是會消耗 169 gas。

參與排序的成員

事實上,所有公用成員都會被列入計算。所以除了函式之外,屬性也會被列入計算,也包含 constant。例如:

1
2
3
4
5
6
contract Test {
uint256 public a;

function b() public {
}
}

b() 會消耗147 gas。

當然繼承的公用屬性和函式也都會列入計算,所以當合約功能較多後,公用成員可能會有幾十個。假設有 50 個的話,最後一個函式就多了一千的消耗。

函式簽章

在說明 Method ID 產生規則之前,要先知道函式簽章的規則,函式簽章包含函式名稱和參數型別。例如:

1
2
3
4
5
function func() public {
}

function func2(address[] addr, uint256 amount) public {
}

上面兩個函式的簽章分別為:

1
2
func()
func2(address[],uint256)

為了參與排序,屬性也會有簽章存在,再以同樣方式產生 Method ID。一般屬性視為沒有參數的函式,且只看名稱不看型別,無論是 uint256 還是 addressmapping 則將 Key 視為第一個參數,例如:

1
2
3
uint256 public a;
address public b;
mapping(address => uint256) public c;

簽章

1
2
3
a()
b()
c(address)

Method ID

Method ID 的產生規則如下:

1
keccack256(函式簽章) 取前四 bytes

所以上面的 a(), b()f() 函式簽章拿去做 keccak256( 可利用我寫的線上 Keccak-256 工具 ) 可以得到下面結果:

1
2
3
0dbe671f81a573cff601b9c227db0ed2e5339d3e0a07edc45c42f315a9cb8f0f
4df7e3d0fdffd35719c59893b4839a04b686be9ac7bec9cdd04a272e9ad7c628
26121ff025a6ba40cf27bcfb7cd50bcb8eab64881826af3760564c9e1ffa71eb

可以看出前四個 Bytes 正是 Method ID。

結論

在暸解了這些運作原理之後,我們可以做出以下兩種應對方式:

  1. 減少公用成員
  2. 將常用函式順位提高

第一點很容易理解與執行,而第二點我們可以透過命名的方式來達成。但是要人工的方式,命名到雜湊值剛好較低的方式並不容易。於是我寫了一個函式名稱優化小工具來處理這個問題。以上面的例子來說,假設 b() 是常用的函式,若想將它提升順位,可以輸入函式簽章 b(),程式會自動找出一個 Method ID 前兩個 bytes 為零,結尾亂碼的新名稱:

1
b_A6Q(): 0x0000e3fa

另外由於 Method ID 會出現在 input data 中,所以除了排序提升的 gas 減少之外,還額外省了 128 gas ( 請參考上一篇 Solidity 智能合約 Gas 優化技巧的交易資料章節 )。

延伸閱讀

上一篇 Solidity 智能合約 Gas 優化技巧
下一篇 Solidity 智能合約 Gas 優化技巧 - 資料壓縮