區塊鏈技術:智能合約入門

什麼是智能合約

一個智能合約是一套以數字形式定義的承諾(promises) ,包括合約參與方能夠在上面執行這些承諾的協議。一個合約由一組代碼(合約的函數)和數據(合約的狀態)組成,而且運行在以太坊虛擬機上.

以太坊虛擬機(EVM)使用了256比特長度的機器碼,是一種基於堆棧的虛擬機,用於執行以太坊智能合約 。因爲EVM是針對以太坊體系設計的,所以使用了以太坊帳戶模型(Account Model)進行價值傳輸html

合約的代碼具備什麼能力:
讀取交易數據。
讀取或寫入合約本身的存儲空間。
讀取環境變量(塊高,哈希值,gas)
向另外一個合約發送一個「內部交易」。

1. 什麼是solidity

Solidity是一種智能合約高級語言,運行在Ethereum虛擬機(EVM)之上。java

solidity 語言特色node

它的語法接近於Javascript,是一種面向對象的語言。但做爲一種真正意義上運行在網絡上的去中心合約,它有不少的不一樣點:git

  • 異常機制,相似於事務的原子性。一旦出現異常,全部的執行都將會被回撤,這主要是爲了保證合約執行的原子性,以免中間狀態出現的數據不一致。
  • 運行環境是在去中心化的網絡上,會比較強調合約或函數執行的調用的方式。由於原來一個簡單的函數調用變爲了一個網絡上的節點中的代碼執行
  • 存儲是使用網絡上的區塊鏈,數據的每個狀態均可以永久存儲。

2. 開發的工具

3 快速入門

準備工做
  1. 搭建FISCO-BCOS塊鏈,詳細安裝見文檔
  2. 安裝WeBASE-Front,詳細見文檔 , 修改相關配置,啓動服務。

    如下例子演示,依賴於上面的兩個步驟。程序員

3.1 開發合約

合約開發步驟:github

1. 寫合約
2. 編譯合約
3. 部署合約
4. 測試合約
5. 生成java文件

說明:WeBase 幫助用戶開發、測試和生成對應的Java類,用戶獲取java類,能夠直接進行業務開發,加快開發進度和效率。web

3.1.1. 獲取合約例子

pragma solidity ^0.4.24;

contract HelloWorld {
    string name;

    function HelloWorld() {
        name = "Hello, World!";
    }

    function get()constant returns(string) {
        return name;
    }

    function set(string n) {
        name = n;
    }
}

3.1.2. 部署合約到區塊鏈上

  • 編譯合約

圖片描述

  • 編譯合約

圖片描述

  • 調用get方法

圖片描述

圖片描述

  • 調用set方法

  • 檢查調用狀況

3.1.4. 生成Java

獲取Java文件

3.1.5 應用開發

  • 下載應用腳手架spring

    $ git clone  https://github.com/FISCO-BCOS/spring-boot-starter.git
  • 導入文件進行配置開始開發

    [更多工具和例子]()編程

3.2.1 引入概念:

address:以太坊地址的長度,大小20個字節,160位,因此能夠用一個uint160編碼。地址是全部合約的基礎,全部的合約都會繼承地址對象,也能夠隨時將一個地址串,獲得對應的代碼進行調用。合約的地址是基於帳號隨機數和交易數據的哈希計算出來的json

ABI:是以太坊的一種合約間調用時或消息發送時的一個消息格式。就是定義操做函數簽名,參數編碼,返回結果編碼等。

交易:以太坊中「交易」是指存儲從外部帳戶發出的消息的簽名數據包。

簡單理解是:只要對區塊鏈進行寫操做,必定會發生交易。

交易回執:發生交易後的返回值

3.2.2 擴展閱讀:

3.3 合約文件結構簡介

版本聲明

pragma solidity ^0.4.24;

狀態變量(State Variables)

string name;

詳細說明見下文

函數(Functions)

function get()constant returns(string) {
        return name;
    }

    function set(string n) {
        name = n;
    }

事件(Events)

//事件的聲明
   event AddMsg(address indexed sender, bytes32 msg);
   //事件的使用
   function setData(int256 x) public {
         storedData = x;
         AddMsg(msg.sender, "in the set() method");
     }

結構類型(Structs Types)

contract Contract {
     struct Data {
       uint deadline;
       uint amount;
     }
     Data data;
     function set(uint id, uint deadline, uint amount) {
       data.deadline = deadline;
       data.amount = amount;
     }
   }

函數修飾符(Function Modifiers)

相似於hook

modifier only_with_at_least(int x) {
          if (x >= 5) {
            x = x+10;
             _;
          }
       }

4. 合約編程模式COP

面向條件的編程(COP)是面向合約編程的一個子域,做爲一種面向函數和命令式編程的混合模式。COP解決了這個問題,經過須要程序員顯示地枚舉全部的條件。邏輯變得扁平,沒有條件的狀態變化。條件片斷能夠被正確的文檔化,複用,能夠根據需求和實現來推斷。重要的是,COP在編程中把預先條件看成爲一等公民。這樣的模式規範能保證合約的安全。

4.1 FEATURES

  • 函數主體沒有條件判斷

例子:

contract Token {
    // The balance of everyone
    mapping (address => uint) public balances;
    // Constructor - we're a millionaire!
    function Token() {
        balances[msg.sender] = 1000000;
    }
    // Transfer `_amount` tokens of ours to `_dest`.
    function transfer(uint _amount, address _dest) {
        balances[msg.sender] -= _amount;
        balances[_dest] += _amount;
    }
}

改進後:

function transfer(uint _amount, address _dest) {
    if (balances[msg.sender] < _amount)
        return;
    balances[msg.sender] -= _amount;
    balances[_dest] += _amount;
}

COP的風格

modifier only_with_at_least(uint x) {
    if (balances[msg.sender] >= x) _;
}

function transfer(uint _amount, address _dest)
only_with_at_least(_amount) {
    balances[msg.sender] -= _amount;
    balances[_dest] += _amount;
}

擴展閱讀:

5. 語法介紹

5.1 值類型

  • 布爾(Booleans)true false支持的運算符

    !邏輯非&& 邏輯與|| 邏輯或== 等於!= 不等於

  • 整型(Integer)int/uint:變長的有符號或無符號整型。變量支持的步長以8遞增,支持從uint8到uint256,以及int8到int256。須要注意的是,uint和int默認表明的是uint256和int256
  • 地址(Address):以太坊地址的長度,大小20個字節,160位,因此能夠用一個uint160編碼。地址是全部合約的基礎,全部的合約都會繼承地址對象,也能夠隨時將一個地址串,獲得對應的代碼進行調用
  • 定長字節數組(fixed byte arrays)
  • 有理數和整型(Rational and Integer Literals,String literals)
  • 枚舉類型(Enums)
  • 函數(Function Types)

5.2 引用類型(Reference Types)

  • 不定長字節數組(bytes)
  • 字符串(string)
  • 數組(Array)
  • 結構體(Struts)

更多詳情見官方API

6. 重要概念

6.1 Solidity的數據位置

數據位置的類型

變量的存儲位置屬性。有三種類型,memory,storage和calldata。

  • memory存儲位置同咱們普通程序的內存相似。即分配,即便用,越過做用域即不可被訪問,等待被回收-
  • storage的變量,數據將永遠存在於區塊鏈上。
  • calldata 數據位置比較特殊,通常只有外部函數的參數(不包括返回參數)被強制指定爲calldata

Storage - 狀態變量的存儲模型

大小固定的變量(除了映射,變長數組之外的全部類型)在存儲(storage)中是依次連續從位置0開始排列的。若是多個變量佔用的大小少於32字節,會盡量的打包到單個storage槽位裏,具體規則以下:

  • 在storage槽中第一項是按低位對齊存儲(lower-order aligned)
  • 基本類型存儲時僅佔用其實際須要的字節。
  • 若是基本類型不能放入某個槽位餘下的空間,它將被放入下一個槽位。
  • 結構體和數組老是使用一個全新的槽位,並佔用整個槽(但在結構體內或數組內的每一個項仍聽從上述規則)

優化建議:

爲了方便EVM進行優化,嘗試有意識排序storage的變量和結構體的成員,從而讓他們能打包得更緊密。好比,按這樣的順序定義,uint128, uint128, uint256,而不是uint128, uint256, uint128。由於後一種會佔用三個槽位。

Memory - 內存變量的佈局(Layout in Memory)

Solidity預留了3個32字節大小的槽位:

0-64:哈希方法的暫存空間(scratch space)

64-96:當前已分配內存大小(也稱空閒內存指針(free memory pointer))

暫存空間可在語句之間使用(如在內聯編譯時使用)

Solidity老是在空閒內存指針所在位置建立一個新對象,且對應的內存永遠不會被釋放(也許將來會改變這種作法)。

有一些在Solidity中的操做須要超過64字節的臨時空間,這樣就會超過預留的暫存空間。他們就將會分配到空閒內存指針所在的地方,但因爲他們自身的特色,生命週期相對較短,且指針自己不能更新,內存也許會,也許不會被清零(zerod out)。所以,你們不該該認爲空閒的內存必定已是清零(zeroed out)的。

例子

6.2 address

以太坊地址的長度,大小20個字節,160位,因此能夠用一個uint160編碼。地址是全部合約的基礎,全部的合約都會繼承地址對象,也能夠隨時將一個地址串,獲得對應的代碼進行調用

6.3 event

event AddMsg(address indexed sender, bytes32 msg);

  • 這行代碼聲明瞭一個「事件」。客戶端(服務端應用也適用)能夠以很低的開銷來監聽這些由區塊鏈觸發的事件

事件是使用EVM日誌內置功能的方便工具,在DAPP的接口中,它能夠反過來調用Javascript的監聽事件的回調。

var event = instance.AddMsg({}, function(error, result) {
        if (!error) {
            var msg = "AddMsg: " + utils.hex2a(result.args.msg) + " from "
            console.log(msg);
            return;
        } else {
            console.log('it error')
        }
    });
  • 事件在合約中可被繼承。當被調用時,會觸發參數存儲到交易的日誌中(一種區塊鏈上的特殊數據結構)。這些日誌與合約的地址關聯,併合併到區塊鏈中,只要區塊能夠訪問就一直存在(至少Frontier,Homestead是這樣,但Serenity也許也是這樣)。日誌和事件在合約內不可直接被訪問,即便是建立日誌的合約。
  • 日誌位置在nodedir0/log 裏面,能夠打出特殊的類型進行驗證

6.4 數組

數組是定長或者是變長數組。有length屬性,表示當前的數組長度。

  1. bytes:相似於byte[], 動態長度的字節數組
  2. string:相似於bytes,動態長度的UTF-8編碼的字符類型
  3. bytes1~bytes32

    通常使用定長的 bytes1~bytes32。在知道字符串長度的狀況下,指定長度時,更加節省空間。

6.4.1 建立數組

  1. 字面量 uint[] memory a = []
  2. new uint[] memory a = new uint[](7);
    例子

    pragma solidity ^0.4.0;
    
     contract SimpleStartDemo{
       uint[] stateVar;
     
       function f(){
        //定義一個變長數組
         uint[] memory memVar;
     
         //不能在使用new初始化之前使用
         //VM Exception: invalid opcode
         //memVar [0] = 100;
     
         //經過new初始化一個memory的變長數組
         memVar = new uint[](2);
         
         //不能在使用new初始化之前使用
         //VM Exception: invalid opcode
         //stateVar[0] = 1;
         
         //經過new初始化一個storage的變長數組
         stateVar = new uint[](2);
         stateVar[0] = 1;
       }
     }

6.4.2 數組的屬性和方法

length屬性

storage變長數組是能夠修改length

memory變長數組是不能夠修改length

push方法

storage變長數組可使用push方法

bytes可使用push方法

例子

pragma solidity ^0.4.2;

contract SimpleStartDemo {
  uint[] stateVar;

  function f() returns (uint){
    //在元素初始化前使用
    stateVar.push(1);

    stateVar = new uint[](1);
    stateVar[0] = 0;
    //自動擴充長度
     uint pusharr = stateVar.push(1);
     uint len = stateVar.length;
    //不支持memory
    //Member "push" is not available in uint256[] memory outside of storage.
    //uint[] memory memVar = new uint[](1);
    //memVar.push(1);

    return len;
  }
}

下標:和其餘語言相似

6.4.3 Memory數組

  1. 若是Memory數組做爲函數的參數傳遞,只能支持ABI能支持的類型類型。
  2. Memory數組是不能修改修改數組大小的屬性
    例子

    pragma solidity ^0.4.2;

    contract SimpleStartDemo {

    function f() {
         //建立一個memory的數組
         uint[] memory a = new uint[](7);
         
         //不能修改長度
         //Error: Expression has to be an lvalue.
         //a.length = 100;
     }
     
     //storage
     uint[] b;
     
     function g(){
         b = new uint[](7);
         //能夠修改storage的數組
         b.length = 10;
         b[9] = 100;
     }

    }

EVM的限制

因爲EVM的限制,不能經過外部函數直接返回動態數組和多維數組

  1. 將stroage數組不能直接返回,須要轉換成memory類型的返回
//Data層數據
      struct Rate {
              int key1;
            int unit;
            uint[3] exDataArr;
            bytes32[3] exDataStr;
        }
    
        mapping(int =>Rate) Rates;
     function getRate(int key1) public constant returns(int,uint[3],bytes32[3]) {
            uint[3] memory exDataInt = Rates[key1].exDataArr;
            bytes32[3] memory exDataStr = Rates[key1].exDataStr;
            return (Rates[key1].unit,exDataInt,exDataStr);
        }

業務場景

6.5 函數

function (<parameter types>) {internal(默認)|external} constant [returns (<return types>)]

6.5.1 函數的internal與external

例子

pragma solidity ^0.4.5;

contract FuntionTest{
    function internalFunc() internal{}

    function externalFunc() external{}

    function callFunc(){
        //直接使用內部的方式調用
        internalFunc();

        //不能在內部調用一個外部函數,會報編譯錯誤。
        //Error: Undeclared identifier.
        //externalFunc();

        //不能經過`external`的方式調用一個`internal`
        //Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
        //this.internalFunc();

        //使用`this`以`external`的方式調用一個外部函數
        this.externalFunc();
    }
}
contract FunctionTest1{
    function externalCall(FuntionTest ft){
        //調用另外一個合約的外部函數
        ft.externalFunc();
        
        //不能調用另外一個合約的內部函數
        //Error: Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
        //ft.internalFunc();
    }
}

訪問函數有外部(external)可見性。若是經過內部(internal)的方式訪問,好比直接訪問,你能夠直接把它當一個變量進行使用,但若是使用外部(external)的方式來訪問,如經過this.,那麼它必須經過函數的方式來調用。

例子

pragma solidity ^0.4.2;
    
    contract SimpleStartDemo {
        uint public c = 10;
        
        function accessInternal() returns (uint){
            return c;
        }
        
        function accessExternal() returns (uint){
            return this.c();
        }
    }

6.5.2 函數調用

  • 內部調用,不會建立一個EVM調用,也叫消息調用
  • 外部調用,建立EVM調用,會發起消息調用

6.5.3 函數修改器(Function Modifiers)

修改器(Modifiers)能夠用來輕易的改變一個函數的行爲。好比用於在函數執行前檢查某種前置條件。修改器是一種合約屬性,可被繼承,同時還可被派生的合約重寫(override)

例子

pragma solidity ^0.4.2;
    
    contract SimpleStartDemo {
        int256 storedData;
        event AddMsg(address indexed sender, bytes32 msg);
    
        modifier only_with_at_least(int x) {
           if (x >= 5) {
             x = x+10;
              _;
           }
        } 
        function setData(int256 x) public only_with_at_least(x){
            storedData = x;
            AddMsg(msg.sender, "[in the set() method]");
        }
    }

6.5.4合約構造函數 同名函數

  • 可選
  • 僅能有一個構造器
  • 不支持重載

6.6 Constant

函數也可被聲明爲常量,這類函數將承諾本身不修改區塊鏈上任何狀態。

通常從鏈上獲取數據時,get函數都會加上constant

6.7 繼承(Inheritance)

Solidity經過複製包括多態的代碼來支持多重繼承。

父類

pragma solidity ^0.4.4;
    
    contract Meta {
        string  public name;
        string  public abi;
        address metaAddress;
    
        function Meta(string n,string a){
            name=n;
            abi=a;
        }
       
        function getMeta()public constant returns(string,string,address){
            return (name,abi,metaAddress);
        }
       
        function setMetaAddress(address meta) public {
            metaAddress=meta;
        }    
    }

子類

pragma solidity ^0.4.4;
    
    import "Meta.sol";
    contract Demo is Meta{
        bytes32 public orgID; 
    
        function Demo (string n,string abi,bytes32 id) Meta(n,abi)
        {
            orgID = id;
        }
    }
最簡單的合約架構

1:1合約架構圖

7. 限制

基於EVM的限制,不能經過外部函數返回動態的內容

please keep in mind

  • Fail as early and loudly as possible
  • Favor pull over push payments
  • Order your function code: conditions, actions, interactions
  • Be aware of platform limits
  • Write tests
  • Fault tolerance and Automatic bug bounties
  • Limit the amount of funds deposited
  • Write simple and modular code
  • Don’t write all your code from scratch
  • Timestamp dependency: Do not use timestamps in critical parts of the code, because miners can manipulate them
  • Call stack depth limit: Don’t use recursion, and be aware that any call can fail if stack depth limit is reached
  • Reentrancy: Do not perform external calls in contracts. If you do, ensure that they are the very last thing you do

8. 語言自己存在的痛點

  1. ABI支持的類型有限,難以返回複雜的結構體類型。
  2. Deep Stack的問題
  3. 難以調試,只能靠event log ,進行合約的調試
  4. 合約調用合約只能使用定長數組

9. 合約架構

9.1. 合約架構分層

實用架構示意圖

getDEmo

合約的架構分兩層數據合約和邏輯合約,方便後期合約的升級。更多詳情,請參見淺談以太坊智能合約的設計模式與升級方法

9.2. 更多合約更新架構和方法

10. 參考資料

歡迎你們貢獻和分享,一塊兒來學習區塊鏈~

相關文章
相關標籤/搜索