编写智能合约
Nebulas实现了NVM虚拟机来运行智能合约,NVM的实现使用了JavaScript V8引擎,所以当前的开发版,我们可以使用JavaScript、TypeScript来编写智能合约。
编写智能合约的简要规范
- 智能合约代码必须是一个Prototype的对象;
- 智能合约代码必须有一个init()的方法,这个方法只会在部署的时候被执行一次;
- 智能合约里面的私有方法是以_开头的方法,私有方法不能被外部直接调用;
智能合约存储区
星云链智能合约(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;
智能合约实例
下面通过实现一个非常简单的功能,来说明智能合约的开发。
功能简要说明
- 提交文字信息存储到星云链,包括:标题和内容
- 根据作者地址查看提交的信息
智能合约代码
'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钱包下的部署方法,操作更简单。
第一步
第二步
第三步
执行智能合约方法
执行save方法,发布信息
注意:目的地址为合约地址
执行read方法,查看信息
来源:51CTO
作者:thao888
链接:https://blog.51cto.com/634435/2114749