以太坊漏油的 Gas 罐:揭秘 13 個代價高昂的 Gas 模型矛盾之處

本文為機器翻譯
展示原文

以太坊中的 13 個 Gas 模型不一致問題

這篇文章總結了以太坊 gas 模型中的 13 個不一致之處。
所有符號均來自以太坊黃皮書(特別是附錄 G.費用表)。


1. 創建賬戶時,外部交易不會產生新賬戶費用( G_ { newaccount } G newaccount 內部交易會產生

例子
假設你想讓合約C將ETH轉移到一個尚不存在的全新賬戶A

  • 如果C直接轉賬,內部新建賬戶路徑收取25,000 gas ( G_ { newaccount } G newaccount )
  • 替代方案:首先從 EOA 向A發送一筆外部交易,費用為 1 wei。這筆交易的Gas 成本為 21,000(標準基數)。之後, A就存在了。現在CA轉賬不再產生新賬戶費用(節省約 4,000 Gas )。

結論
如果你依靠合約來創建新賬戶,則需要支付 25,000 gas。
但是如果你先發送一個外部交易(21,000 gas),然後讓合約發送資金,你實際上可以節省~4,000 gas

原因
外部和內部創建路徑使用不同的收費掛鉤;只有合約調用才會觸發明確的新賬戶費用。

2. 預編譯調用有時會跳過交易輸入字節費用

例子
調用預編譯合約(例如ECRECOVER )可能會跳過交易輸入字節的計費。通常情況下,每個輸入字節的Gas 費用為 4(零)16(非零) 。以下是兩個真實的交易示例:
* 1. 交易0x6b01使用 24,276 gas 調用ECRECOVER ,其中 276 gas 用於輸入字節。
* 2. 交易0x1fb0使用 24,000 gas 調用ECRECOVER ,其中 0 gas 用於輸入字節。
如果你閱讀執行客戶端的源代碼,你會發現第二個事務也可以執行ECRECOVER ,而無需對輸入字節進行計費。客戶端會用零數據填充輸入以匹配預期的大小。

補充說明
我想你足夠聰明,會注意到我提到的兩筆交易都是外部交易。
那麼他們為什麼要調用預編譯合約呢?

我猜部分原因是一些瀏覽器錯誤地將預編譯地址(0x01-0x0A)標記為“刻錄地址”,這進一步使用戶感到困惑(請參見下面的快照)。

burn_address
burn_address 539×182 39.1 KB

此外,在這些特殊地址(0x01–0x0A)中部署預編譯地址是一個失敗的設計。
有時,人們只是想直接呼叫這些特殊地址。

原因
預編譯合約的地址設計不良以及區塊瀏覽器的誤導,導致混亂和錯誤標記。

3. 即使從未訪問過,訪問列表條目也會收費(即G_{accesslistaddress} G a c c e s s l i s t a d r e s s G_{accessliststorage} G a c c e s s l i s t s t o r a g e

例子
EIP-2930 引入了訪問列表,允許交易指定其想要訪問的地址和存儲槽。然而,交易可以將地址和存儲槽添加到其訪問列表中,但永遠不會觸及它們。
例如,事務0x0dd0c設置了訪問列表,但由於地址原因從未訪問指定的插槽。

原因
該協議收取包含費用以簡化執行,無論條目是否被使用。如果你相信你的用戶能夠提供正確的輸入,那你還不如相信泰勒·斯威夫特是你的妻子。

4. 自轉移仍需轉移氣體

例子
賬戶 A 將ETH發送給自己。
餘額沒有變化,但費用仍然包含價值轉移所需的9,000 gasG_{ callvalue } G callvalue 根據@vbuterin這篇帖子

兩次賬戶寫入(一次餘額編輯 CALL 通常需要花費 9000 gas)

為什麼一個賬戶寫入操作仍然需要花費9000 gas呢?其實,如果你看過執行客戶端的源碼,就會發現,當 from 地址和 to 地址相同時,客戶端不會做任何操作。

當交易為自我轉賬或使用CALLCODE轉移價值時,可能會發生上述情況。

原因
無論轉移是否為無操作,都會觸發執行費用。

5. 調用數據與合約字節碼磁盤定價不匹配

例子

  • Tx calldata: 16 gas/字節(非零)4 gas/字節(零)
  • 合約字節碼: 200 gas/字節
    兩者都佔用磁盤空間,但定價卻不一致。這讓我很困惑,因為交易調用數據比合約字節碼更便宜,因為它應該考慮實際的磁盤使用量和網絡開銷。

原因
Gas 計劃將“調用數據”和“代碼存放”分開,而不將它們與實際磁盤使用情況對齊。

6. 撤銷的交易將按寫入磁盤的方式收費

例子
恢復的交易會修改內存中的狀態,但不會保留任何更改,但仍會收取寫入費用,以下 gas 費用會受到影響:

  • 25,000 gas 賬戶 G_ { newaccount } G newaccount
  • 9,000 gas 價值轉移 G_ { callvalue } G callvalue
  • 2,100 gas (冷 G_ { coldslot } G
  • 200 gas 代碼存款 G_ { codedeposit } G代碼存款

實際內存成本約為100 gas

原因
執行期間會收取 Gas 費用;之後的回滾會取消狀態更改,但不會取消費用。具體實現會謹慎收費,以防止 DoS 攻擊。

7. 單筆交易中的多筆ETH轉賬被誤認為是冷轉賬

例子
假設合約在單筆交易中多次將ETH發送到不同的賬戶。

  • 第一次轉賬正確產生了G_{callvalue} G call value 9,000 gas 寫入賬戶餘額
  • 同一交易中後續向其他賬戶的轉賬應收取熱訪問費( 100 gas + 4,500 gas ),但有時仍按冷訪問費計費( 9,000 gas )。

原因
對於單筆交易中的多次價值轉移,冷/暖訪問簿記並不會持續更新。

8. 礦工/驗證者獎勵或提現寫入不收取費用

例子
協議級餘額更新(例如獎勵、提款)會修改磁盤上的狀態,但消耗0 gas

原因
系統級簿記繞過了 gas 會計掛鉤。

9. SSTORE 首次磁盤讀取不收費(根據 EIP-2200)

例子
執行SSTORE操作碼時,它首先從磁盤(合約存儲)讀取當前值,然後再決定是否寫入新值。根據EIP-2200規定,如果存儲的值與現有值匹配,則不會進行磁盤寫入,並且僅收取少量 Gas 費用。但是,初始磁盤讀取本身不收取任何 Gas 費用——協議僅在值發生變化時對後續寫入收取費用。

原因
EIP-2200 的邏輯側重於對狀態變化收費,但忽略了對總是首先發生的磁盤讀取的收費。這意味著對存儲槽的首次訪問是免費的,即使是冷讀。

10. 存儲讀取優化減少了 I/O,但 gas 保持不變

例子
以太坊客戶端已採用扁平存儲/快照優化(例如,Geth 的 快照加速結構),將狀態組織為扁平鍵值存儲,並允許直接磁盤讀取,從而繞過了傳統 Merkle-Patricia Trie (MPT) 所需的中間節點。此優化顯著減少了冷存儲讀取的磁盤 I/O。例如,Geth 和其他客戶端現在使用 SAS 或類似結構,但冷訪問的 Gas 費用(2,600 / 2,100 / 2,400 / 1,900)保持不變。

原因
冷訪問的 Gas 常數最初是針對 MPT 進行校準的,因為 MPT 需要遍歷多個 trie 節點,因此磁盤讀取成本更高。使用 SAS 後,實際磁盤資源消耗要低得多,但協議尚未更新相應的 Gas 費用。

減輕
當客戶端切換到 SAS 或類似的優化存儲後端時,重新校準氣體常數以反映減少的磁盤 I/O。

11. SLOAD 與 MLOAD 定價不匹配

例子

  • SLOAD (溫暖)→ 100 氣體
  • MLOAD3 氣體
    兩者都是內存讀取,但是價格差別很大。

原因
狀態和內存操作之間的遺留區別;優化模糊了實際成本差距。

12. 內部交易有時無需 Gas 即可更新賬戶

例子
當磁盤中的賬戶更新不收取 Gas 費用時。具體來說,這個問題出現在用戶向合約 A 發送外部交易,合約 A 又對合約 B 進行內部調用的場景中。如果合約 B 修改了其存儲中的某個 slot,則必須在磁盤上更新合約 B 賬戶中對應的存儲根。然而,由於賬戶 B 的此次更新不收取 Gas 費用,導致不一致。

原因
出現此漏洞的原因是,合約 B 的存儲 trie 修改不會因更新其賬戶狀態而產生額外的 gas 費用。這是因為即使執行了磁盤寫入操作,協議也不會對由內部交易觸發的賬戶狀態更新收取費用。

13. EXT* 操作碼定價太粗略

例子
EXTCODESIZE可能讀取比BALANCE更多數據,但兩者都收取相同的冷賬戶費用( 2,600 gas )。

原因
操作碼定價桶很粗略,並且忽略了可變的工作。

結束語

這個問題來自我的論文如下,我通過此鏈接分享它。

何哲、李哲、羅建、羅鋒、段建、李建…… & 張曉燕 (2025年2月)。Auspex:揭示區塊鏈交易費機制的不一致性漏洞。刊於第23屆USENIX文件與存儲技術會議論文集。

如果您能引用我的論文,我將非常高興。

所有這些都凸顯了全面審查和調整以太坊協議內 Gas 定價機制的必要性。通過解決這些不一致問題,我們可以確保一個更高效、更公平的 Gas 市場,準確反映各種操作的底層資源成本。


來源
免責聲明:以上內容僅為作者觀點,不代表Followin的任何立場,不構成與Followin相關的任何投資建議。
喜歡
收藏
評論