您好,登錄后才能下訂單哦!
本篇內容介紹了“solidity語言開發智能合約怎么實現”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
在Solidity中,一個智能合約由一組代碼(合約的函數)和數據(合約的狀態)組成。智能合約位于以太坊區塊鏈上的一個特殊地址。uint storedData*;* 這行代碼聲明了一個狀態變量,變量名為storedData,類型為 uint (256bits無符號整數)。你可以認為它就像數據庫里面的一個存儲單元,跟管理數據庫一樣,可以通過調用函數查詢和修改它。在以太坊中,通常只有合約的擁有者才能這樣做。在這個例子中,函數 set 和 get 分別用于修改和查詢變量的值。
跟很多其他語言一樣,訪問狀態變量時,不需要在前面增加 this. 這樣的前綴。
這個合約還無法做很多事情(受限于以太坊的基礎設施),僅僅是允許任何人儲存一個數字。而且世界上任何一個人都可以來存取這個數字,缺少一個(可靠的)方式來保護你發布的數字。任何人都可以調用set方法設置一個不同的數字覆蓋你發布的數字。但是你的數字將會留存在區塊鏈的歷史上。稍后我們會學習如何增加一個存取限制,使得只有你才能修改這個數字。
先從一個非常基礎的例子開始,不用擔心你現在還一點都不了解,我們將逐步了解到更多的細節。
contract SimpleStorage { uint storedData; function set(uint x) { storedData = x; } function get() constant returns (uint retVal) { return storedData; } }
接下來的合約將實現一個形式最簡單的加密貨幣。空中取幣不再是一個魔術,當然只有創建合約的人才能做這件事情(想用其他貨幣發行模式也很簡單,只是實現細節上的差異)。而且任何人都可以發送貨幣給其他人,不需要注冊用戶名和密碼,只要有一對以太坊的公私鑰即可。
Note
對于在線solidity環境來說,這不是一個好的例子。如果你使用在線solidity環境 來嘗試這個例子。調用函數時,將無法改變from的地址。所以你只能扮演鑄幣者的角色,可以鑄造貨幣并發送給其他人,而無法扮演其他人的角色。這點在線solidity環境將來會做改進。
contract Coin { //關鍵字“public”使變量能從合約外部訪問。 address public minter; mapping (address => uint) public balances; //事件讓輕客戶端能高效的對變化做出反應。 event Sent(address from, address to, uint amount); //這個構造函數的代碼僅僅只在合約創建的時候被運行。 function Coin() { minter = msg.sender; } function mint(address receiver, uint amount) { if (msg.sender != minter) return; balances[receiver] += amount; } function send(address receiver, uint amount) { if (balances[msg.sender] < amount) return; balances[msg.sender] -= amount; balances[receiver] += amount; Sent(msg.sender, receiver, amount); } }
這個合約引入了一些新的概念,讓我們一個一個來看一下。
address public minter;
這行代碼聲明了一個可公開訪問的狀態變量,類型為address。address類型的值大小為160 bits,不支持任何算術操作。適用于存儲合約的地址或其他人的公私鑰。public關鍵字會自動為其修飾的狀態變量生成訪問函數。沒有public關鍵字的變量將無法被其他合約訪問。另外只有本合約內的代碼才能寫入。自動生成的函數如下:
function minter() returns (address) { return minter; }
當然我們自己增加一個這樣的訪問函數是行不通的。編譯器會報錯,指出這個函數與一個狀態變量重名。
下一行代碼 mapping (address => uint) public balances;
創建了一個public的狀態變量,但是其類型更加的復雜。該類型將一些address映射到無符號整數。mapping可以被認為是一個哈希表,每一個可能的key對應的value被虛擬的初始化為全0.這個類比不是很嚴謹,對于一個mapping,無法獲取一個包含其所有key或者value的鏈表。所以我們得自己記著添加了哪些東西到mapping中。更好的方式是維護一個這樣的鏈表,或者使用其他更高級的數據類型。或者只在不受這個缺陷影響的場景中使用mapping,就像這個例子。在這個例子中由public關鍵字生成的訪問函數將會更加復雜,其代碼大致如下:
function balances(address _account) returns (uint balance) { return balances[_account]; }
我們可以很方便的通過這個函數查詢某個特定賬號的余額。
event Sent(address from, address to, uint value);
這行代碼聲明了一個“事件”。由send函數的最后一行代碼觸發。客戶端(服務端應用也適用)可以以很低的開銷來監聽這些由區塊鏈觸發的事件。事件觸發時,監聽者會同時接收到from,to,value這些參數值,可以方便的用于跟蹤交易。為了監聽這個事件,你可以使用如下代碼:
Coin.Sent().watch({}, '', function(error, result) { if (!error) { console.log("Coin transfer: " + result.args.amount + " coins were sent from " + result.args.from + " to " + result.args.to + "."); console.log("Balances now:\n" + "Sender: " + Coin.balances.call(result.args.from) + "Receiver: " + Coin.balances.call(result.args.to)); } }
注意在客戶端中是如何調用自動生成的 balances 函數的。
這里有個比較特殊的函數 Coin。它是一個構造函數,會在合約創建的時候運行,之后就無法被調用。它會永久得存儲合約創建者的地址。msg(以及tx和block)是一個神奇的全局變量,它包含了一些可以被合約代碼訪問的屬于區塊鏈的屬性。msg.sender 總是存放著當前函數的外部調用者的地址。
最后,真正被用戶或者其他合約調用,用來完成本合約功能的函數是mint和send。如果合約創建者之外的其他人調用mint,什么都不會發生。而send可以被任何人(擁有一定數量的代幣)調用,發送一些幣給其他人。注意,當你通過該合約發送一些代幣到某個地址,在區塊鏈瀏覽器中查詢該地址將什么也看不到。因為發送代幣導致的余額變化只存儲在該代幣合約的數據存儲中。通過事件我們可以很容易創建一個可以追蹤你的新幣交易和余額的“區塊鏈瀏覽器”。
對于程序員來說,區塊鏈這個概念其實不難理解。因為最難懂的一些東西(挖礦,哈希,橢圓曲線加密,點對點網絡等等)只是為了提供一系列的特性和保障。你只需要接受這些既有的特性,不需要關心其底層的技術。就像你如果僅僅是為了使用亞馬遜的AWS,并不需要了解其內部工作原理。
區塊鏈是一個全局共享的,事務性的數據庫。這意味著參與這個網絡的每一個人都可以讀取其中的記錄。如果你想修改這個數據庫中的東西,就必須創建一個事務,并得到其他所有人的確認。事務這個詞意味著你要做的修改(假如你想同時修改兩個值)只能被完完全全的實施或者一點都沒有進行。
此外,當你的事務被應用到這個數據庫的時候,其他事務不能修改該數據庫。
舉個例子,想象一張表,里面列出了某個電子貨幣所有賬號的余額。當從一個賬戶到另外一個賬戶的轉賬請求發生時,這個數據庫的事務特性確保從一個賬戶中減掉的金額會被加到另一個賬戶上。如果因為某種原因,往目標賬戶上增加金額無法進行,那么源賬戶的金額也不會發生任何變化。
此外,一個事務會被發送者(創建者)進行密碼學簽名。這項措施非常直觀的為數據庫的特定修改增加了訪問保護。在電子貨幣的例子中,一個簡單的檢查就可以確保只有持有賬戶密鑰的人,才能從該賬戶向外轉賬。
區塊鏈要解決的一個主要難題,在比特幣中被稱為“雙花攻擊”。當網絡上出現了兩筆交易,都要花光一個賬戶中的錢時,會發生什么?一個沖突?
簡單的回答是你不需要關心這個問題。這些交易會被排序并打包成“區塊”,然后被所有參與的節點執行和分發。如果兩筆交易相互沖突,排序靠后的交易會被拒絕并剔除出區塊。
這些區塊按時間排成一個線性序列。這也正是“區塊鏈”這個詞的由來。區塊以一個相當規律的時間間隔加入到鏈上。對于以太坊,這個間隔大致是17秒。
作為“順序選擇機制”(通常稱為“挖礦”)的一部分,一段區塊鏈可能會時不時被回滾。但這種情況只會發生在整條鏈的末端。回滾涉及的區塊越多,其發生的概率越小。所以你的交易可能會被回滾,甚至會被從區塊鏈中刪除。但是你等待的越久,這種情況發生的概率就越小。
以太坊虛擬機(EVM)是以太坊中智能合約的運行環境。它不僅被沙箱封裝起來,事實上它被完全隔離,也就是說運行在EVM內部的代碼不能接觸到網絡、文件系統或者其它進程。甚至智能合約與其它智能合約只有有限的接觸。
以太坊中有兩類賬戶,它們共用同一個地址空間。外部賬戶,該類賬戶被公鑰-私鑰對控制(人類)。合約賬戶,該類賬戶被存儲在賬戶中的代碼控制。
外部賬戶的地址是由公鑰決定的,合約賬戶的地址是在創建該合約時確定的(這個地址由合約創建者的地址和該地址發出過的交易數量計算得到,地址發出過的交易數量也被稱作"nonce")
合約賬戶存儲了代碼,外部賬戶則沒有,除了這點以外,這兩類賬戶對于EVM來說是一樣的。
每個賬戶有一個key-value形式的持久化存儲。其中key和value的長度都是256比特,名字叫做storage.
另外,每個賬戶都有一個以太幣余額(單位是“Wei"),該賬戶余額可以通過向它發送帶有以太幣的交易來改變。
一筆交易是一條消息,從一個賬戶發送到另一個賬戶(可能是相同的賬戶或者零賬戶,見下文)。交易可以包含二進制數據(payload)和以太幣。
如果目標賬戶包含代碼,該代碼會執行,payload就是輸入數據。
如果目標賬戶是零賬戶(賬戶地址是0),交易將創建一個新合約。正如上文所講,這個合約地址不是零地址,而是由合約創建者的地址和該地址發出過的交易數量(被稱為nonce)計算得到。創建合約交易的payload被當作EVM字節碼執行。執行的輸出做為合約代碼被永久存儲。這意味著,為了創建一個合約,你不需要向合約發送真正的合約代碼,而是發送能夠返回真正代碼的代碼。
以太坊上的每筆交易都會被收取一定數量的gas,gas的目的是限制執行交易所需的工作量,同時為執行支付費用。當EVM執行交易時,gas將按照特定規則被逐漸消耗。
gas price(以太幣計)是由交易創建者設置的,發送賬戶需要預付的交易費用 = gas price * gas amount。 如果執行結束還有gas剩余,這些gas將被返還給發送賬戶。
無論執行到什么位置,一旦gas被耗盡(比如降為負值),將會觸發一個out-of-gas異常。當前調用幀所做的所有狀態修改都將被回滾。
每個賬戶有一塊持久化內存區域被稱為存儲。其形式為key-value,key和value的長度均為256比特。在合約里,不能遍歷賬戶的存儲。相對于另外兩種,存儲的讀操作相對來說開銷較大,修改存儲更甚。一個合約只能對它自己的存儲進行讀寫。
第二個內存區被稱為主存。合約執行每次消息調用時,都有一塊新的,被清除過的主存。主存可以以字節粒度尋址,但是讀寫粒度為32字節(256比特)。操作主存的開銷隨著其增長而變大(平方級別)。
EVM不是基于寄存器,而是基于棧的虛擬機。因此所有的計算都在一個被稱為棧的區域執行。棧最大有1024個元素,每個元素256比特。對棧的訪問只限于其頂端,方式為:允許拷貝最頂端的16個元素中的一個到棧頂,或者是交換棧頂元素和下面16個元素中的一個。所有其他操作都只能取最頂的兩個(或一個,或更多,取決于具體的操作)元素,并把結果壓在棧頂。當然可以把棧上的元素放到存儲或者主存中。但是無法只訪問棧上指定深度的那個元素,在那之前必須要把指定深度之上的所有元素都從棧中移除才行。
EVM的指令集被刻意保持在最小規模,以盡可能避免可能導致共識問題的錯誤實現。所有的指令都是針對256比特這個基本的數據類型的操作。具備常用的算術,位,邏輯和比較操作。也可以做到條件和無條件跳轉。此外,合約可以訪問當前區塊的相關屬性,比如它的編號和時間戳。
合約可以通過消息調用的方式來調用其它合約或者發送以太幣到非合約賬戶。消息調用和交易非常類似,它們都有一個源,一個目標,數據負載,以太幣,gas和返回數據。事實上每個交易都可以被認為是一個頂層消息調用,這個消息調用會依次產生更多的消息調用。
一個合約可以決定剩余gas的分配。比如內部消息調用時使用多少gas,或者期望保留多少gas。如果在內部消息調用時發生了out-of-gas異常(或者其他異常),合約將會得到通知,一個錯誤碼被壓在棧上。這種情況只是內部消息調用的gas耗盡。在solidity中,這種情況下發起調用的合約默認會觸發一個人工異常。這個異常會打印出調用棧。就像之前說過的,被調用的合約(發起調用的合約也一樣)會擁有嶄新的主存并能夠訪問調用的負載。調用負載被存儲在一個單獨的被稱為calldata的區域。調用執行結束后,返回數據將被存放在調用方預先分配好的一塊內存中。
調用層數被限制為1024,因此對于更加復雜的操作,我們應該使用循環而不是遞歸。
存在一種特殊類型的消息調用,被稱為callcode。它跟消息調用幾乎完全一樣,只是加載自目標地址的代碼將在發起調用的合約上下文中運行。
這意味著一個合約可以在運行時從另外一個地址動態加載代碼。存儲,當前地址和余額都指向發起調用的合約,只有代碼是從被調用地址獲取的。
這使得Solidity可以實現”庫“。可復用的庫代碼可以應用在一個合約的存儲上,可以用來實現復雜的數據結構。
在區塊層面,可以用一種特殊的可索引的數據結構來存儲數據。這個特性被稱為日志,Solidity用它來實現事件。合約創建之后就無法訪問日志數據,但是這些數據可以從區塊鏈外高效的訪問。因為部分日志數據被存儲在布隆過濾器(Bloom filter) 中,我們可以高效并且安全的搜索日志,所以那些沒有下載整個區塊鏈的網絡節點(輕客戶端)也可以找到這些日志。
合約甚至可以通過一個特殊的指令來創建其他合約(不是簡單的向零地址發起調用)。創建合約的調用跟普通的消息調用的區別在于,負載數據執行的結果被當作代碼,調用者/創建者在棧上得到新合約的地址。
只有在某個地址上的合約執行自毀操作時,合約代碼才會從區塊鏈上移除。合約地址上剩余的以太幣會發送給指定的目標,然后其存儲和代碼被移除。
注意,即使一個合約的代碼不包含自毀指令,依然可以通過代碼調用(callcode)來執行這個操作。
如果你希望高效的學習以太坊DApp開發,可以訪問匯智網提供的最熱門在線互動教程:
適合區塊鏈新手的以太坊DApp實戰入門教程
區塊鏈+IPFS+Node.js+MongoDB+Express去中心化以太坊電商應用開發實戰
“solidity語言開發智能合約怎么實現”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。