简介:前几篇文章我们一直在讨论Solidity语言的相关语法,从本文开始,我们将介绍智能合约开发。今天我们将介绍一个完整范例。

此章节将介绍一个完整案例来帮助开发者快速了解合约的开发规范及流程。

注意:

在进行案例编写前,请先前往JUICE开放服务平台,完成用户注册,JUICE区块链账户创建;并下载、安装、配置好JUICE客户端。https://open.juzix.net/

场景描述

在案例实践前请确保已拥有可用的JUICE区块链平台环境!!!

现假设一个场景,编写一个顾客管理合约。主要实现以下功能:

  • 提供增加顾客信息功能,手机号作为唯一KEY;
  • 提供根据手机号删除顾客信息的功能;
  • 提供输出所有顾客信息的功能;

接口定义

说明:此接口定义了顾客管理合约的基本操作,接口的定义可以开放给三方进行调用而不暴露源码;

文件目录:${workspace}/contracts/interfaces 用于存放抽象合约目录

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);}

  • add(string _mobile, string _name, string _account, string _remark) 新增一个顾客信息
  • deleteByMobile(string_mobile) 根据手机号删除顾客信息
  • listAll() 输出所有顾客信息,此方法不影响变量状态,因此使用constant修饰;

数据结构定义

说明:当接口中的输入输出数据项比较多,或者存储在链上的数据项比较多时,开发者可以定义一个结构化数据,来简化数据项的声明。并且在这个结构化数据,还可以封装对数据的序列化操作,主要包括通过将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;}}

  • toJson(Consumer storage _self) 将struct结构序列化为JSON格式:{"mobile":"xxx",...}.
  • fromJson(Consumer storage _self, string _json) 将一个JSON串反序列为struct结构.
  • fromJsonArray(Consumer[] storage _self, string _json),将一个数组形式的JSON串转为数据struct结构
  • toJsonArray(Consumer[] storage _self) 数组结构反序列化,eg.[{"mobile":"xxx",...},...]
  • reset(Consumer _self) 重置struct中为默认值.

业务合约编写

说明:顾客管理合约的主要业务逻辑,即合约接口的实现类. ConsumerManager.sol,该合约继承了基础合约OwnerNamed以及抽象合约IConsumerManager。

  • OwnerNamed 主要提供一些基础操作,主要包含模块注册、合约注册、数据写入DB等操作,所有业务合约需按规定继承该合约。

文件目录:${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 LibConsumerfor * ;using LibStringfor * ;using LibIntfor * ;using LibLogfor * ;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为0delete keyMap[_mobile];//数组总长度-1consumerList.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

  • 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 LibModulefor * ;using LibContractfor * ;using LibStringfor * ;using LibIntfor * ;using LibLogfor * ;LibModule.Module tmpModule;LibContract.Contract tmpContract;//定义Demo模块中的错误信息enum MODULE_ERROR {NO_ERROR}//定义Demo模块中用的事件,可以用于返回错误信息,也可以返回其他信息event Notify(uint _code, string _info);// module : predefined datafunction 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;//保持falsetmpContract.deleted = false;//保持0tmpContract.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;}}

  • 模块合约作用:当进行一个新的DAPP开发时会伴随着一些合约的业务服务的编写,即,合约为DAPP应用提供业务逻辑的服务,我们将这一类(或一组)合约统一归属到一个模块中(eg:HelloWorldModuleMgr)。在JUICE区块链平台上有一套鉴权体系,一个合约要被成功调用需要经过多层鉴权:
  • 校验模块开关,开:继续鉴权,关:直接通过
  • 校验合约开关,开:继续鉴权,关:直接通过
  • 检验函数开关,开:继续鉴权,关:直接通过
  • 校验用户是否存在,存在则访问通过,不存在则鉴权失败

注意:如果是合约发布者owner(超级管理员)则不需要鉴权可直接通过。

  • HelloWorldModuleMgr该合约的主要功能就是做数据的初始化操作,当合约被发布时触发构造函数的调用。
  • 添加一个新的模块到角色过滤器(默认过滤器)
  • 添加绑定合约与模块的关系
  • 添加菜单(新的DAPP如果需要菜单-如:用户管理)
  • 添加权限,合约中的每个函数操作都是一个Action,如果需要访问就需要进行配置;
  • 添加角色,初始化某些角色到模块中,并绑定对应的权限到角色上;

编译部署、测试

编译部署

业务合约,模块合约编写完成后

  • 首先,处理业务合约

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

转载于:https://www.cnblogs.com/shjuzhen/p/9273994.html

智能合约从入门到精通:完整范例相关推荐

  1. 智能合约从入门到精通:Solidity Assembly

    简介:上一节,我们讲过Solidity汇编语言,这个汇编语言,可以不同Solidity一起使用.这个汇编语言还可以嵌入到Solidity源码中,以内联汇编的方式使用.下面我们将从内联汇编如何使用着手, ...

  2. 智能合约从入门到精通:JIDE集成开发工具

    简介:前面两节,我们介绍了JIDE的工具库,本节我们将介绍智能合约开发最后一段内容,JIDE集成开发工具.除了JIDE的安装,我们还将介绍智能合约的编写.编译.部署.运行以及查看日志等.您看过所有的内 ...

  3. 智能合约从入门到精通:调用数据的布局和ABI

    简介:本文将介绍Solidity语言的调用数据的布局和ABI详解.其中调用数据的布局将主要介绍以太坊合约间调用时的消息格式ABI. 好久时间没有更新文章,前文中我们介绍了Solidity的特性与内部机 ...

  4. 以太坊智能合约安全入门了解一下(上)

    作者:RickGray 作者博客:http://rickgray.me/2018/05/17/ethereum-smart-contracts-vulnerabilites-review/ (注:本文 ...

  5. 以太坊智能合约安全入门了解一下(下)

    作者:RickGray 作者博客:http://rickgray.me/2018/05/26/ethereum-smart-contracts-vulnerabilities-review-part2 ...

  6. java和以太坊交互_java类库web3j开发以太坊智能合约快速入门

    web3j简介 web3j是一个轻量级.高度模块化.响应式.类型安全的Java和Android类库提供丰富API,用于处理以太坊智能合约及与以太坊网络上的客户端(节点)进行集成. 可以通过它进行以太坊 ...

  7. NFT合约 从入门到精通

    在<区块链杀手级应用落地畅想(上)>中我们提到,2021年被称为NFT"元年".在短时间内,NFT已不再局限于加密世界的投机价值,其释放的潜力吸引了越来越多的国际品牌, ...

  8. 原 EOS智能合约开发入门

    链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. EOS智能合约的开发需要使用llvm和abigen来生成abi文件. 为此eos提供了一个 名为eosiocpp的工具. ...

  9. 区块链智能合约solidity入门

    想知道更多关于区块链技术知识,请百度[链客区块链技术问答社区] 链客,有问必答!! 使用ubuntu系统安装ethereum开发环境 安装 Nodejs sudo apt-get update sud ...

最新文章

  1. Spark内核以及源码解析
  2. SAP FICO期初开账存货导入尾差
  3. 设计模式-软件架构设计七大原则及开闭原则详解
  4. Java技术分享之函数式编程
  5. 计算机网络基础:Internet常用服务介绍​
  6. eclipse创建springBoot工程
  7. C#(asp.net)实现目录(无扩展名)重写
  8. 无可用源 没有为任何调用堆栈加载任何符号_看完这篇后,别再说你不懂JVM类加载机制了~...
  9. 1分钟教会你怎么PDF转图片,告别手动截图
  10. 番外5. Python OpenCV 中滑动条详细说明与常见问题解决方案
  11. Autojs抖音小助手抢红包福袋脚本实战分享
  12. python推箱子游戏代码_推箱子游戏,推箱子游戏代码python实现
  13. 图形处理之网格平滑vtkSmoothPolyDataFilter
  14. Kd-Tree算法原理
  15. 错误ORA-01438: 值大于为此列指定的允许精度
  16. 利用Python you-get 下载网页视频
  17. Redis五大基本数据类型(String、LIst、Set、Hash、ZSet)及其底层结构
  18. BUUCTF WEB [BJDCTF2020]ZJCTF,不过如此
  19. 数据库导入导出方法以及注意事项
  20. 10个打开了我新世界大门的 WebAPI

热门文章

  1. [JSOI2008 Prefix火星人]
  2. CSS3笔记之基础篇(二)颜色和渐变色彩
  3. vue --- 提交表单到服务器
  4. Java 的单例模式
  5. 阿里云RPA(机器人流程自动化)干货系列之二:认识RPA(下)
  6. 一张图学习常见this的指向
  7. 51 Nod 1007 正整数分组【类01背包】
  8. 启动Eclipse 弹出“Failed to load the JNI shared library”错误的解决方法
  9. 多功能节点连线绘图控件Nevron Diagram for .NET使用方法及下载地址
  10. crf与bitrate对照表