區塊鏈研究實驗室|停止使用Solidity的transfer()函數

原文鏈接:https://mp.weixin.qq.com/s/gz5CWMo9L2Q3EKOW-V41PQ

以太坊看起來EIP 1884正在伊斯坦布爾硬叉前進。這一變化增加了sload操作的gas成本,因此打破了一些現有的智能合約。

這些合約將破裂,因爲它們的fallback函數過去消耗的gas少於2300,現在它們將消耗更多。爲什麼2300gas是重要的? 如果通過Solidity的transfer()或send()方法調用合約的fallback函數,則它是合約的fallback函數。

自從transfer()引入以來,它通常被安全社區推薦,因爲它有助於防止重入攻擊。在gas成本不變的假設下,這一指導意見是有意義的,但事實證明這一假設是錯誤的。我們現在建議避免transfer()和send()。

氣體成本可以也將改變

evm支持的每個操作碼都有一個相關的gas成本。例如,sload從存儲器中讀取一個單詞,目前(但不是很長時間)需要200氣體。gas的價格不是隨意的。它們旨在反映構成以太坊的節點上每個操作所消耗的底層資源。

EIP的動機部分:

操作價格與資源消耗(CPU時間、內存等)之間的不平衡有幾個缺點:

  • 它可以用於攻擊,通過填充區塊與定價過低的操作,導致區塊處理時間過長。

  • 定價過低的操作碼會導致區塊gas限值傾斜,有時區塊氣體快速完成,但其他類似gas使用的塊體緩慢完成。

  • 如果操作平衡,我們可以最大限度地提高區塊氣限,並有一個更穩定的處理時間。

sload歷來定價過低,而eip 1884糾正了這一點。

智能合約不能依賴氣體成本

如果gas成本會發生變化,那麼智能合約就不能依賴於任何特定的gas成本。

任何使用transfer()或send()的智能合約都會通過轉發固定gas數量2300來嚴格依賴gas成本。

我們的建議是停止在代碼中使用transfer()和send(),改爲使用call():

這兩份智能合約除了gas的傳輸數量不同,其餘都是相等的。

可重入性?(Reentrancy )

希望您在看到上述代碼時首先考慮過這個問題。引入transfer()和send()的全部原因是爲了解決DAO臭名昭着的黑客攻擊的原因。 其思想是2300 gas足以發出一個日誌條目,但不足以發出一個可重入調用,然後修改存儲。

不過,請記住,gas成本可能會發生變化,這意味着無論如何,這是解決重入問題的糟糕方法。今年早些時候,君士坦丁堡叉子被推遲了,因爲降低gas成本導致先前安全的代碼無法再進入。

如果我們不再使用transfer()和send(),我們將不得不以更健壯的方式防止重入。幸運的是,這個問題有很好的解決方案。

檢查 - 效果 - 交互模式

消除重入錯誤的最簡單方法是使用檢查-效果-交互模式。這是一個可重入錯誤的典型例子:

 

如果msg.sender是智能合約,它在第6行有機會在第7行發生之前再次調用withdraw()。在第二次調用中,balanceOf [msg.sender]仍然是原始金額,因此將再次傳輸。這可以根據需要重複多次以消耗智能合約。

檢查 - 效果 - 交互模式的想法是確保所有交互(外部調用)最終發生。上述代碼的典型修復方法如下:

請注意,在此代碼中,餘額在傳輸之前被清零,因此嘗試對withdraw()進行可重入調用將不會使攻擊者受益。

使用Reentrancy 保護

防止重入的另一種方法是明確檢查和拒絕此類調用。這是一個簡單版本的reentrancy 保護,你可以看到這個想法:

使用此代碼,如果嘗試重入調用,第7行上的require將拒絕它,因爲lock仍設置爲true。

在OpenZeppelin的ReentrancyGuard合同中可以找到一個更復雜、更省gas的版本。如果從ReentrancyGuard繼承,則只需使用nonReentrant修飾函數以防止重入。

請注意,此方法僅在顯式將其應用於所有正確的函數時才保護您。由於需要在儲存中保持一定的價值,這也增加了gas成本。

Vyper如何?

Vyper的send()函數使用與Solidity的transfer()相同的硬編碼gas,因此也應避免使用。你可以改用raw_call。

Vyper內置了一個@nonreentrant(<unique_key>)裝飾器,其工作方式與OpenZeppelin的ReentrancyGuard類似。

總結

  1. 在假定gas成本不變的情況下,推薦transfer()是有意義的。

  2. gas成本並不恆定。智能合同對這個事實應該是健全的。Solidity的transfer()和send()使用硬編碼的gas量。

  3. 應該避免這些方法。請改用.call.value(...)(“”)。

  4. 這就帶來了重新進入的風險。請確保使用可用於防止重入漏洞的健壯方法之一。

  5. vyper的send()也有同樣的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章