• <option id="cacee"><noscript id="cacee"></noscript></option>
  • <table id="cacee"><noscript id="cacee"></noscript></table>
  • <td id="cacee"></td>
  • <option id="cacee"></option>
  • <table id="cacee"></table>
  • <option id="cacee"><option id="cacee"></option></option>
  • <table id="cacee"><source id="cacee"></source></table><td id="cacee"><rt id="cacee"></rt></td>
    <option id="cacee"><option id="cacee"></option></option>
     找回密碼
     立即注冊

    掃一掃,登錄網站

    首頁 自媒體 查看內容
    • 2191
    • 0
    • 分享到

    智能合約-Solidity官方文檔(1)

    2018-4-5 14:44

    來源: HiBlock-Net



    寫在前面:HiBlock區塊鏈社區成立了翻譯小組(以太坊中文社區),翻譯區塊鏈相關的技術文檔及資料,本文為solidity官方文檔翻譯的第一部分《智能合約概述》,特發布出來邀請solidity愛好者、開發者做公開的審校,您可以添加微信baobaotalk_com,驗證輸入“solidity”,然后將您的意見和建議發送給我們,也可以在文末“留言”區留言,有效的建議我們會采納及合并進下一版本,同時將送一份小禮物給您以示感謝。


    1

    簡單的智能合約


    讓我們先看一下最基本的例子。現在就算你都不理解也不要緊,后面我們會有更深入的講解。


    存儲


    pragma solidity ^0.4.0;


    contract SimpleStorage {
       uint storedData;

       function set(uint x) public {
           storedData = x;
       }

       function get() public constant returns (uint) {
           return storedData;
       }

    }


    第一行就是告訴大家源代碼使用Solidity版本0.4.0寫的,并且使用0.4.0以上版本運行也沒問題(最高到0.5.0,但是不包含0.5.0)。這是為了確保合約不會在新的編譯器版本中突然行為異常。關鍵字 pragma 的含義是,一般來說,pragmas(編譯指令)是告知編譯器如何處理源代碼的指令的(例如, pragma once )。


    Solidity中合約的含義就是一組代碼(它的 函數 )和數據(它的 狀態 ),它們位于以太坊區塊鏈的一個特定地址上。 代碼行 uint storedData; 聲明一個類型為 uint (256位無符號整數)的狀態變量,叫做 storedData 。 你可以認為它是數據庫里的一個位置,可以通過調用管理數據庫代碼的函數進行查詢和變更。對于以太坊來說,上述的合約就是擁有合約(owning contract)。在這種情況下,函數 set 和 get 可以用來變更或取出變量的值。


    要訪問一個狀態變量,并不需要像 this. 這樣的前綴,雖然這是其他語言常見的做法。


    該合約能完成的事情并不多(由于以太坊構建的基礎架構的原因):它能允許任何人在合約中存儲一個單獨的數字,并且這個數字可以被世界上任何人訪問,且沒有可行的辦法阻止你發布這個數字。當然,任何人都可以再次調用 set ,傳入不同的值,覆蓋你的數字,但是這個數字仍會被存儲在區塊鏈的歷史記錄中。隨后,我們會看到怎樣施加訪問限制,以確保只有你才能改變這個數字。



    子貨幣(Subcurrency)例子


    下面的合約實現了一個最簡單的加密貨幣。這里,幣確實可以無中生有地產生,但是只有創建合約的人才能做到(實現一個不同的發行計劃也不難)。而且,任何人都可以給其他人轉幣,不需要注冊用戶名和密碼 —— 所需要的只是以太坊密鑰對。


    pragma solidity ^0.4.21;


    contract Coin {
       // 關鍵字“public”讓這些變量可以從外部讀取
       address public minter;
       mapping (address => uint) public balances;

       // 輕客戶端可以通過事件針對變化作出高效的反應
       event Sent(address from, address to, uint amount);

       // 這是構造函數,只有當合約創建時運行
       function Coin() public {
           minter = msg.sender;
       }

       function mint(address receiver, uint amount) public {
           if (msg.sender != minter) return;
           balances[receiver] += amount;
       }

       function send(address receiver, uint amount) public {
           if (balances[msg.sender] < amount) return;
           balances[msg.sender] -= amount;
           balances[receiver] += amount;
           emit Sent(msg.sender, receiver, amount);
       }

    }


    這個合約引入了一些新的概念,讓我們逐一解讀。


    address public minter; 這一行聲明了一個可以被公開訪問的 address 類型的狀態變量。 address 類型是一個160位的值,且不允許任何算數操作。這種類型適合存儲合約地址或外部人員的密鑰對。關鍵字 public 自動生成一個函數,允許你在這個合約之外訪問這個狀態變量的當前值。如果沒有這個關鍵字,其他的合約沒有辦法訪問這個變量。由編譯器生成的函數的代碼大致如下所示:


    function minter() returns (address) { return minter; }


    當然,加一個和上面完全一樣的函數是行不通的,因為我們會有同名的一個函數和一個變量,這里,主要是希望你能明白——編譯器已經幫你實現了。


    下一行, 


    mapping (address => uint) public balances;


    也創建一個公共狀態變量,但它是一個更復雜的數據類型。 該類型將address映射為無符號整數。 Mappings 可以看作是一個 哈希表 它會執行虛擬初始化,以使所有可能存在的鍵都映射到一個字節表示為全零的值。 但是,這種類比并不太恰當,因為它既不能獲得映射的所有鍵的列表,也不能獲得所有值的列表。 因此,要么記住你添加到mapping中的數據(使用列表或更高級的數據類型會更好),要么在不需要鍵列表或值列表的上下文中使用它,就如本例。 而由 public 關鍵字創建的getter函數 getter function 則是更復雜一些的情況, 它大致如下所示:


    function balances(address _account) public view returns (uint) {
       return balances[_account];

    }


    正如你所看到的,你可以通過該函數輕松地查詢到賬戶的余額。


    event Sent(address from, address to, uint amount);


    這行聲明了一個所謂的“事件(event)”,它會在 send 函數的最后一行被發出。 用戶界面(當然也包括服務器應用程序)可以監聽區塊鏈上正在發送的事件,而不會花費太多成本。一旦它被發出, 監聽該事件的listener都將收到通知。而所有的事件都包含了 from , to 和 amount 三個參數,可方便追蹤事務。 為了監聽這個事件,你可以使用如下代碼:


    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 函數可被任何人用于向他人發送幣 (當然,前提是發送者擁有這些幣)。記住,如果你使用合約發送幣給一個地址,當你在區塊鏈瀏覽器上查看該地址時是看不到任何相關信息的。因為,實際上你發送幣和更改余額的信息僅僅存儲在特定合約的數據存儲器中。通過使用事件,你可以非常簡單地為你的新幣創建一個“區塊鏈瀏覽器”來追蹤交易和余額。


    2

    區塊鏈基礎


    對于程序員來說,區塊鏈這個概念并不難理解,這是因為大多數難懂的東西 (挖礦, 哈希 ,橢圓曲線密碼學 ,點對點網絡(P2P) 等) 都只是用于提供特定的功能和承諾。你只需接受這些既有的特性功能,不必關心底層技術,比如,難道你必須知道亞馬遜的 AWS 內部原理,你才能使用它嗎?


    交易/事務


    區塊鏈是全球共享的事務性數據庫,這意味著每個人都可加入網絡來閱讀數據庫中的記錄。如果你想改變數據庫中的某些東西,你必須創建一個被所有其他人所接受的事務。事務一詞意味著你想做的(假設您想要同時更改兩個值),要么一點沒做,要么全部完成。此外,當你的事務被應用到數據庫時,其他事務不能修改數據庫。


    舉個例子,設想一張表,列出電子貨幣中所有賬戶的余額。如果請求從一個賬戶轉移到另一個賬戶,數據庫的事務特性確保了如果從一個賬戶扣除金額,它總被添加到另一個賬戶。如果由于某些原因,無法添加金額到目標賬戶時,源賬戶也不會發生任何變化。


    此外,交易總是由發送人(創建者)簽名。


    這樣,就可非常簡單地為數據庫的特定修改增加訪問保護機制。 在電子貨幣的例子中,一個簡單的檢查可以確保只有持有賬戶密鑰的人才能從中轉賬。


    區塊


    在比特幣中,要解決的一個主要難題,被稱為“雙花攻擊 (double-spend attack)”:如果網絡存在兩筆交易,都想花光同一個賬戶的錢時(即所謂的沖突)會發生什么情況?交易互相沖突?


    簡單的回答是你不必在乎此問題。網絡會為你自動選擇一條交易序列,并打包到所謂的“區塊”中,然后它們將在所有參與節點中執行和分發。如果兩筆交易互相矛盾,那么最終被確認為后發生的交易將被拒絕,不會被包含到區塊中。


    這些塊按時間形成了一個線性序列,這正是“區塊鏈”這個詞的來源。區塊以一定的時間間隔添加到鏈上 —— 對于以太坊,這間隔大約是17秒。


    作為“順序選擇機制”(也就是所謂的“挖礦”)的一部分,可能會發生這樣的情況:塊不時地被回滾,但只發生在區塊鏈的“末端”。在末端涉及回滾區塊越多,其發生的概率越小。所以你的交易有可能被回滾,甚至從區塊鏈中抹除,但你等待的時間越長,這種情況發生的概率就越小。


    3

    以太坊虛擬機



    概述


    以太坊虛擬機 EVM 是智能合約的運行環境。它不僅是沙盒封裝的,而且是完全隔離的,也就是說在 EVM 中運行代碼是無法訪問網絡、文件系統和其他進程的。智能合約與其他智能合約也是只有有限接觸。


    賬戶


    以太坊中有兩類賬戶(它們共用同一個地址空間): 外部賬戶 由公鑰-私鑰對控制; 合約賬戶 由存儲在賬戶中的代碼控制。


    外部賬戶的地址是由公鑰決定的,而合約賬戶的地址是在創建該合約時確定的(這個地址通過合約創建者的地址和從該地址發出過的交易數量計算得到的,也就是所謂的“nonce”)


    無論帳戶是否存儲代碼,這兩類賬戶對 EVM 來說是一樣的。


    每個賬戶都有一個鍵值對形式的持久化存儲。其中key和value的長度都是256比特,我們稱之為 存儲


    此外,每個賬戶有一個以太幣余額( balance )(單位是“Wei”),余額會因為發送包含以太幣的交易而改變。


    交易


    交易可以看作是從一個帳戶發送到另一個帳戶的消息(這里的賬戶,可能是相同的或特殊的零帳戶,請參閱下文)。它能包含一個二進制數據(合約負載)和以太幣。


    如果目標賬戶含有代碼,此代碼會被執行,并以 payload 作為入參。


    如果目標賬戶是零賬戶(賬戶地址為 0 ),此交易將創建一個 新合約 。 如前文所述,合約的地址不是零地址,而是通過合約創建者的地址和從該地址發出過的交易數量計算得到的(所謂的“nonce”)。 這個用來創建合約的交易的payload會被轉換為EVM字節碼并執行。執行的輸出將作為合約代碼被永久存儲。這意味著,為創建一個合約,你不需要向合約發送真正的合約代碼,而是發送能夠產生真正代碼的代碼。


    Gas


    一經創建,每筆交易都收取一定數量的 gas,目的是限制執行交易所需要的工作量和為交易支付手續費。EVM 執行交易時,gas 將按特定規則逐漸耗盡。


    gas price 是被交易發送者設置的一個數值,發送者賬戶需要預付的手續費= gas_price * gas 。如果交易執行后還有剩余, gas 會原路返還。


    無論執行到什么位置,一旦 gas 被耗盡(比如降為負值),將會觸發一個 out-of-gas 異常。當前調用幀所做的所有狀態修改都將被回滾。


    存儲,內存和棧


    每個賬戶有一塊持久化內存區被稱為 存儲。 存儲是一個 key-value 的鍵值對 ,其存儲著一個由256位的鍵到256位的值的映射. 在合約中,不能枚舉戶中的存儲,且存儲的讀操作相對開銷高,修改存儲開銷更高。一個合約只能對它自己的存儲進行讀寫。


    第二個內存區稱為 內存,合約會試圖為每一次消息調用獲取一塊被重新擦拭干凈的內存實例。 內存是線性的,可按字節級尋址,但讀的長度被限制為256位,而寫的長度可以是8位或256位。當訪問(無論是讀還是寫)之前從未訪問過的內存字(word)時(無論是偏移到該字內的任何位置),內存將按字進行擴展(每個字是256 bit)。擴容也將消耗一定的gas。 內存越大,費用就越高(平方級別)。


    EVM 不是基于寄存器的,而是基于棧的,因此所有的計算都在一個被稱為 stack 的區域執行。 棧最大有1024個元素,每個元素長度是一個字(256 bit)。對棧的訪問只限于其頂端,限制方式為:允許拷貝最頂端的16個元素中的一個到棧頂,或者是交換棧頂元素和下面16個元素中的一個。所有其他操作都只能取最頂的兩個(或一個,或更多,取決于具體的操作)元素,運算后,把結果壓入棧頂。當然可以把棧上的元素放到存儲或內存中。但是無法只訪問棧上指定深度的那個元素,除非先從棧頂移除其他元素。


    指令集


    EVM的指令集量應盡量少,以最大限度地避免可能導致共識問題的錯誤實現。所有的指令都是針對"256位的字(word)"這個基本的數據類型來進行操作。具備常用的算術、位、邏輯和比較操作。也可以做到有條件和無條件跳轉。此外,合約可以訪問當前區塊的相關屬性,比如它的編號和時間戳。


    消息調用


    合約可以通過消息調用的方式來調用其它合約或者發送以太幣到非合約賬戶。消息調用和交易非常類似,它們都有一個源、目標、數據、以太幣、gas和返回數據。事實上每個交易都由一個頂層消息調用組成,這個消息調用又可創建更多的消息調用。


    合約可以決定在其內部的消息調用中,對于剩余的 gas,應發送和保留多少。如果在內部消息調用時發生了out-of-gas異常(或其他任何異常),這將由一個被壓入棧頂的錯誤值所指明。此時,只有與該內部消息調用一起發送的gas會被消耗掉。并且,Solidity中,發起調用的合約默認會觸發一個手工的異常,以便異常可以從調用棧里“冒泡出來”。 如前文所述,被調用的合約(可以和調用者是同一個合約)會獲得一塊剛剛清空過的內存,并可以訪問調用的payload——由被稱為 calldata 的獨立區域所提供的數據。調用執行結束后,返回數據將被存放在調用方預先分配好的一塊內存中。 調用深度被 限制 為 1024 ,因此對于更加復雜的操作,我們應使用循環而不是遞歸。


    委托調用/代碼調用和庫


    有一種特殊類型的消息調用,被稱為 委托調用(delegatecall)。它和一般的消息調用的區別在于,目標地址的代碼將在發起調用的合約的上下文中執行,并且 msg.sender 和 msg.value 不變。 這意味著一個合約可以在運行時從另外一個地址動態加載代碼。存儲、當前地址和余額都指向發起調用的合約,只有代碼是從被調用地址獲取的。 這使得 Solidity 可以實現”庫“能力:可復用的代碼庫可以放在一個合約的存儲上,如用來實現復雜的數據結構的庫。


    日志


    有一種特殊的可索引的數據結構,其存儲的數據可以一路映射直到區塊層級。這個特性被稱為 日志(logs),Solidity用它來實現 事件(events)。合約創建之后就無法訪問日志數據,但是這些數據可以從區塊鏈外高效的訪問。因為部分日志數據被存儲在 布隆過濾器(Bloom filter) 中,我們可以高效并且加密安全地搜索日志,所以那些沒有下載整個區塊鏈的網絡節點(輕客戶端)也可以找到這些日志。


    創建


    合約甚至可以通過一個特殊的指令來創建其他合約(不是簡單的調用零地址)。創建合約的調用 create calls 和普通消息調用的唯一區別在于,負載會被執行,執行的結果被存儲為合約代碼,調用者/創建者在棧上得到新合約的地址。


    自毀


    合約代碼從區塊鏈上移除的唯一方式是合約在合約地址上的執行自毀操作 selfdestruct 。合約賬戶上剩余的以太幣會發送給指定的目標,然后其存儲和代碼從狀態中被移除。




    版權申明:本內容來自于互聯網,屬第三方匯集推薦平臺。本文的版權歸原作者所有,文章言論不代表鏈門戶的觀點,鏈門戶不承擔任何法律責任。如有侵權請聯系QQ:3341927519進行反饋。
    相關新聞
    發表評論

    請先 注冊/登錄 后參與評論

      回頂部
    • <option id="cacee"><noscript id="cacee"></noscript></option>
    • <table id="cacee"><noscript id="cacee"></noscript></table>
    • <td id="cacee"></td>
    • <option id="cacee"></option>
    • <table id="cacee"></table>
    • <option id="cacee"><option id="cacee"></option></option>
    • <table id="cacee"><source id="cacee"></source></table><td id="cacee"><rt id="cacee"></rt></td>
      <option id="cacee"><option id="cacee"></option></option>
      妖精视频