星雲鏈智能合約開發(六):智能合約開發與部署

編寫智能合約

Nebulas實現了NVM虛擬機來運行智能合約,NVM的實現使用了JavaScript V8引擎,所以當前的開發版,我們可以使用JavaScript、TypeScript來編寫智能合約。

編寫智能合約的簡要規範

  1. 智能合約代碼必須是一個Prototype的對象;
  2. 智能合約代碼必須有一個init()的方法,這個方法只會在部署的時候被執行一次;
  3. 智能合約裏面的私有方法是以_開頭的方法,私有方法不能被外部直接調用;

智能合約存儲區

星雲鏈智能合約(smart contract)提供了鏈上數據存儲功能。類似於傳統的key-value存儲系統(eg:redis),可以付費(消耗gas)將數據存儲到星雲鏈上。

星雲鏈的智能合約運行環境內置了存儲對象==LocalContractStorage==,可以存儲數字,字符串,JavaScript對象,存儲數據只能在智能合約內使用,其他合約不能讀取存儲的內容。

基礎用法

LocalContractStorage的簡單接口包括set,get,del接口,實現了存儲,讀取,刪除數據功能。存儲可以是數字,字符串,對象。

LocalContractStorage存儲數據

// 存儲數據,數據會被json序列化成字符串保存
LocalContractStorage.put(key, value);
// 或者
LocalContractStorage.set(key, value);

LocalContractStorage讀取數據

// 獲取數據
LocalContractStorage.get(key);

LocalContractStorage刪除數據

// 刪除數據, 數據刪除後無法讀取
LocalContractStorage.del(key);
// 或者
LocalContractStorage.delete(key);

高級用法

LocalContractStorage除了基本的set,get,del方法,還提供方法來綁定合約屬性。對綁定過的合約屬性的讀寫將直接在LocalContractStorage上讀寫,而無需調用get和set方法。

綁定屬性

在綁定一個合約屬性時,需要提供對象實例,屬性名和序列化方法。

// SampleContract的`size`屬性爲存儲屬性,對`size`的讀寫會存儲到鏈上,
// 此處的`descriptor`設置爲null,將使用默認的JSON.stringify()和JSON.parse()
LocalContractStorage.defineProperty(this, "size", null);

// SampleContract的`value`屬性爲存儲屬性,對`value`的讀寫會存儲到鏈上,
// 此處的`descriptor`自定義實現,存儲時直接轉爲字符串,讀取時獲得Bignumber對象
LocalContractStorage.defineProperty(this, "value", { // 提供自定義的序列化和反序列化方法
    stringify: function (obj) { // 序列化方法
        return obj.toString();
    },
    parse: function (str) { //反序列化方法
        return new BigNumber(str);
    }
});
// SampleContract的多個屬性批量設置爲存儲屬性,對應的descriptor默認使用JSON序列化
LocalContractStorage.defineProperties(this, {
    name: null,
    count: null
});

然後,我們可以如下在合約裏直接讀寫這些屬性。

SampleContract.prototype = {
    // 合約部署時調用,部署後無法二次調用
    init: function (name, count, size, value) {
        // 在部署合約時將數據存儲到鏈上
        this.name = name;
        this.count = count;
        this.size = size;
        this.value = value;
    },
    testStorage: function (balance) {
        // 使用value時會從存儲中讀取鏈上數據,並根據descriptor設置自動轉換爲Bignumber
        var amount = this.value.plus(new BigNumber(2));
        if (amount.lessThan(new BigNumber(balance))) {
            return 0
        }
    }
};

綁定Map屬性

'use strict';

var SampleContract = function () {
    // 爲`SampleContract`定義`userMap`的屬性集合,數據可以通過`userMap`存儲到鏈上
    LocalContractStorage.defineMapProperty(this, "userMap");

    // 爲`SampleContract`定義`userBalanceMap`的屬性集合,並且存儲和讀取序列化方法自定義
    LocalContractStorage.defineMapProperty(this, "userBalanceMap", {
        stringify: function (obj) {
            return obj.toString();
        },
        parse: function (str) {
            return new BigNumber(str);
        }
    });

    // 爲`SampleContract`定義多個集合
    LocalContractStorage.defineMapProperties(this,{
        key1Map: null,
        key2Map: null
    });
};

SampleContract.prototype = {
    init: function () {
    },
    testStorage: function () {
        // 將數據存儲到userMap中,並序列化到鏈上
        this.userMap.set("robin","1");
        // 將數據存儲到userBalanceMap中,使用自定義序列化函數,保存到鏈上
        this.userBalanceMap.set("robin",new BigNumber(1));
    },
    testRead: function () {
        //讀取存儲數據
        var balance = this.userBalanceMap.get("robin");
        this.key1Map.set("robin", balance.toString());
        this.key2Map.set("robin", balance.toString());
    }
};

module.exports = SampleContract;

Map數據遍歷

在智能合約中如果需要遍歷map集合,可以採用如下方式:定義兩個map,分別是arrayMap,dataMap,arrayMap採用嚴格遞增的計數器作爲key,dataMap採用data的key作爲key,詳細參見set方法。遍歷實現參見forEach,先遍歷arrayMap,得到dataKey,再對dataMap遍歷。Tip:由於Map遍歷性能開銷比較大,不建議對大數據量map進行遍歷,建議按照limit,offset形式進行遍歷,否者可能會由於數據過多,導致調用超時。

"use strict";

var SampleContract = function () {
   LocalContractStorage.defineMapProperty(this, "arrayMap");
   LocalContractStorage.defineMapProperty(this, "dataMap");
   LocalContractStorage.defineProperty(this, "size");
};

SampleContract.prototype = {
    init: function () {
        this.size = 0;
    },

    set: function (key, value) {
        var index = this.size;
        this.arrayMap.set(index, key);
        this.dataMap.set(key, value);
        this.size +=1;
    },

    get: function (key) {
        return this.dataMap.get(key);
    },

    len:function(){
      return this.size;
    },

    forEach: function(limit, offset){
        limit = parseInt(limit);
        offset = parseInt(offset);
        if(offset>this.size){
           throw new Error("offset is not valid");
        }
        var number = offset+limit;
        if(number > this.size){
          number = this.size;
        }
        var result  = "";
        for(var i=offset;i<number;i++){
            var key = this.arrayMap.get(i);
            var object = this.dataMap.get(key);
            result += "index:"+i+" key:"+ key + " value:" +object+"_";
        }
        return result;
    }
};

module.exports = SampleContract;

智能合約實例

下面通過實現一個非常簡單的功能,來說明智能合約的開發。

功能簡要說明

  1. 提交文字信息存儲到星雲鏈,包括:標題和內容
  2. 根據作者地址查看提交的信息

    智能合約代碼

'use strict';

// 定義信息類
var Info = function (text) {
    if (text) {
        var obj = JSON.parse(text); // 如果傳入的內容不爲空將字符串解析成json對象
        this.title = obj.title;                     // 標題
        this.content = obj.content;                 // 內容
        this.author = obj.author;                  // 作者
        this.timestamp = obj.timestamp;            // 時間戳
    } else {
        this.title = "";
        this.content = "";
        this.author = "";
        this.timestamp = 0;
    }
};

// 將信息類對象轉成字符串
Info.prototype.toString = function () {
    return JSON.stringify(this)
};

// 定義智能合約
var InfoContract = function () {
    // 使用內置的LocalContractStorage綁定一個map,名稱爲infoMap
    // 這裏不使用prototype是保證每佈署一次該合約此處的infoMap都是獨立的
    LocalContractStorage.defineMapProperty(this, "infoMap", {
        // 從infoMap中讀取,反序列化
        parse: function (text) {
            return new Info(text);
        },
        // 存入infoMap,序列化
        stringify: function (o) {
            return o.toString();
        }
    });
};

// 定義合約的原型對象
InfoContract.prototype = {
    // init是星雲鏈智能合約中必須定義的方法,只在佈署時執行一次
    init : function () {

    },
    // 提交信息到星雲鏈保存,傳入標題和內容
    save : function (title, content) {
        title = title.trim();
        content = content.trim();

        if (title === "" || content === "") {
            throw new Error("標題或內容爲空!");
        }

        if (title.length > 64) {
            throw new Error("標題長度超過64個字符!");
        }

        if (content.length > 256) {
            throw new Error("內容長度超過256個字符!");
        }
        // 使用內置對象Blockchain獲取提交內容的作者錢包地址
        var from = Blockchain.transaction.from;
        // 此處調用前面定義的反序列方法parse,從存儲區中讀取內容
        var existInfo = this.infoMap.get(from);
        if (existInfo) {
            throw new Error("您已經發布過內容!");
        }

        var info = new Info();
        info.title = title;
        info.content = content;
        info.timestamp = new Date().getTime();
        info.author = from;

        // 此處調用前面定義的序列化方法stringify,將Info對象存儲到存儲區
        this.infoMap.put(from, info);
    },
    // 根據作者的錢包地址從存儲區讀取內容,返回Info對象
    read : function (author) {
        author = author.trim();
        if (author === "") {
            throw new Error("地址爲空!");
        }
        // 驗證地址
        if (!this.verifyAddress(author)) {
            throw new Error("輸入的地址不存在!");
        }
        var existInfo = this.infoMap.get(author);
        return existInfo;
    },
    // 驗證地址是否合法
    verifyAddress: function (address) {
        // 1-valid, 0-invalid
        var result = Blockchain.verifyAddress(address);
        return {
            valid: result == 0 ? false : true
        };
    }
};
// 導出代碼,標示智能合約入口
module.exports = InfoContract;

部署智能合約

可以在命令行下進行部署,本文介紹在web錢包下的部署方法,操作更簡單。

第一步

image

第二步

image

第三步

image

執行智能合約方法

執行save方法,發佈信息

image

image

注意:目的地址爲合約地址

執行read方法,查看信息

image

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章