簡介:前幾篇文章咱們一直在討論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