數千份以太坊 Token 合約不兼容問題浮出水面,嚴重影響 DAPP 生態

如今以太坊上已經部署了超過 7萬個ERC20 Token智能合約,基於 ERC20 合約的 DAPP 正在迅速發展,包括去中心化交易所、資產託管、錢包等應用。但是 安比(SECBIT)實驗室 不斷髮現有大量 ERC20 Token 合約沒有遵守 EIP20 規範,這些非標準合約將會對 DAPP 的生態造成嚴重影響。特別是自從今年4月17日,以太坊的智能合約語言編譯器 Solidity 升級至 0.4.22 版本後,編譯產生的合約代碼將會無法兼容一些非標準的智能合約,這會對 DAPP 的開發帶來很大的困擾。這個問題首當其衝地影響去中心化交易所(DEX),一些非標準 Token

可能無法正常完成交易/轉賬,另外一些針對 ERC20 Token 的 DAPP 也會受到影響。根據 安比(SECBIT)實驗室不完全統計,存在這種不兼容問題的 ERC20 合約至少 2603份。但時至今日,這個問題並沒有引起各方足夠的重視。

5月10日,以太坊 Solidity 社區收到以下一個Issue(Enforcing ABI length checks for return data from calls can be breaking)提到了編譯器升級後對非標準 Token 合約的影響。
這裏寫圖片描述
問題解決方案直達:https://github.com/sec-bit/badERC20Fix/

問題描述

根據 Issue 中的描述信息可知,該問題是由ERC20 Token合約中 transfer() 函數未嚴格按照EIP20規範的實現所引起的。在以太坊官方的 EIP20 Token合約規範文檔中,對各個接口和 Event 的實現都明確的做了規定。
這裏寫圖片描述
每個函數都應包含返回值,其中transfer()函數應返回一個bool值。但是大量實際部署的Token合約,並沒有嚴格按照 EIP20 規範來實現,如下列一段知名Token合約(市值63Billion USD)的代碼所示,transfer函數沒有返回值。
這裏寫圖片描述
Token合約中,沒有返回值的transfer()函數,並不符合EIP20合約規範。對於普通賬戶直接調用transfer()函數進行轉賬的場景不會有任何影響。但若外部合約按照EIP20規範的ABI解析去調用transfer()函數,在solidity編譯器升級至0.4.22版本以前,合約調用也不會出現異常。但當合約升級至0.4.22後,transfer函數調用將發生revert。
這裏寫圖片描述
這種不符合EIP規範的寫法,爲什麼在之前的版本上可以正常調用,當編譯器升級到0.4.22版本後就無法兼容了呢?

在solidity中,函數的調用通過 signature 調用合約查找對應的函數,而 signature 是通過函數名和參數類型哈希得到的,與返回值無關。因此不管是否有返回值的同名相同參數類型的函數,其 signature 都是相同的。
這裏寫圖片描述
call(g, a, v, in, insize, out, outsize)函數中,in和out分別爲函數的輸入、輸出地址,由於用戶僅需爲首次使用的內存支付一次gas,所以通常將in和out設爲同一個地址,即函數的輸入輸出值在內存中保存在同一位置。在有返回值的情況下,函數執行完成後,函數輸出值將會覆蓋掉輸入值。如果嘗試讀取一個沒有返回值的函數,輸入值將不會被修改,那麼會讀取到的即輸入的內容。

在0.4.22之前的版本中,當外部合約調用沒有返回值的 transfer()函數,外部合約還是會在內存中查詢函數的返回值。由於沒有真正的返回值,外部調用合約返回值本應在內存中存儲的對應的位置查找,將查到的數據作爲返回值。實際上,查詢到的返回值並不是 transfer()函數的返回值,通常是一個大於0的值,外部調用合約將其視爲true。這個假的返回值並不會影響外部合約對 transfer()函數的調用。於是就造成了即使沒有返回值,外部合約調用transfer函數時,也不會有問題,這就自然而然的掩蓋了這個問題。

以太坊在2017年10月份的拜占庭硬分叉中,採用了一系列的EIP修改提案,其中包括在EVM層面加入revert和RETURNDATASIZE等指令操作符。而在solidity編譯器0.4.22版本中,正式引入了對 RETURNDATASIZE 的支持。這個操作符的作用是得到被調用合約函數返回值的大小。

因此,在Solidity 0.4.22 版本編譯出的代碼在調用外部合約時,將會對函數返回值進行校驗,若返回值的長度小於RETURNDATASIZE存儲的長度,函數將無法正常調用,進而引發revert。這個操作符使得外部合約調用操作更爲安全,但也因此導致上文所述的無返回值 transfer()函數無法被正常調用。

這個不兼容性問題意味着什麼?

智能合約語言 Solidity 編譯器升級之後,未來會造成大量的 ERC20 Token 合約與 DAPP (使用 0.4.22 以上的Solidity編譯器編譯)產生不兼容的問題。這個不兼容性不僅限於
transfer() 函數,還有 transferFrom()函數與 approve()函數。

Solidity 團隊的核心開發者 Christian Reitwiessner 認爲這個編譯器升級非常有必要,升級雖然造成了一些Token兼容性問題的暴露,但是這個升級是正確的做法。因爲根據 EVM 的內存佈局,函數調用數據(call data)與函數返回數據(return data)是共同使用一塊內存區域,如果 transfer() 函數沒有調用 RETURN 指令返回任何值,那麼如果調用者去用 RETURNDATACOPY 來取返回值的時候,會將內存中的髒數據取回。髒數據的值很大概率會爲 非零值,這在EVM裏面代表 “true”。但是這裏請注意,髒數據也很有可能是 零,代表 “false”。這就意味着:即使token合約正常完成了轉賬,但是卻返回 “false”,導致外部的 DAPP 誤認爲 轉賬沒有成功,進而可能引發安全漏洞。

社區中也有人提到這一點:
這裏寫圖片描述
假設1: 如果transfer()函數沒有返回值,那麼當調用者合約去取 RETURNDATA 的時候,通常拿到的髒數據是 function signature的值,即該值是transfer()函數的signature的 SHA-3 哈希(”0xa9059cbb”),也就是說,通常情況下,這個值爲非零值,也就是相當於 return true。

如果上述假設成立,那麼這個不兼容性問題至少不會導致 DAPP 邏輯上的混亂,未來不會產生沒有安全漏洞。

但是很多人也指出,依賴這種假設是非常危險的,會埋下安全隱患。

那麼有讀者會問,EIP20規範中並沒有提及 transfer() 函數的返回值爲false的含義,

並且 EIP20 規範明確說了如果轉賬不成功,應該直接revert。如下圖所示:
這裏寫圖片描述
但是實際部署的合約中存在着一大批的新老合約不符合EIP20規範。這些合約在某些情況下會返回 “false”,以表示轉賬未成功。但是我們同時看到,EIP20規範對返回值語焉不詳。

這對於 DAPP (solidity > 0.4.22)而言,將面臨着三種不同的 Token 合約轉賬語義:

  1. 合約的transfer() 函數在轉賬未成功時,返回false
  2. 合約的transfer() 函數在轉賬未成功時,執行revert
  3. 合約的transfer() 函數在轉賬能夠成功,但是由於缺少返回值而導致合約調用 revert,導致轉賬失敗

非標準的合約會給 DAPP 開發帶來非常大的困擾。可以預見,隨着很多DAPP的升級,會有越來越多的 ERC20 Token API調用會失敗(transfer()函數會因爲EVM執行revert而失敗結束)。不兼容的 Token 合約數量現在已經達到 2603個,市值TOP100 的 Token列表中,不兼容的 Token 數量爲11個。

爲何有如此大量的合約不滿足規範?

安比(SECBIT)實驗室通過深入分析問題合約發現,其中大量的問題合約都是參考了一些權威的模板實現的,但是這些權威模板也有不兼容性問題。其中業界頗具口碑的 openzeppelin-solidity 早期的合約中,ERC20Basic合約中 transfer()函數一直無返回值,在52120a8c42(2017年3月21日) 版本中,將 StandardToken 合約也改爲了無返回值,直到在6331dd125d(2017年7月13日)中才將所有合約的transfer()函數修正。因此參考這個階段(2017年3月 – 2017年7月)間參考 openzeppelin 合約實現的 Token合約均有可能存在該問題。

另外以太坊官網提供的 Token 合約模板也屬於問題合約模板:
這裏寫圖片描述
下面是受權威模板影響的 Token 合約數量統計:

  1. 參考以太坊官方網站提供的問題合約模板(https://ethereum.org/token),已知有1703份。
  2. 參考Open-Zeppelin的問題合約,已知有 990份。

我們如何應對?

  1. Token方重新發布合約
  2. DAPP 開發者需要通過一個安全的調用代碼來訪問各種不兼容 ERC20 的Token合約
  3. 以太坊硬分叉升級

我們認爲第一種方案能徹底解決這一不兼容性問題帶來的安全風險,但是如此大量的 Token 合約重新發行的代價是極高的。第二種方案需要 DAPP 開發團隊修改代碼,以應對各種不兼容性情況,這對衆多的 DAPP 開發團隊是一個不小的負擔。第三種方案可以解決 假設1,解除所有 Token 的安全隱患,但是硬分叉方案需要深入的分析討論,近期難以得到妥善解決。

針對方案二,DAPP 可以採用下面的代碼片段來作爲調用 ERC20合約 transfer() 的中間層代碼:

這裏寫圖片描述
這段代碼使用 call 方法手動直接調用 transfer()函數,並使用內聯 assembly code 手動獲取 returndatasize() 進行判斷。如果爲 0,則表明被調用 ERC20 合約正常執行完畢,但沒有返回值,即轉賬成功;如果爲 32,則表明ERC20合約符合標準,直接進行 returndatacopy() 操作,調用 mload() 拿到返回值進行判斷即可;如果爲其他值則 revert。封裝成函數替代 transfer 使用。

完整的中間層代碼也需要支持 transferFrom() 有 approve() 函數,完整代碼請參考 SECBIT 實驗室github倉庫https://github.com/sec-bit/badERC20Fix/

總結

這個不兼容性問題引入的根源是非常複雜而且涉及多方面的,包括以太坊智能合約虛擬機 EVM 架構、智能合約語言 Solidity 編譯器,Token 合約 EIP20規範,ERC20合約代碼模板。任何一方都難以對問題有充分全面的瞭解,一個月之前 Solidity社區中有人提出了這個問題,隨後 Christian Reitwiessner 在博客中描述了這個問題,Lukas Cremer 在博客中提出了幾個解決方案。Brendan Chou 在 github 上最早提出了一個解決方案

但是將近一個月之後,大多數的 DAPP 開發團隊對此瞭解甚少,也缺乏對該問題的安全警惕意識。

SECBIT實驗室再次呼籲,以太坊社區應加強溝通和技術推廣。同時強烈建議項目發行方在項目開始之前諮詢專業的智能合約技術團隊,杜絕後患。

後續

SECBIT 實驗室團隊在發現該問題後,立即與幾個知名去中心化交易所(DEX.top, loopring, ddex.io)取得聯繫,這些團隊在第一時間確認問題並更新了代碼,並反覆確認了已部署運行的合約不影響交易所功能。團隊也向幾個知名的 Token 發行項目方報告了這一情況,均得到了及時反饋。同時團隊在第一時間給以太坊官網提交了若干補丁修復 Token 問題合約模板,也迅速得到了迴應。SECBIT 實驗室感謝社區的積極應對,也希望更多的 Token發行方與 DAPP 開發團隊能意識到這個兼容性帶來的問題,確保 DAPP 生態的健康發展。

參考文獻

[1] https://medium.com/@chris_77367/explaining-unexpected-reverts-starting-with-solidity-0-4-22-3ada6e82308c Explaining unexpected reverts starting with Solidity 0.4.22

[2] https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca Missing return value bug — At least 130 tokens affected

[3] https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md eip-20

[4] https://github.com/ethereum/ethereum-org/blob/master/solidity/token-advanced.sol#L85 token advanced

[5] https://github.com/OpenZeppelin/openzeppelin-solidity openzeppelin-solidity

[6] https://github.com/ethereum/solidity/issues/4116 Enforcing ABI length checks for return data from calls can be breaking

[7] https://solidity.readthedocs.io/en/v0.4.22/assembly.html#returndatasize returndatasize

[8] https://gist.github.com/BrendanChou/88a2eeb80947ff00bcf58ffdafeaeb61 Brendan’s implementation of ERC20SafeTransfer

以上數據均有SECBIT實驗室提供。合作交流請聯繫 [email protected]


SECBIT(安比)實驗室是誰?

SECBIT(安比)實驗室專注於智能合約安全問題,全方位監控智能合約安全漏洞、提供專業合約安全審計服務,在智能合約安全技術上開展深入研究,致力於參與共建共識、可信、有序的區塊鏈經濟體。

SECBIT(安比)實驗室創始人郭宇,中國科學技術大學博士、耶魯大學訪問學者、畢業後在中科大執教九年,任副教授,後擔任知名金融科技公司副總裁。專注於形式化證明與系統軟件研究領域十餘年,並在金融安全行業具有豐富的產品研發經驗,是國內早期關注並參與比特幣與區塊鏈技術的科研人員之一,二十餘項區塊鏈專利核心發明人。研究專長:區塊鏈技術、形式化驗證、程序語言理論、操作系統內核。

發佈了42 篇原創文章 · 獲贊 8 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章