聊一聊 Zombie Club NFT 的技術八卦

  • FrankFrank
  • /
  • 17 分鐘閱讀
  • /
  • Mar 20, 2022
  • /
  • - views

Zombie Club NFT 最近可謂紅的不得了。這個有潮流明星余文樂站台的項目一開始就引人關注。技術上個人感覺項目的技術處理還是很不錯的,白名單,公售都基本順暢,對於其智能合約的處理網路上已經有了很多技術分析文,這裡就不贅述。

寫這篇文章的初衷,是因為昨天晚上聽了他們團隊的一個技術 AMA,聽到了來自官方團隊對技術上的一些分析和解釋。兩個多小時的 AMA,在最後的聽眾問答環節被推上高潮, 過程有聽眾對於智能合約的處理發出了質疑,問答的重點是關於公售時期幾百筆 out of gas 的原因追問。官方也花了很長時間解釋。但坦白講對於官方的回答有些地方我也不是十分認同。我也多次想發言可惜一直沒有被選中(事實上只有三個人被選中發言)。 星期日沒什麼事那這裡我們就來聊一聊這個 out of gas 是怎麼個回事。

在討論關鍵問題之前,首先我們要理解幾個概念, Gas Limit,** Max Priority Fee** 和 Max Fee

Gas Limit

Gas Limit 指的是你最多允許這個交易消耗多少單位的 Gas。 注意這裡指的是 Gas 單位,不涉及到金錢。用平民化的例子來說,比如我要從元朗開車到中環,Gas Limit 就是指我允許這個旅程最多消耗多少升汽油。一般而言,傳入相同參數的智能合約 function 調用,如果智能合約中沒有特別的邏輯(下面會講),其消耗的 gas 應該基本固定,這也是 metamask 進行 gas 預估的原理。用上面汽車的例子,同一架汽車從元朗開車到中環,行駛相同的路徑,每次消耗的汽油應該基本固定。

Max Priority Fee

首先有個 「Fee」 字的都是和錢相關的概念。這個數值和最後我們支付的 gas fee 息息相關。 Max Priority Fee 是 EIP-1559 升級後新出的概念。 原本劃一收費的 gas price 現在會分為兩個部分, 一個是 Base Fee(基礎費用),這個費用可以理解為海鮮價,整個以太坊網路會根據網路繁忙程度自動計算,不可更改, 每個區塊內的所有交易 Base Fee 都相同。另一個是 Priority Fee(礦工小費), 這個數字用戶可以自行設定,直接支付給礦工作為小費。Priority Fee 設置越高,該筆交易則會擁有越高的優先級,更快完成。這個 Priority Fee 也是在 NFT 限量搶購等需要「鬥快」的交易中充當重要角色的參數。同樣用汽車的例子,Priority Fee 可以理解為,想象有一堆人在加油站等待加油出發,而加油站的汽油卻有限,你願意為每升汽油多花多少錢給小費,給的小費越多,你便可以越快的速度被服務,加滿油開始你的旅程。

Max Fee

Max Fee 和Max Priority Fee 一樣,都是關於「錢」的概念。 Max Fee 的概念是指你願意為每一單位的 Gas 支付的最大費用。 Max Fee 必須大於或者等於 Base Fee + Max Priority Fee 。看到這裡,不知大家會不會有疑問, 為什麼會有 Max Fee 的存在? 直接 Base Fee + Priority Fee 不就好了? 其實仔細想想就會知道, Max Fee 的存在是為了保障我們對交易的費用控制, 上面說了 Base Fee 是會網路狀況變動的「海鮮價」,如果沒有 Max Fee 的控制則可能會導致當網路突然異常繁忙時的 Gas 失控。

對於 Max Fee 的估算,EIP-1559 也給出了建議:

Max Fee = (2 * Base Fee) + Max Priority Fee

之所以會乘 2 是為了防止 Base Fee 在下一個 Block 增加而導致費用不足而交易失敗, 根據EIP-1559 對於 Base Fee 的約定, Base Fee 乘 2 可以保證在連續 6 個滿載區塊的情況下你的交易都可以順利執行。絕大多數情況下, 真正的 Gas 消耗要比 Max Fee 少。

了解了上面的概念後,我們就可以聊聊 Zombie Club NFT 公售時的這個小插曲了。 公售開始時有大量的 Trx 出現 out of gas 的錯誤。所謂 out of gas,就是指整個交易已經用盡了 Gas Limit 中指定的上限,因此礦工會放棄執行餘下的工作,該筆交易會立即失敗,並且因為礦工已經耗費了算力,因此消耗掉的 Gas 不會退回(但是交易中夾帶的 ETH 會返還)。所以說,即便你的 Priority Fee 給的再高, 但只要 Gas Limit 設置的不夠,則最終依然會失敗,而且損失的錢會比因為 Priority Fee 不夠而最後失敗的那些交易還要多很多。就好像這個交易,Priority 已經設置到驚人的 11,111 Gwei,可惜還是因為 Gas Limit 不夠,而導致最終失敗。

我們可以發現, 出現 out of gas 的錯誤,集中發生在 1440443, 1440444, 1440445 這幾個區塊,也就是公售剛開始的前後幾個區塊中。昨晚官方 AMA 中的解釋,這是因為 Metamask 錯誤的估算了 Gas Limit 導致,可能是 Metamask 版本太舊,可能是用了其它的錢包等原因導致。

Gas 估算

好了,那問題來了。 MetaMask 是怎麼估算 Gas Limit 的呢?

如果做過 ethereum 區塊鏈開發的人大概都知道, 在 web3 等常用的工具中都有 estimateGas 這個 function,estimateGas 估算 gas 用量的方法是使用完全相同的參數,模擬真實情況透過 RPC 調用智能合約的 function,然整個交易模擬執行一次,從而計算出整個調用所需要的 Gas 用量。這個模擬調用,不會提交到區塊鏈,因此不會真正被礦工記錄,不會真正消耗 gas,但這個模擬調用,卻能夠較為精準的估算 Gas 用量,也可以發現可能遇到的錯誤。這也是有時候我們會見到 MetaMask 告訴我們 「This trx is expected to fail」的原因。

那為什麼 MetaMask 會估算錯誤呢?

首先上面講到, MetaMask 對 gas 的估算是傳入相同的參數,模擬執行智能合約,從而得出 Gas 用量。因此,如果智能合約的處理不當,在傳入相同參數的情況下兩次執行會出現不同的程式邏輯,則會導致估算的結果和實際的 gas 用量不符的情況。

舉個例子,假如智能合約使用 block number 或者 block timestamp 來決定執行邏輯,例如,如果區塊時間 > 22:00:00 則進行 public mint, 如果區塊時間 < 22:00:00 則進行 whitelist mint, 這裡因為區塊時間是一個變量,即便傳入的參數一樣,因為區塊時間的不同,因此會導致智能合約運行的邏輯不一樣,因此導致所謂「Gas 估算錯誤」。

再比如,如果調用的 function 中含有 for loop, 即便傳入的參數一樣,但 for loop 因為某些原因提早或延遲終止,也會導致 Gas 的消耗出現明顯差異。

回到 Zombie Club 的 Case, AMA 中官方有說明,智能合約中的確運用了 Block Timestamp 來進行某些邏輯判斷,如果我沒有理解錯就是根據 Block Timestamp 來區分目前是白名單 mint 還是公售 mint。而公售 mint 因為某些原因需要記錄 signature 而導致消耗的 gas 比白名單要多。所以不難想象出現 「Gas 估算錯誤」的原因就是 MetaMask 估算時,因為 timestamp 的不同而用白名單 mint 的邏輯進行了估算,但實際執行時卻是使用了 public mint 的邏輯,因此導致實際 gas 消耗要比估算高。

Zombie Club的做法有問題嗎

不能說有問題,但我覺得應該有更好的處理方式。

首先,官方說他們的前端沒有去建議 Gas Limit,而是將之交給用戶去設定。作為一個源碼沒有公開的智能合約,作為用戶其實是沒有可能能夠預測 gas 的用量,更何況是在秒殺那種分秒必爭的情況下。因此說交給用戶去設定 gas limit,這個說法多少有點勉強。我覺得對於 mint 這種邏輯簡單的 function,前端網頁其實應該給出 gas limit 而不是靠 metamask 去估算。昨天的 AMA 中官方有提到這個問題,他們的說法是希望將這個控制權交給用戶去決定,就好像設置 Gas Price 一樣,而不是由官方決定。對於這個觀點,我持有保留態度。 gas limit,不同於 gas fee,只要合約沒有類似 for loop 等的會嚴重影響 gas 用量的不確定因素,每次合約調用的用量基本都是固定的,完全可以通過在測試網測試等方法提早得知。 網頁前端在提交 trx 給 metamask 時,完全可以根據測試網的 gas 用量, 再加上一個足夠的 buffer,例如測試得出的 gas 用量是 21,000, 那麼可以建議 gas limit 是 21,000+(21,000 x 0.5) = 31,500 , 提交給 metamask,這樣基本可以避免出現 out of gas 的問題。

另外就是官方有提到智能合約會根據 block timestamp 去決定當前是公售還是白名單, 這樣做的原因是為了「更加去中心化」,防止被懷疑「官方作弊」。 對於這個觀點我也持保留態度。首先對於 block timestamp 這個全局變量,solidity 社區應該都有共識,不應該用它來參與一些重要的邏輯,因為礦工對於這個參數有一定的控制力。如果一定要在智能合約中加入一個標記來決定,那也完全可以透過一個參數,例如 state = 1 是白名單, state = 2 是公開發售來控制。 再者,在 Zombie Club NFT 這個例子中,官方引入了中心化伺服器 API 簽名認證(這個做法本身非常棒),但這已經和官方「更加去中心化」的說法有違背。 既然有了 API 的簽名參與,整個邏輯已經完全可以由 API 控制,那麼加入 block timestamp 的驗證則顯得畫蛇添足,也因此導致了 metamask 的估算錯誤,出現幾百個 out of gas 的問題。

結語

這次的 out of gas,嚴格來講並不能說是完全是官方的責任,究其原因的確是因為 block timestamp 導致 MetaMask 對 gas limit 的估算錯誤,儘管這個問題開發者如果再考慮仔細一點本來應該是可以避免。作為開發者我想我們在設計程式的時候應該要考慮到區塊鏈上的不確定性,特別是對於 block timestamp 這類變量的控制。而作為參與者或許也應該在 MataMask 做出的 Gas Limit 估計上再增加一定的額外額度。 畢竟,秒殺這種東西,本來就風險很高,更何況當時的 NFT 存量只有少得可憐的 1000 多個,既然願意參與,那麼應該也要知道可能發生的風險。

我本人覺得 Zombie Club NFT 這個項目是一個非常棒和有潛力的項目。不難看出,項目方為了它花費了大量心血。技術上,智能合約對於 gas 的用量控制,以及機器人的防範,都做的非常好。儘管公開發售出現了 out of gas 的問題算是有點不完美。 一個項目的成功與否,技術是基石,但團隊後續的態度,對項目的經營,社區的維護和發展,對項目的成敗會更為重要。從項目的 discord 社群很容易能夠看出,社群裡面的參與者們,非常積極的出謀獻策,參與討論,這在其他的項目中是很少見的。

衷心希望這個項目能夠成為藍籌之一。