在開發智能合約的過程,由於想要能夠達到多筆撮合的功能,我們實作了一個函式能夠輸入多筆訂單資料。在隨著我們加入了越來越多的功能,同時為了確保合約公正性,而需要越來越的參數,例如:礦工手續費、taker 手續費、maker 手續費和支付手續費方式等,我們遇到了變數過多而無法編譯的情況,而大量的資料所消耗的 gas 也成為一個問題。

我們在撮合的函式中,最後每個訂單會有這些資料:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
uint256 amountSell,
uint256 amountBuy,
address tokenSell,
address tokenBuy,
address user,
uint256 nonce,
uint256 gasFee,
uint256 takerFee,
uint256 makerFee,
uint256 joyPrice,
bool isBuy,
uint8 v,
byte32 r,
byte32 s

於是我們試著想辦法減少變數的數量,在某次我看著 Etherscan 某交易顯示如下的參數資料時

1
2
3
4
5
6
7
8
9
10
11
12
13
Function: trade(address tokenGet, uint256 amountGet, address tokenGive, uint256 amountGive, uint256 expires, uint256 nonce, address user, uint8 v, bytes32 r, bytes32 s, uint256 amount) ***
MethodID: 0x0a19b14a
[0]:0000000000000000000000000000000000000000000000000000000000000000
[1]:000000000000000000000000000000000000000000000000006a94d74f430000
[2]:000000000000000000000000a92f038e486768447291ec7277fff094421cbe1c
[3]:0000000000000000000000000000000000000000000000000000000005f5e100
[4]:000000000000000000000000000000000000000000000000000000000024cd39
[5]:00000000000000000000000000000000000000000000000000000000e053cefa
[6]:000000000000000000000000a11654ff00ed063c77ae35be6c1a95b91ad9586e
[7]:000000000000000000000000000000000000000000000000000000000000001c
[8]:caa3a70dd8ab2ea89736d7c12c6a8508f59b68590016ed99b40af0bcc2de8dee
[9]:26e2347abfba108444811ae5e6ead79c7bd0434cf680aa3102596f1ab855c571
[10]:000000000000000000000000000000000000000000000000000221b262dd8000

我發現所有的參數長度都是 256 bits,無論你變數的型別是 byte32address 還是 uint8。所以大部分的參數左邊都有大量的 ”0”,是未使用的位元資料,很自然的我就想到了利用這些”空間”。

資料合併

首先 uin256byte32 的型別會佔滿整個變數空間,金額數量的 amount 和簽章的 r, s 都是不能使用的變數,所以我選擇了 address 欄位的變數來使用。另外也將不需要太多位元數的型別做了調整:

1
2
3
4
5
nonce -> 40 bits
takerFee -> 16 bits
makerFee -> 16 bits
uint256 joyPrice -> 28 bits
isBuy -> 4 bits (實際上 1 bit 即可, 為了文件容易表示)

一個原本是 address 的變數,例如:

1
000000000000000000000000a92f038e486768447291ec7277fff094421cbe1c

就可以塞入上面的資料變成
Solidity 資料壓縮

上面的資料對應如下

1
2
3
4
5
nonce: 0181bfeb
takerFee: 0014
makerFee: 000a
joyPrice: 0000000
isBuy: 1

所以原本的資料就可以減少為

1
2
3
4
5
6
7
8
9
uint256 amountSell,
uint256 amountBuy,
uint256 tokenSellWithData,
address tokenBuy,
address user,
uint256 gasFee,
uint8 v,
byte32 r,
byte32 s

當然 v 可以也在塞到另一個 address 的變數中,這邊就不再贅述。

編號

到此已經省下不少的變數,但是我又有了新的想法:token 的 address 應該是有限個,不如用資料庫的自動遞增 (Auto increment) 的方式來編號?於是我們進一步的把 token 和 user 都做了編號,最後資料變成:
Solidity 資料壓縮

最右邊的三個分別就表示了tokenSell, tokenBuy 和 user,而橘色的 1 則表示簽章的 v。最終我們的一筆訂單資料壓縮為:

1
2
3
4
5
6
uint256 amountSell,
uint256 amountBuy,
uint256 data,
uint256 gasFee,
byte32 r,
byte32 s

從原本的 14 個變數,省為 6 個變數。

延伸閱讀

上一篇 Solidity 智能合約 Gas 優化技巧 - 函式名稱
下一篇 Solidity 智能合約 Gas 優化技巧 - 變數順序