热门标签 | 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
智能合约开发教程视频:区块链系列视频课程之智能合约简介


推荐阅读
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
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社区 版权所有