简介:前几篇文章咱们一直在讨论Solidity语言的相关语法,从本文开始,咱们将介绍智能合约开发。今天咱们将介绍一个完整范例。html
此章节将介绍一个完整案例来帮助开发者快速了解合约的开发规范及流程。web
注意:编程
在进行案例编写前,请先前往JUICE开放服务平台,完成用户注册,JUICE区块链帐户建立;并下载、安装、配置好JUICE客户端。https://open.juzix.net/json
在案例实践前请确保已拥有可用的JUICE区块链平台环境!!!数组
现假设一个场景,编写一个顾客管理合约。主要实现如下功能:数据结构
说明:此接口定义了顾客管理合约的基本操做,接口的定义能够开放给三方进行调用而不暴露源码;app
文件目录:${workspace}/contracts/interfaces 用于存放抽象合约目录dom
pragma solidity ^0.4.2; contract IConsumerManager { function add(string _mobile, string _name, string _account, string _remark) public returns(uint); function deleteByMobile(string _mobile) public returns(uint); function listAll() constant public returns (string _json); }
说明:当接口中的输入输出数据项比较多,或者存储在链上的数据项比较多时,开发者能够定义一个结构化数据,来简化数据项的声明。而且在这个结构化数据,还能够封装对数据的序列化操做,主要包括经过将json格式转为结构化数据 或 反序列化为json格式。函数
能够把结构化数据,当作面向对象编程中的对象。区块链
文件目录:${workspace}/contracts/librarys 用于存放数据结构的定义
pragma solidity ^0.4.2; import "../utillib/LibInt.sol"; import "../utillib/LibString.sol"; import "../utillib/LibStack.sol"; import "../utillib/LibJson.sol"; library LibConsumer { using LibInt for *; using LibString for *; using LibJson for *; using LibConsumer for *; struct Consumer { string mobile; string name; string account; string remark; } /** *@desc fromJson for Consumer * Generated by juzhen SolidityStructTool automatically. * Not to edit this code manually. */ function fromJson(Consumer storage _self, string _json) internal returns(bool succ) { _self.reset(); if (!_json.isJson()) return false; _self.mobile = _json.jsonRead("mobile"); _self.name = _json.jsonRead("name"); _self.account = _json.jsonRead("account"); _self.remark = _json.jsonRead("remark"); return true; } /** *@desc toJson for Consumer * Generated by juzhen SolidityStructTool automatically. * Not to edit this code manually. */ function toJson(Consumer storage _self) internal constant returns (string _json) { LibStack.push("{"); LibStack.appendKeyValue("mobile", _self.mobile); LibStack.appendKeyValue("name", _self.name); LibStack.appendKeyValue("account", _self.account); LibStack.appendKeyValue("remark", _self.remark); LibStack.append("}"); _json = LibStack.pop(); } /** *@desc fromJsonArray for Consumer * Generated by juzhen SolidityStructTool automatically. * Not to edit this code manually. */ function fromJsonArray(Consumer[] storage _self, string _json) internal returns(bool succ) { _self.length = 0; if (!_json.isJson()) return false; while (true) { string memory key = "[".concat(_self.length.toString(), "]"); if (!_json.jsonKeyExists(key)) break; _self.length++; _self[_self.length-1].fromJson(_json.jsonRead(key)); } return true; } /** *@desc toJsonArray for Consumer * Generated by juzhen SolidityStructTool automatically. * Not to edit this code manually. */ function toJsonArray(Consumer[] storage _self) internal constant returns(string _json) { _json = _json.concat("["); for (uint i=0; i<_self.length; ++i) { if (i == 0) _json = _json.concat(_self[i].toJson()); else _json = _json.concat(",", _self[i].toJson()); } _json = _json.concat("]"); } /** *@desc update for Consumer * Generated by juzhen SolidityStructTool automatically. * Not to edit this code manually. */ function update(Consumer storage _self, string _json) internal returns(bool succ) { if (!_json.isJson()) return false; if (_json.jsonKeyExists("mobile")) _self.mobile = _json.jsonRead("mobile"); if (_json.jsonKeyExists("name")) _self.name = _json.jsonRead("name"); if (_json.jsonKeyExists("account")) _self.account = _json.jsonRead("account"); if (_json.jsonKeyExists("remark")) _self.remark = _json.jsonRead("remark"); return true; } /** *@desc reset for Consumer * Generated by juzhen SolidityStructTool automatically. * Not to edit this code manually. */ function reset(Consumer storage _self) internal { delete _self.mobile; delete _self.name; delete _self.account; delete _self.remark; } }
说明:顾客管理合约的主要业务逻辑,即合约接口的实现类. ConsumerManager.sol,该合约继承了基础合约OwnerNamed以及抽象合约IConsumerManager。
文件目录:${workspace}/contracts 用于存放业务合约主体逻辑
pragma solidity ^0.4.2; import "./library/LibConsumer.sol"; import "./sysbase/OwnerNamed.sol"; import "./interfaces/IConsumerManager.sol"; import "./interfaces/IUserManager.sol"; import "./utillib/LibLog.sol"; contract ConsumerManager is OwnerNamed, IConsumerManager { using LibConsumer for * ; using LibString for * ; using LibInt for * ; using LibLog for * ; event Notify(uint _errno, string _info); LibConsumer.Consumer[] consumerList; mapping(string => uint) keyMap; //定义错误信息 enum ErrorNo { NO_ERROR, BAD_PARAMETER, MOBILE_EMPTY, USER_NOT_EXISTS, MOBILE_ALREADY_EXISTS, ACCOUNT_ALREDY_EXISTS, NO_PERMISSION } // 构造函数,在合约发布时会被触发调用 function ConsumerManager() { LibLog.log("deploy ConsumerModule...."); //把合约注册到JUICE链上, 参数必须和ConsumerModule.sol中的保持一致 register("ConsumerModule", "0.0.1.0", "ConsumerManager", "0.0.1.0"); //或者注册到特殊的模块"juzix.io.debugModule",这样用户就不须要编写模块合约了 //register("juzix.io.debugModule", "0.0.1.0", "ConsumerManager", "0.0.1.0"); } function add(string _mobile, string _name, string _account, string _remark) public returns(uint) { LibLog.log("into add..", "ConsumerManager"); LibLog.log("ConsumerManager into add.."); if (_mobile.equals("")) { LibLog.log("Invalid mobile.", "ConsumerManager"); errno = 15200 + uint(ErrorNo.MOBILE_EMPTY); Notify(errno, "顾客手机号为空,插入失败."); return errno; } if (keyMap[_mobile] == 0) { if (consumerList.length > 0) { if (_mobile.equals(consumerList[0].mobile)) { LibLog.log("mobile aready exists", "ConsumerManager"); errno = 15200 + uint(ErrorNo.MOBILE_ALREADY_EXISTS); Notify(errno, "顾客手机号已存在,插入失败."); return errno; } } } else { LibLog.log("mobile aready exists", "ConsumerManager"); errno = 15200 + uint(ErrorNo.MOBILE_ALREADY_EXISTS); Notify(errno, "顾客手机号已存在,插入失败."); return errno; } uint idx = consumerList.length; consumerList.push(LibConsumer.Consumer(_mobile, _name, _account, _remark)); keyMap[_mobile] = idx; errno = uint(ErrorNo.NO_ERROR); LibLog.log("add a consumer success", "ConsumerManager"); Notify(errno, "add a consumer success"); return errno; } function deleteByMobile(string _mobile) public returns(uint) { LibLog.log("into delete..", "ConsumerManager"); //合约拥有者,才能删除顾客信息 if (tx.origin != owner) { LibLog.log("msg.sender is not owner", "ConsumerManager"); LibLog.log("operator no permission"); errno = 15200 + uint(ErrorNo.NO_PERMISSION); Notify(errno, "无操做权限,非管理员"); return; } //顾客列表不为空 if (consumerList.length > 0) { if (keyMap[_mobile] == 0) { //_mobile不存在,或者是数组第一个元素 if (!_mobile.equals(consumerList[0].mobile)) { LibLog.log("consumer not exists: ", _mobile); errno = 15200 + uint(ErrorNo.USER_NOT_EXISTS); Notify(errno, "顾客手机号不存在,删除失败."); return; } } } else { LibLog.log("consumer list is empty: ", _mobile); errno = 15200 + uint(ErrorNo.USER_NOT_EXISTS); Notify(errno, "顾客列表为空,删除失败."); return; } //数组总长度 uint len = consumerList.length; //此用户在数组中的序号 uint idx = keyMap[_mobile]; if (idx >= len) return; for (uint i = idx; i < len - 1; i++) { //从待删除的数组element开始,把后一个element移动到前一个位置 consumerList[i] = consumerList[i + 1]; //同时修改keyMap中,对应key的在数组中的序号 keyMap[consumerList[i].mobile] = i; } //删除数组最后一个元素(和倒数第二个重复了) delete consumerList[len - 1]; //删除mapping中元素,其实是设置value为0 delete keyMap[_mobile]; //数组总长度-1 consumerList.length--; LibLog.log("delete user success.", "ConsumerManager"); errno = uint(ErrorNo.NO_ERROR); Notify(errno, "删除顾客成功."); } function listAll() constant public returns(string _json) { uint len = 0; uint counter = 0; len = LibStack.push(""); for (uint i = 0; i < consumerList.length; i++) { if (counter > 0) { len = LibStack.append(","); } len = LibStack.append(consumerList[i].toJson()); counter++; } len = itemsStackPush(LibStack.popex(len), counter); _json = LibStack.popex(len); } function itemsStackPush(string _items, uint _total) constant private returns(uint len) { len = 0; len = LibStack.push("{"); len = LibStack.appendKeyValue("result", uint(0)); len = LibStack.appendKeyValue("total", _total); len = LibStack.append(",\"data\":["); len = LibStack.append(_items); len = LibStack.append("]"); len = LibStack.append("}"); return len; } }
说明:模块合约是JUICE区块链中,为了管理用户的业务合约,以及为了管理DAPP和业务的关系而引入的。开发者在实现业务合约后,必须编写一个或多个模块合约,并在模块合约中说明本模块中用到的业务合约。从DAPP的角度来理解,就是一个DAPP必须对应一个模块,一个DAPP能调用的业务合约,必须在DAPP对应的模块合约中说明。
模块合约继承了基础模块合约BaseModule
文件目录:${workspace}/contracts 用于存放业务模块合约主体逻辑
/** * @file ConsumerModule.sol * @author JUZIX.IO * @time 2017-12-11 * @desc 给用户展现如何编写一个本身的模块。 * ConsumerModule自己也是一个合约,它须要部署到链上;同时,它又负责管理用户的合约。只有添加到模块中的用户合约,用户才能在dapp中调用这些合约 */ pragma solidity ^ 0.4 .2; //juice的管理库,必须引入 import "./sysbase/OwnerNamed.sol"; import "./sysbase/BaseModule.sol"; //juice提供的模块库,必须引入 import "./library/LibModule.sol"; //juice提供的合约库,必须引入 import "./library/LibContract.sol"; //juice提供的string库 import "./utillib/LibString.sol"; //juice提供的log库 import "./utillib/LibLog.sol"; contract ConsumerModule is BaseModule { using LibModule for * ; using LibContract for * ; using LibString for * ; using LibInt for * ; using LibLog for * ; LibModule.Module tmpModule; LibContract.Contract tmpContract; //定义Demo模块中的错误信息 enum MODULE_ERROR { NO_ERROR } //定义Demo模块中用的事件,能够用于返回错误信息,也能够返回其余信息 event Notify(uint _code, string _info); // module : predefined data function ConsumerModule() { //定义模块合约名称 string memory moduleName = "ConsumerModule"; //定义模块合约名称 string memory moduleDesc = "顾客模块"; //定义模块合约版本号 string memory moduleVersion = "0.0.1.0"; //指定模块合约ID //moduleId = moduleName.concat("_", moduleVersion); string memory moduleId = moduleName.concat("_", moduleVersion); //把合约注册到JUICE链上 LibLog.log("register DemoModule"); register(moduleName, moduleVersion); //模块名称,只是JUICE区块链内部管理模块使用,和moduleText有区别 tmpModule.moduleName = moduleName; tmpModule.moduleVersion = moduleVersion; tmpModule.moduleEnable = 0; tmpModule.moduleDescription = moduleDesc; //显示JUICE开放平台,个人应用列表中的DAPP名字 tmpModule.moduleText = moduleDesc; uint nowTime = now * 1000; tmpModule.moduleCreateTime = nowTime; tmpModule.moduleUpdateTime = nowTime; tmpModule.moduleCreator = msg.sender; //这里设置用户DAPP的链接地址(目前DAPP须要有用户本身发布、部署到公网上) tmpModule.moduleUrl = "http://host.domain.com/youDapp/"; tmpModule.icon = ""; tmpModule.publishTime = nowTime; //把模块合约自己添加到系统的模块管理合约中。这一步是必须的,只有这样,用户的dapp才能调用添加到此模块合约的相关合约。 //并在用户的“个人应用”中展现出来 LibLog.log("add ConsumerModule to SysModule"); uint ret = addModule(tmpModule.toJson()); if (ret != 0) { LibLog.log("add ConsumerModule to SysModule failed"); return; } //添加用户合约到模块合约中 LibLog.log("add ConsumerManager to ConsumerModule"); ret = initContract(moduleName, moduleVersion, "ConsumerManager", "顾客管理合约", "0.0.1.0"); if (ret != 0) { LibLog.log("add ConsumerManager to ConsumerModule failed"); return; } //返回消息,以便控制台能看到是否部署成功 Notify(1, "deploy ConsumerModule success"); } /** * 初始化用户自定义合约。 * 若是用户有多个合约文件,则须要屡次调用此方法。 * @param moduleName 约合所属模块名 * @param moduleVersion 约合所属模块版本 * @param contractName 约合名 * @param contractDesc 约合描述 * @param contractVersion 约合版本 * @return return 0 if success; */ function initContract(string moduleName, string moduleVersion, string contractName, string contractDesc, string contractVersion) private returns(uint) { tmpContract.moduleName = moduleName; tmpContract.moduleVersion = moduleVersion; //合约名称 tmpContract.cctName = contractName; //合约描述 tmpContract.description = contractDesc; //合约版本 tmpContract.cctVersion = contractVersion; //保持false tmpContract.deleted = false; //保持0 tmpContract.enable = 0; uint nowTime = now * 1000; //合约建立时间 tmpContract.createTime = nowTime; //合约修改时间 tmpContract.updateTime = nowTime; //合约建立人 tmpContract.creator = msg.sender; //预定块高 tmpContract.blockNum = block.number; uint ret = addContract(tmpContract.toJson()); return ret; } }
注意:若是是合约发布者owner(超级管理员)则不须要鉴权可直接经过。
业务合约,模块合约编写完成后
1.编译业务合约,编译成功后,在控制台分别复制出ABI,BIN,并分别保存到contracts/ConsumerManager.abi,contracts/ConsumerManager.bin文本文件中。这两个文件,能够用web3j生成调用业务合约的JAVA代理类,这个在编写DAPP时有用,所以在编译阶段就先保存这两个文件。(注:JUICE客户端的后续版本中,将在编译业务合约时,直接生成JAVA代理类,开发者不用再手工保存bin/abi,再手工生成JAVA代理类)
2.部署业务合约
1.编译模块合约。编译成功后的的bin/abi,不须要保存。
2.部署模块合约
在JUICE客户端中,选择须要测试的业务合约,以及相应的业务方法,而后填写输入参数,便可运行。用户可观察控制台的日志输出,来判断业务方法是否执行成功。
参考内容:https://open.juzix.net/doc
智能合约开发教程视频:http://edu.51cto.com/course/13403.html