热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:智能合约从入门到精通:完整范例

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

简介:前几篇文章我们一直在讨论Solidity语言的相关语法,从本文开始,我们将介绍智能合约开发。今天我们将介绍一个完整范例。
此章节将介绍一个完整案例来帮助开发者快速了解合约的开发规范及流程。
注意:
在进行案例编写前,请先前往JUICE开放服务平台,完成用户注册,JUICE区块链账户创建;并下载、安装、配置好JUICE客户端。
场景描述
在案例实践前请确保已拥有可用的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 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 //从待删除的数组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 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 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;
}
}


  • 模块合约作用:当进行一个新的DAPP开发时会伴随着一些合约的业务服务的编写,即,合约为DAPP应用提供业务逻辑的服务,我们将这一类(或一组)合约统一归属到一个模块中(eg:HelloWorldModuleMgr)。在JUICE区块链平台上有一套鉴权体系,一个合约要被成功调用需要经过多层鉴权:

o 校验模块开关,开:继续鉴权,关:直接通过
o 校验合约开关,开:继续鉴权,关:直接通过
o 检验函数开关,开:继续鉴权,关:直接通过
o 校验用户是否存在,存在则访问通过,不存在则鉴权失败
注意:如果是合约发布者owner(超级管理员)则不需要鉴权可直接通过。


  • HelloWorldModuleMgr该合约的主要功能就是做数据的初始化操作,当合约被发布时触发构造函数的调用。

o 添加一个新的模块到角色过滤器(默认过滤器)
o 添加绑定合约与模块的关系
o 添加菜单(新的DAPP如果需要菜单-如:用户管理)
o 添加权限,合约中的每个函数操作都是一个Action,如果需要访问就需要进行配置;
o 添加角色,初始化某些角色到模块中,并绑定对应的权限到角色上;
编译部署、测试
编译部署

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


  • 首先,处理业务合约
    1.编译业务合约,编译成功后,在控制台分别复制出ABI,BIN,并分别保存到contracts/ConsumerManager.abi,contracts/ConsumerManager.bin文本文件中。这两个文件,可以用web3j生成调用业务合约的JAVA代理类,这个在编写DAPP时有用,因此在编译阶段就先保存这两个文件。(注:JUICE客户端的后续版本中,将在编译业务合约时,直接生成JAVA代理类,开发者不用再手工保存bin/abi,再手工生成JAVA代理类)
    2.部署业务合约

  • 然后,处理模块合约

2.1.编译模块合约。编译成功后的的bin/abi,不需要保存。
2.部署模块合约

测试
在JUICE客户端中,选择需要测试的业务合约,以及相应的业务方法,然后填写输入参数,即可运行。用户可观察控制台的日志输出,来判断业务方法是否执行成功。

参考内容:https://open.juzix.net/doc
智能合约开发教程视频:区块链系列视频课程之智能合约简介


推荐阅读
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 更新vuex的数据为什么用mutation?
    更新vuex的数据为什么用mutation?,Go语言社区,Golang程序员人脉社 ... [详细]
  • 浅析python实现布隆过滤器及Redis中的缓存穿透原理_python
    本文带你了解了位图的实现,布隆过滤器的原理及Python中的使用,以及布隆过滤器如何应对Redis中的缓存穿透,相信你对布隆过滤 ... [详细]
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • 单片微机原理P3:80C51外部拓展系统
      外部拓展其实是个相对来说很好玩的章节,可以真正开始用单片机写程序了,比较重要的是外部存储器拓展,81C55拓展,矩阵键盘,动态显示,DAC和ADC。0.IO接口电路概念与存 ... [详细]
  • async/await 是现代 JavaScript 中非常强大的异步编程工具,可以极大地简化异步代码的编写。本文将详细介绍 async 和 await 的用法及其背后的原理。 ... [详细]
  • 本文介绍如何使用 Python 的 DOM 和 SAX 方法解析 XML 文件,并通过示例展示了如何动态创建数据库表和处理大量数据的实时插入。 ... [详细]
  • 重要知识点有:函数参数默许值、盈余参数、扩大运算符、new.target属性、块级函数、箭头函数以及尾挪用优化《深切明白ES6》笔记目次函数的默许参数在ES5中,我们给函数传参数, ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • poj 3352 Road Construction ... [详细]
  • 本文介绍了如何利用 `matplotlib` 库中的 `FuncAnimation` 类将 Python 中的动态图像保存为视频文件。通过详细解释 `FuncAnimation` 类的参数和方法,文章提供了多种实用技巧,帮助用户高效地生成高质量的动态图像视频。此外,还探讨了不同视频编码器的选择及其对输出文件质量的影响,为读者提供了全面的技术指导。 ... [详细]
  • 本文介绍了如何使用Python的Paramiko库批量更新多台服务器的登录密码。通过示例代码展示了具体实现方法,确保了操作的高效性和安全性。Paramiko库提供了强大的SSH2协议支持,使得远程服务器管理变得更加便捷。此外,文章还详细说明了代码的各个部分,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 在尝试对 QQmlPropertyMap 类进行测试驱动开发时,发现其派生类中无法正常调用槽函数或 Q_INVOKABLE 方法。这可能是由于 QQmlPropertyMap 的内部实现机制导致的,需要进一步研究以找到解决方案。 ... [详细]
  • 【实例简介】本文详细介绍了如何在PHP中实现微信支付的退款功能,并提供了订单创建类的完整代码及调用示例。在配置过程中,需确保正确设置相关参数,特别是证书路径应根据项目实际情况进行调整。为了保证系统的安全性,存放证书的目录需要设置为可读权限。值得注意的是,普通支付操作无需证书,但在执行退款操作时必须提供证书。此外,本文还对常见的错误处理和调试技巧进行了说明,帮助开发者快速定位和解决问题。 ... [详细]
  • PyTorch实用技巧汇总(持续更新中)
    空洞卷积(Dilated Convolutions)在卷积操作中通过在卷积核元素之间插入空格来扩大感受野,这一过程由超参数 dilation rate 控制。这种技术在保持参数数量不变的情况下,能够有效地捕捉更大范围的上下文信息,适用于多种视觉任务,如图像分割和目标检测。本文将详细介绍空洞卷积的计算原理及其应用场景。 ... [详细]
author-avatar
手机用户2502935311
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有