本文提供基于實際場景的合約設計示例,供開發人員參考。下面以基于智能合約的住房租賃積分管理系統為例,闡述如何設計和實現智能合約。
本文中的示例僅供參考,不可應用于生產環境。
背景
基于區塊鏈智能合約的住房租賃積分管理系統旨在有效地服務于公眾群體、市民,真正讓那些為城市當前發展做出努力的人有房住、租得起,讓那些為城市建設長遠發展做出貢獻的人有房產、買得起。為此,建立住房租賃積分制度,從住房租賃市場主體屬性、政策激勵、租賃行為三方面,運用區塊鏈、大數據等前沿技術,建立科學、有效的住房租賃積分全生命周期管理機制,營造活力、健康、有序、可持續的住房租賃生態。
目標
通過使用區塊鏈平臺作為底層數據支撐,確保隱私保護和數據不可篡改,達到公平、公正、透明的目的;利用積分決策引擎,結合住房租賃積分評價體系,產出應用于住房租賃場景的綜合積分方案。該方案具備橫向擴展性,在底層技術、數據不變的基礎上,未來能夠支撐經濟信用積分、綠色生活積分等諸多積分體系的建設,從而擴展形成城市信用體系,應用于如醫療教育、金融借貸、綠色生活等諸多場景。
合約設計
權限管理
在住房租賃積分管理系統中,主要分為4種角色,分別是管理者、操作者、觀察者以及市民。管理者、操作者、觀察者是智能合約的操作人員,每個角色可以對應多名人員。市民是租賃積分智能合約服務的對象,是整個智能合約的核心用戶。
角色操作權限
管理者是合約的超級管理員,能夠對管理者、操作者以及觀察者進行添加、刪除、查詢操作。
操作者可以查詢操作者信息。
觀察者可以查詢觀察者信息。
積分操作權限
管理者:具有積分獎勵、積分查詢、積分轉移、積分違規扣除、積分權益兌換等積分操作權限。
操作者:操作者是由管理者設置的積分操作角色,該角色可以是具體的人員也可以是系統服務,具有積分獎勵、積分轉移、積分違規扣除、積分權益兌換等積分操作權限。
觀察者:觀察者是由管理者設置的積分查詢角色,該角色可以是具體的人員也可以是系統服務,具有積分查詢的操作權限。
市民:市民是住房租賃積分系統的用戶,是住房租賃積分系統的實際權益對象。
市民可以通過組織機構的租賃積分外部服務系統代理的觀察者權限查看自己的積分狀況。
租賃積分外部服務系統會根據市民的合規表現,通過操作者的權限進行積分獎勵或積分違規扣除。
市民可以通過租賃積分外部服務系統代理的操作者權限進行積分權益兌換或積分轉移操作。
積分管理
租賃積分智能合約提供的服務方法包括:積分獎勵、積分查詢、積分權益兌換、積分違規扣除、積分轉移以及積分達到閾值后觸發積分事件。
積分獎勵(awardScore)
在市民租賃房屋的過程中,針對租賃的時間以及租賃過程中產生的行為,為市民進行積分累計。
積分查詢(queryScore)
市民可通過租賃積分外部服務系統代理的觀察者權限查看自己的積分狀況。
積分權益兌換(exchangeScore)
市民的積分達到指定閾值后可以享有相應的權益,如租房優惠、買房資格、落戶資格等。
積分違規扣除(deductScore)
在市民租賃房屋的過程中,針對租賃過程中產生的錯誤行為,對市民懲罰性的扣減積分。住房租賃積分作為正向激勵的積分體系,鼓勵民眾積極向上。對于積分主體的偽造資料行為,視情節嚴重程度扣減積分、取消積分資格以及收回積分對應的權益。
積分事件(SCORE_EQUITY_NOTICE)
當積分權益實時增加時,會觸發積分權益檢查。若積分達到了指定的閾值,則會產生相應的積分權益通知事件。通過外部服務系統通知到具體的市民。
積分轉移(transferScore)
當市民因工作關系、家庭因素需要到其他城市生活時,可以將該城市的積分轉移到目標城市的積分系統。為滿足該合約的調用,目標合約需要有對應的積分獎勵方法(awardScore
),且需要為發起合約提供操作權限,即將發起合約的地址設置為操作者或管理員。
智能合約代碼
pragma solidity ^0.4.20;
contract LeasingScoreManager {
identity[] adminList;
identity[] observerList;
identity[] calculatorList;
//用于記錄管理數組被置位元素
uint adminDeled;
uint observerDeled;
uint calculatorDeled;
mapping (bytes32 => uint) leasingScore;
enum ScoreAction {
//獎勵積分, 因相關行為,進行積分獎勵
ActionAwardScore,
//積分權益兌換,花費指定積分
ActionExchangeScore,
//扣除積分, 因違規等, 扣除積分
ActionDeductScore,
//查詢積分
ActionQueryScore
//積分轉移, 將積分轉移到其他的合約。
}
enum UserRole {
RoleAdmin, //管理員
RoleCalculator, //積分操作員
RoleObserver, //積分查看人員
RolePlayer //積分參與者
}
//是否為有效的操作人
event VALID(bool valid, UserRole role);
//admin/observer/calculator 用戶存在
event USER_EXIST(bool exist, UserRole role);
//積分事件
event SCORE_OPERATOR(ScoreAction action, string describe);
//積分操作錯誤
event SCORE_ERROR(ScoreAction action, string describe);
//積分權益通知
event SCORE_EQUITY_NOTICE(string action, uint score, string describe);
constructor() public {
adminList.push(msg.sender);
adminList.push(this);
}
function indexAdmin(identity admin) view returns (uint) {
for (uint i = 0; i < adminList.length; i++) {
if (adminList[i] == admin) {
return i;
}
}
return adminList.length;
}
function validAdmin(identity admin) view returns (bool) {
return indexAdmin(admin) < adminList.length;
}
function indexCalculator(identity calculator) view returns (uint) {
for (uint i = 0; i < calculatorList.length; i++) {
if (calculatorList[i] == calculator) {
return i;
}
}
return calculatorList.length;
}
function validCalculator(identity calculator) view returns (bool) {
return indexCalculator(calculator) < calculatorList.length;
}
function indexObserver(identity observer) view returns (uint) {
for (uint i = 0; i < observerList.length; i++) {
if (observerList[i] == observer) {
return i;
}
}
return observerList.length;
}
function validObserver(identity observer) view returns (bool) {
return indexObserver(observer) < observerList.length;
}
modifier onlyAdmin {
bool isValid = validAdmin(msg.sender);
emit VALID(isValid, UserRole.RoleAdmin);
require(isValid);
_;
}
modifier onlyCalculatorOrAdmin {
bool isValid = validAdmin(msg.sender);
if(isValid) {
emit VALID(isValid, UserRole.RoleAdmin);
} else {
isValid = validCalculator(msg.sender);
emit VALID(isValid, UserRole.RoleCalculator);
}
require(isValid);
_;
}
modifier onlyObserverOrAdmin {
bool isValid = validAdmin(msg.sender);
if(isValid) {
emit VALID(isValid, UserRole.RoleAdmin);
} else {
isValid = validObserver(msg.sender);
emit VALID(isValid, UserRole.RoleObserver);
}
require(isValid);
_;
}
function addAdmin(identity admin) public onlyAdmin {
bool isExist = validAdmin(admin);
emit USER_EXIST(isExist, UserRole.RoleAdmin);
require(!isExist);
if(adminDeled > 0) {
uint deled = 1;
for (uint i = 0; i < adminList.length; i++) {
if(deled&adminDeled != 0) {
adminList[i] = admin;
adminDeled ^= deled;
break;
}
deled <<= 1;
}
} else {
adminList.push(admin);
}
}
function removeAdmin(identity admin) public onlyAdmin {
uint index = indexAdmin(admin);
bool isValid = index != adminList.length;
emit USER_EXIST(isValid, UserRole.RoleAdmin);
require(isValid);
delete adminList[index];
adminDeled ^= 1 << index;
}
function queryAdmins() view public onlyAdmin returns (identity[]) {
return adminList;
}
function addCalculator(identity calculator) public onlyAdmin {
bool isExist = validCalculator(calculator);
emit USER_EXIST(isExist, UserRole.RoleCalculator);
require(!isExist);
if(calculatorDeled > 0) {
uint deled = 1;
for (uint i = 0; i < calculatorList.length; i++) {
if(deled&calculatorDeled != 0) {
calculatorList[i] = calculator;
calculatorDeled ^= deled;
break;
}
deled <<= 1;
}
} else {
calculatorList.push(calculator);
}
}
function removeCalculator(identity calculator) public onlyAdmin {
uint index = indexCalculator(calculator);
bool isValid = index < calculatorList.length;
emit USER_EXIST(isValid, UserRole.RoleCalculator);
require(isValid);
delete calculatorList[index];
calculatorDeled ^= 1 << index;
}
function queryCalculators() view public onlyCalculatorOrAdmin returns (identity[]) {
return calculatorList;
}
function addObserver(identity observer) public onlyAdmin {
bool isExist = validObserver(observer);
emit USER_EXIST(isExist, UserRole.RoleObserver);
require(!isExist);
if(observerDeled > 0) {
uint deled = 1;
for (uint i = 0; i < observerList.length; i++) {
if(deled&observerDeled != 0) {
observerList[i] = observer;
observerDeled ^= deled;
break;
}
deled <<= 1;
}
} else {
observerList.push(observer);
}
}
function removeObserver(identity observer) public onlyAdmin {
uint index = indexCalculator(observer);
bool isValid = index < observerList.length;
emit USER_EXIST(isValid, UserRole.RoleObserver);
require(isValid);
delete observerList[index];
observerDeled ^= 1 << index;
}
function queryObservers() view public onlyObserverOrAdmin returns (identity[]) {
return observerList;
}
function checkScoreEquity(uint balance, uint score) {
uint total = balance + score;
if(total >= 100 && balance < 100) {
emit SCORE_EQUITY_NOTICE("RentConcessions", total, "Citizens enjoy a 90% discount on rental housing");
}
if(total >= 200 && balance < 200) {
emit SCORE_EQUITY_NOTICE("RentConcessions_1", total, "Citizens enjoy a 80% discount on rental housing");
}
if(total >= 300 && balance < 300) {
emit SCORE_EQUITY_NOTICE("PurchaseDiscount", total, "Citizens enjoy a 90% discount on purchase housing");
}
}
//積分獎勵
function awardScore(bytes32 player, uint score, string describe) public onlyCalculatorOrAdmin {
uint balance = leasingScore[player];
leasingScore[player] = balance + score;
emit SCORE_OPERATOR(ScoreAction.ActionAwardScore, describe);
checkScoreEquity(balance, score);
}
//積分權級兌換,為消費者主動意愿,若不夠花,則不扣除積分,且發送失敗事件。
function exchangeScore(bytes32 player, uint score, string describe) public onlyCalculatorOrAdmin returns (bool) {
emit SCORE_OPERATOR(ScoreAction.ActionExchangeScore, describe);
if(leasingScore[player] >= score) {
leasingScore[player] -= score;
return true;
}
emit SCORE_ERROR(ScoreAction.ActionExchangeScore, "Score not enough to exchange");
return false;
}
//積分扣除,為消費者被動意愿,進行強制積分扣除,若積分不夠,則清零,且發送失敗時間
function deductScore(bytes32 player, uint score, string describe) public onlyCalculatorOrAdmin {
emit SCORE_OPERATOR(ScoreAction.ActionDeductScore, describe);
uint balance = leasingScore[player];
if(balance >= score) {
leasingScore[player] -= score;
} else {
if(balance != 0) {
leasingScore[player] = 0;
}
emit SCORE_ERROR(ScoreAction.ActionDeductScore, "Score not enough to deduct");
}
}
//積分查詢
function queryScore(bytes32 player, string describe) view public onlyObserverOrAdmin returns (uint) {
emit SCORE_OPERATOR(ScoreAction.ActionQueryScore, describe);
return leasingScore[player];
}
//拼接字符串
function stringAdd(string a, string b) returns(string){
bytes memory _a = bytes(a);
bytes memory _b = bytes(b);
bytes memory res = new bytes(_a.length + _b.length);
for(uint i = 0;i < _a.length;i++)
res[i] = _a[i];
for(uint j = 0;j < _b.length;j++)
res[_a.length+j] = _b[j];
return string(res);
}
//積分轉移,將積分從一個一個合約轉移到另一個合約
function transferScore(bytes32 player, uint score, LeasingScoreManager toContract, string describe) public onlyCalculatorOrAdmin {
//判斷積分是否夠用并扣除
describe = stringAdd("Transfer score: ", describe);
require(exchangeScore(player, score, describe));
toContract.awardScore(player, score, describe);
}
}