Nebulas實現了NVM虛擬機來運行智能合約,NVM的實現使用了JavaScript V8引擎,因此當前的開發版,咱們可使用JavaScript、TypeScript來編寫智能合約。web
星雲鏈智能合約(smart contract)提供了鏈上數據存儲功能。相似於傳統的key-value存儲系統(eg:redis),能夠付費(消耗gas)將數據存儲到星雲鏈上。redis
星雲鏈的智能合約運行環境內置了存儲對象==LocalContractStorage==,能夠存儲數字,字符串,JavaScript對象,存儲數據只能在智能合約內使用,其餘合約不能讀取存儲的內容。json
LocalContractStorage的簡單接口包括set,get,del接口,實現了存儲,讀取,刪除數據功能。存儲能夠是數字,字符串,對象。less
// 存儲數據,數據會被json序列化成字符串保存 LocalContractStorage.put(key, value); // 或者 LocalContractStorage.set(key, value);
// 獲取數據 LocalContractStorage.get(key);
// 刪除數據, 數據刪除後沒法讀取 LocalContractStorage.del(key); // 或者 LocalContractStorage.delete(key);
LocalContractStorage除了基本的set,get,del方法,還提供方法來綁定合約屬性。對綁定過的合約屬性的讀寫將直接在LocalContractStorage上讀寫,而無需調用get和set方法。ide
在綁定一個合約屬性時,須要提供對象實例,屬性名和序列化方法。函數
// 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 } } };
'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,分別是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;
下面經過實現一個很是簡單的功能,來講明智能合約的開發。this
'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錢包下的部署方法,操做更簡單。prototype
注意:目的地址爲合約地址