EOS智能合約安全終極指南。當世界上最大的ICO,EOS於2018年6月推出時,加密社區變得持懷疑態度,而且因爲軟件錯誤而被凍結了2天。但快進4個月,EOS今天佔了以太網今天所作交易的兩倍以上。經過免費和更快速交易的承諾,EOS最頂級的Dapp擁有大約13,000個每日活躍用戶,而以太網的最頂級Dapp只有2,000個。php
一些常見的智能合約漏洞幾乎適用於全部平臺。與以太坊同樣,在EOS上編寫的智能合約須要在主網上上線以前進行審覈。合約中的致命錯誤能夠在合約沒有通過足夠的測試時被利用。在本指南中,咱們將幫助你避免在EOS上製做下一個殺手dApp的過程當中常見的陷阱。java
在閱讀本指南以前,瞭解有關EOS開發的一些先決條件信息很是重要,這些信息在你閱讀本指南時會很方便。瞭解C++是必須的。開始智能合約開發的最佳位置是EOSIO本身的文檔。node
extern "C" { void apply(uint64_t receiver, uint64_t code, uint64_t action) { class_name thiscontract(receiver); if ((code == N(eosio.token)) && (action == N(transfer))) { execute_action(&thiscontract, &class_name::transfer); return; } if (code != receiver) return; switch (action) { EOSIO_API(class_name, (action_1)(action_n))}; eosio_exit(0); } }
上面是修改後的ABI調用程序的示例代碼。以下所示的更簡單的ABI調用程序用於簡化合約的操做處理。python
EOSIO_ABI( class_name, (action_1)(action_n) );
ABI調用程序/交易處理程序容許合約收聽傳入的eosio.token
交易時間,以及與智能合約的正常交互。爲了不異常和非法調用,綁定每一個鍵操做和代碼以知足要求是很重要的。android
一個例子是因爲他們的ABI轉發源代碼中的錯誤而發生在dApp EOSBet Casino上的黑客攻擊 。程序員
if( code == self || code == N(eosio.token) ) { TYPE thiscontract( self ); switch( action ) { EOSIO_API( TYPE, MEMBERS ) } }
上面檢查ABI轉發源代碼的apply動做處理程序容許攻擊者徹底繞過eosio.token::transfer()
函數,並在放置以前直接調用contract::transfer()
函數而不將EOS轉移到合約中。打賭。對於損失,他沒有獲得任何報酬,但一無所得。然而,對於勝利,他從合約中支付了真正的EOS。web
他們經過在傳入操做請求合約以前添加eosio.token
合約轉移操做檢查來修復上述錯誤。mongodb
if( code == self || code == N(eosio.token) ) { if( action == N(transfer) ){ eosio_assert( code == N(eosio.token), "Must transfer EOS"); } TYPE thiscontract( self ); switch( action ) { EOSIO_API( TYPE, MEMBERS ) } }
使用語句require_auth(account)
很是重要;進入只須要受權賬戶才能執行的操做。require_auth(_self)
;用於僅受權合約的全部者簽署交易。數據庫
void token::transfer( account_name from, account_name to, asset quantity) { auto sym = quantity.symbol.name(); require_recipient( from ); require_recipient( to ); auto payer = has_auth( to ) ? to : from; sub_balance( from, quantity ); add_balance( to, quantity, payer ); }
上面的示例代碼容許任何人調用該操做。要解決它,請使用require_auth(from)
,聲明受權付款人調用該行動。編程
最近一位白帽黑客因其eosio.token合約中的方法調用問題而設法得到了10億美圓的dapp代幣。Dapp Se7ens(如今處於非活動狀態)在eosio.token合約中聲明瞭一種新方法,用於將其代幣空投到用戶賬戶中。合約沒有反映eosio.token
合約的問題或轉移操做的變化,所以資金神奇地出如今用戶的賬戶上。其次,他們忘記在轉移以前驗證方法中的金額,這容許黑客在此過程當中索取10億個代幣。
除了更改最大供應和代幣符號以外,建議避免爲自定義函數修改它,由於eosio.token合約中的錯誤多是致命的。爲了便於安全地進行空投,將空投代幣轉移到一個單獨的賬戶並從那裏分發。
EOS當前將數據存儲在共享內存數據庫中,以便跨操做共享。
struct [[eosio::table]] person { account_name key; std::string first_name; std::string last_name; std::string street; std::string city; std::string state; uint64_t primary_key() const { return key; } }; typedef eosio::multi_index<N(people), person> address_index;
上面的示例代碼建立了一個名爲people
的multi_index
表 ,該表基於使用struct person
的該表的單行的數據結構。部署後,EOS目前不容許修改表屬性。eosio_assert_message
斷言失敗將是將被拋出的錯誤。所以,在部署表以前須要徹底考慮屬性。不然,須要建立具備不一樣名稱的新表,而且在從舊錶遷移到新表時須要特別當心。若是不這樣作可能會致使數據丟失。
在進行算術運算時,若是沒有足夠負責地檢查邊界條件,則值可能會溢出,從而致使用戶資產丟失。
void transfer(symbol_name symbol, account_name from, account_names to, uint64_t balance) { require_auth(from); account fromaccount; eosio_assert(is_balance_within_range(balance), "invalid balance"); eosio_assert(balance > 0, "must transfer positive balance"); uint64_t amount = balance * 4; //Multiplication overflow }
在上面的示例代碼中,使用uint64_t
表示用戶餘額可能會在值乘以時致使溢出。所以,儘可能避免使用uint64_t
來表示餘額並對其執行算術運算。使用eosiolib
中定義 的asset結構進行操做,而不是處理溢出條件的精確餘額。
在執行合約時會有假設須要斷言。若是斷言失敗,使用eosio_assert
將事先處理條件並中止執行特定操做。舉個例子:
void assert_roll_under(const uint8_t& roll_under) { eosio_assert(roll_under >= 2 && roll_under <= 96, "roll under overflow, must be greater than 2 and less than 96"); }
上面的斷言語句假設roll_under
整數大於2且小於96.但若是不是,則拋出上述消息並中止執行。沒有發現像上面這樣的局部問題可能會成爲規則制定的全局災難。
若是沒有準確完成,在EOS區塊鏈上生成正確的隨機數仍然存在風險。若是沒有正確地作到這一點,將致使對手預測結果,在整個過程當中對整個系統進行遊戲。像Oracalize.it
這樣的服務能夠提供來自外部源的隨機數,但它們很昂貴而且可能出現單點故障。人們過去曾使用Blockchain的上下文變量(塊編號,塊時間戳等)來生成以太坊智能合約中的隨機數,這只是在遊戲中使用。爲了正確地進行生成,程序必須提供一種單一方沒法單獨控制的組合隨機性。目前最好的方法之一是Dan Larimar本身生成隨機數時建議的方法。
string sha256_to_hex(const checksum256& sha256) { return to_hex((char*)sha256.hash, sizeof(sha256.hash)); } string sha1_to_hex(const checksum160& sha1) { return to_hex((char*)sha1.hash, sizeof(sha1.hash)); } template <class T> Inline void hash_combine(std::size_t& seed, const T& v) { std::hash<T> hasher; seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); }
上面的示例代碼給出了1到100之間的優化隨機數生成.seed1
是種子,seed2
是上面的用戶種子。做爲參考,Dappub
和EOSBetCasino
開放了他們的完整合約,隨機數發生器實現了玩家和開發商之間的公平骰子游戲。
uint8_t compute_random_roll(const checksum256& seed1, const checksum160& seed2) { size_t hash = 0; hash_combine(hash, sha256_to_hex(seed1)); hash_combine(hash, sha1_to_hex(seed2)); return hash % 100 + 1; }
EOSBet最近再次遭到65,000EOS的攻擊,當一個對手欺騙他們的eosio.token
合約時,只要他在本身的錢包之間進行交易,就將EOS送到他的錢包。eosio.token
合約代碼 通知EOS代幣的發送者和接收者有傳入代幣。爲了模仿這種行爲並促進黑客攻擊,對手建立了兩個賬戶,讓咱們假設A和B。A與智能合約的操做有聲明require_recipient(N(eosbetdice11))
。當A經過操做調用促進了從A到B的交易時,它通知合約中的轉移功能,就好像該呼叫來自eosio.token
合約同樣。因爲EOS沒有真正轉移到合約中,每當黑客輸掉賭注時,他什麼都沒有丟失,可是在贏得賭注時他得到了獎勵。所以,僅檢查合約名稱和操做名稱是不夠的。
爲了緩解這個問題,該函數應檢查合約是否確實是代幣的接收者。
eosio_assert(transfer_data.from == _self || transfer_data.to == _self, "Must be incoming or outgoing transfer");
錯誤是任何軟件不可避免的一部分。它的後果在去中心化的環境中被放大,特別是若是它涉及價值交易。除了上面討論的EOS特定保護措施以外,如下是新智能合約開發人員應該記住的一些通常預防措施和最佳實踐:
爲了實現它,咱們在multi_index
表中保留一個標誌。咱們使用只能由合約全部者調用的操做來設置標誌。而後咱們檢查每一個公共行動是否將標誌設置爲凍結。下面給出了該函數的示例實現。
struct st_frozen { uint64_t frozen; }; typedef singleton<N(freeze), st_frozen> tb_frozen; tb_frozen _frozen; uint64_t getFreezeFlag() { st_frozen frozen_st{.frozen = 0}; return _frozen.get_or_create(_self, frozen_st); } void setFreezeFlag(const uint64_t& pFrozen) { st_frozen frozen_st = getFreezeFlag(); frozen_st.frozen = pFrozen; _frozen.set(frozen_st, _self); } // public Action void freeze() { require_auth(_self); setFreezeFlag(1); } // public Action void unfreeze() { require_auth(_self); setFreezeFlag(0); } // any public action void action(...) { eosio_assert(getFreezeFlag().frozen == 1, "Contract is frozen!"); ... }
隨時瞭解庫中的安全性加強或平臺上的漏洞披露。必要時當即更新庫。至少開源合約代碼,以便在項目中保持公平性,獨立開發人員能夠更快地發現錯誤。
自EOS推出僅僅5個月,它已經超出了預期。它所取得的DPOS,可變智能合約,21個採礦節點等,固然受到權力下放極端主義者的嚴厲批評。然而,考慮到平臺今天提供的可擴展性,它並無阻止基於以太坊的dApp轉向EOS。不管是EOS仍是以太坊贏得戰爭還有待肯定,但EOS確定贏得了這場戰鬥。它將保持不變,直到以太坊設法達到運行「世界計算機」所需的世界所需的可擴展性。
======================================================================
分享一些以太坊、EOS、比特幣等區塊鏈相關的交互式在線編程實戰教程:
- EOS教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、帳戶與錢包、發行代幣、智能合約開發與部署、使用代碼與智能合約交互等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
- java以太坊開發教程,主要是針對java和android程序員進行區塊鏈以太坊開發的web3j詳解。
- python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
- php以太坊,主要是介紹使用php進行智能合約開發交互,進行帳號建立、交易、轉帳、代幣開發以及過濾器和交易等內容。
- 以太坊入門教程,主要介紹智能合約與dapp應用開發,適合入門。
- 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
- C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括帳戶管理、狀態與交易、智能合約開發與交互、過濾器和交易等。
- java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Java代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
- php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Php代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
- tendermint區塊鏈開發詳解,本課程適合但願使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI接口、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操代碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。
匯智網原創翻譯,轉載請標明出處。這裏是原文EOS智能合約安全終極指南