開發以太坊DApp,Solidity是必經之路。然而,對於跟我同樣的那些有多年開發經驗的以太坊新人來說,Solidity學習當然是一方面,但更重要的是快速瞭解它的慣例和套路,以及一些值得注意的事項。這,正是本文試圖達到的目標。至於詳盡的語法文檔,參考HiBlock區塊鏈社區翻譯的solidity中文文檔(https://solidity-cn.readthedocs.io/zh/develop/)。git
與Java代碼相似,Solidity代碼會先被編譯成字節碼,而後再由EVM負責執行。從邏輯上來說,能夠將以太坊視爲一臺計算機,其中的每一個EVM節點相似在計算機中執行的進程,分佈式帳本則是這臺計算機的存儲。github
一旦部署成功,其代碼會被複制到以太坊上其餘節點,並能夠經過命令查看其源碼。以Truffle開發環境爲例:web
truffle developjson
部署MyCoin合約,deploy數組
MyCoin.at(地址),從其返回的json對象中的source屬性便可看到合約代碼。安全
合約部署以後就沒法更新,這就給開發者帶來了至關大的挑戰:微信
如何開發出高質量的合約,儘量的沒有Bug?app
如何設計可升級的合約?分佈式
說到程序執行的代價,通常指的都是花多少內存、存儲和CPU時間。但執行以太坊上的代碼,除了這些一般意義的代價以外,還須要真金白銀。這是由於以太坊上的交易確認都是須要花錢的!它們主要是那些改變以太坊狀態的操做,如:函數
帳戶轉帳
部署合約
合約內的寫操做
並且,與其餘系統不一樣,這些操做的執行結果並不會馬上生效。它們會以交易的形式提交到交易池中等待礦工確認,這即是交易費的由來。而且,這個價格也不是一個固定值,它隨着市場行情的波動上下浮動。若是你的交易長時間沒有結果,那麼能夠看看是不是由於交易費太低。
關於交易費的行情,能夠從最新的交易(https://etherscan.io/txs)中瞭解。
這也給開發者帶來了挑戰:如何在實現功能的前提下儘量的下降交易成本?
要在以太坊上進行操做,必須要有以太坊帳戶。當前有兩類帳戶類型:
外部帳戶,可簡單認爲是「人類用戶」,有私鑰和餘額,交易發送前會用私鑰先簽名。
合約帳戶,合約部署以後,會隨之對應有一個帳戶,由余額和相應的合約狀態數據。它由外部消息來觸發執行。觸發源來自外部帳戶或其餘合約帳戶。
這裏也帶來了一些關於安全性方面的概念轉變:
私鑰是終極祕密,必定是本地存儲,不然都是不安全的。
因爲合約是公開的,誰均可以發起執行。若是要實現「只有xxx才能執行本合約」,必須要在合約內部代碼中進行控制。
合約相似Java中的類,但與類不一樣之處在於,它的構造函數只會被調用一次,即部署合約的時候。
合約的狀態變量至關於類的實例變量,但一樣是持久化的。而且,mapping只能聲明成狀態變量但可在函數內引用。
變量類型一樣也分值類型和引用類型,其中引用類型包括:數組和結構體,後者給自定義類型提供方案。
函數能夠返回一個值或多個值,同時能夠指定返回的變量。如:
function arithmetic(uint _a, uint _b) public pure returns (uint o_sum, uint o_product) { o_sum = _a + _b; o_product = _a * _b; }
函數修改器(Modifier)相似AOP中的攔截器,提供了修改函數執行流程的機會,通常用來作驗證和檢查。其中「_」用來將控制流返還給被修改的函數,以下例:
modifier onlySeller() { // Modifier require( msg.sender == seller, "Only seller can call this." ); _;} function abort() public onlySeller { // Modifier usage // ... }
幾個重要的修改器:
payable,接收以太的函數必需加上
view或pure,表示函數不會改變以太坊狀態
事件提供了讓外部應用瞭解合約狀態變化的途徑,通常使用流程是:
合約內部發出事件
外部應用利用web3監聽事件
可見性:
external,僅適用於函數,表示其可被外部合約或交易調用,但不能被內部調用。
public
函數缺省的可見性,可被內部調用和經過消息調用,
狀態變量,EVM會爲其自動產生getter
internal,函數和狀態變量可被當前合約和其子合約調用
狀態變量的缺省可見性
private,函數和狀態變量僅被當前合約調用
合約支持多重繼承。
EVM提供了4種數據位置用來存放數據:
storage,持久化,存儲於整個以太坊
memory,函數的本地內存,非持久化
calldata,函數入參,非持久化
stack,EVM的調用棧
規則:
狀態變量:storage
external函數入參:calldata
函數入參:memory
函數局部變量:
引用類型,缺省爲storage,但可被覆蓋
值類型,memory,不可被覆蓋
mapping類型,指向外部的狀態變量
狀態變量之間賦值,將產生獨立副本,即相互更改不受引用。
storage和memory變量之間相互賦值,總產生獨立副本。
memory變量之間賦值
值類型,產生獨立副本
引用類型,指向同一地址
因爲合約執行是有成本的,須要警戒循環語句。
對於多重繼承的合約,須要明確指明順序,如:
contract X {}contract A is X {}contract C is A, X {}
fallback函數沒有函數名,沒法直接調用,但在兩個狀況下會被觸發:
合約中無任何函數匹配調用者發過來的請求時
合約接收以太時,此時,fallback函數需使用payable
因爲其沒法被外部調用,EVM限制其只能最多消耗2300的gas,若超過,則fallback函數失敗。所以,記得要測試合約的fallback函數是否會超過這個限制。
而且,fallback是安全事故的高發地,須要對其進行必要的安全相關的測試。
接口和抽象合約跟Java中的接口和抽象類差異不大,庫(library)是一段可複用的代碼,在調用它的合約上下文內執行:
它不能用狀態變量
不能繼承或被繼承
不能接收以太
合約拋出異常以後,狀態回滾,當前有3種方式:
require(表達式),若表達式爲false,則拋出異常,未使用的gas退回
適合驗證函數的入參
assert(表達式),同上,但未使用的gas不會退回,將所有被消耗
適合驗證內部狀態
revert(),直接拋出異常
鑑於以太坊應用的如下特色,編寫solidity代碼時須要很是當心:
執行消耗真金白銀
合約公開可見,即便是private
合約不可篡改,一旦發佈沒法變動
常見的編碼套路有:
對於支付,優先採用「取款」,而不是「轉帳」(即send或transfer),避免接收合約惡意fallback函數。
對於支付,採用CDI模式,避免重入問題。即:
檢查 -> 更改本合約狀態 ->支付。
善用Modifier進行權限控制。
使用mapping類型保存合約數據,甚至爲了方便升級,單獨分離出兩類:
數據合約,僅包含mapping,保留操做mapping的函數,客觀上相似數據表。
控制合約,僅包含邏輯控制,經過數據合約的接口操做數據。若邏輯有問題,只需升級本合約便可,數據仍然得以保留。
使用代理合約,參見這裏(https://blog.zeppelin.solutions/proxy-libraries-in-solidity-79fbe4b970fd)。
最後,也是最省事的方式:使用成熟類庫,如OpenZeppelin。關於Solidity的好東西,能夠經過其Awesome List(https://github.com/bkrem/awesome-solidity)來了解。
本文做者:HiBlock區塊鏈技術佈道羣-胡鍵
原文發佈於簡書
原文連接:
https://www.jianshu.com/p/ec5ad71e28aa
加微信baobaotalk_com,加入技術佈道羣
線上課程推薦