This page walks through a complete smart contract example built on Alibaba Cloud Blockchain as a Service (BaaS). The contract implements a housing rental point system — a points-based incentive mechanism that rewards tenants for long-term, compliant rental behavior.
This example is for reference only and cannot be used in production environments.
How it works
The LeasingScoreManager contract manages housing rental points for individuals and organizations. It uses a blockchain ledger to prevent data tampering and ensure transparency — every points-related action produces an immutable, auditable record.
The system supports multiple point programs (such as an economic credit or green living scheme) and can extend to broader urban credit scenarios covering healthcare, education, and financial services.
Contract design
Roles and permissions
The contract defines four roles. Each role maps to a Solidity modifier that gates function access.
| Role | Who they are | Role modifier | Point operations |
|---|---|---|---|
| Administrator | Super admin; manages all roles | onlyAdmin | Award, query, transfer, deduct, exchange |
| Calculator | Assigned by admin; a person or automated service | onlyCalculatorOrAdmin | Award, transfer, deduct, exchange |
| Observer | Assigned by admin; a person or automated service | onlyObserverOrAdmin | Query only |
| Player | Tenant; core user of the point system | (none — accesses via proxy) | Earns and redeems points through an external service proxy |
Players do not call the contract directly. To query points, a player goes through an external service system proxy that holds observer permissions. To transfer or exchange points, the player goes through an external service system proxy that holds administrator permissions.


Data structures
The contract uses three identity arrays and a points mapping:
identity[] adminList;
identity[] observerList;
identity[] calculatorList;
// Tracks which slots have been freed by delete operations.
// Uses a bitmask so that the next add() call can reuse a freed slot
// instead of always appending — this keeps array indices stable
// and avoids unbounded array growth.
uint adminDeled;
uint observerDeled;
uint calculatorDeled;
// Maps a player ID (bytes32) to their current point balance.
mapping (bytes32 => uint) leasingScore;Enums
enum ScoreAction {
ActionAwardScore, // Point reward based on rental behavior
ActionExchangeScore, // Point exchange for benefits
ActionDeductScore, // Point deduction for violations
ActionQueryScore // Point query
}
enum UserRole {
RoleAdmin,
RoleCalculator,
RoleObserver,
RolePlayer
}Events
Five events provide an on-chain audit trail:
| Event | When it fires |
|---|---|
VALID(bool valid, UserRole role) | On every role validation check |
USER_EXIST(bool exist, UserRole role) | On add/remove role checks |
SCORE_OPERATOR(ScoreAction action, string describe) | On every successful point operation |
SCORE_ERROR(ScoreAction action, string describe) | When a point operation fails (insufficient balance) |
SCORE_EQUITY_NOTICE(string action, uint score, string describe) | When a player's balance crosses a benefit threshold |
When debugging a failed exchange or deduction, look for SCORE_ERROR events in the transaction receipt. These indicate the player had insufficient points.
Point functions
The contract exposes five callable functions and one event.

awardScore — point reward
function awardScore(bytes32 player, uint score, string describe)
public onlyCalculatorOrAdminAdds points to a player's balance. The external service system calls this based on the player's rental period and behavior. After updating the balance, the function calls checkScoreEquity to check whether the new total crosses a benefit threshold and emits SCORE_EQUITY_NOTICE if so.
Benefit thresholds (defined in checkScoreEquity):
| Points reached | Benefit triggered |
|---|---|
| 100 | 90% discount on rental housing (RentConcessions) |
| 200 | 80% discount on rental housing (RentConcessions_1) |
| 300 | 90% discount on house purchase (PurchaseDiscount) |
queryScore — point query
function queryScore(bytes32 player, string describe)
view public onlyObserverOrAdmin returns (uint)Returns a player's current point balance. Players query their own points through an external service proxy that holds observer permissions.
exchangeScore — point exchange
function exchangeScore(bytes32 player, uint score, string describe)
public onlyCalculatorOrAdmin returns (bool)Deducts points when a player redeems a benefit (such as preferential rental, house purchase qualification, or residency qualification). If the player's balance is insufficient, points are not deducted, a SCORE_ERROR event is emitted, and the function returns false.
deductScore — point deduction
function deductScore(bytes32 player, uint score, string describe)
public onlyCalculatorOrAdminDeducts points for violations (for example, falsifying rental information). Unlike exchangeScore, this is not player-initiated. If the balance is insufficient, the balance is set to zero and a SCORE_ERROR event is emitted.
| Scenario | Behavior |
|---|---|
| Balance >= deduction amount | Balance reduced by the deduction amount |
| Balance < deduction amount | Balance set to 0; SCORE_ERROR emitted |
SCORE_EQUITY_NOTICE — threshold event
Not a callable function — this is a Solidity event emitted by checkScoreEquity whenever awardScore pushes a player's balance past a defined threshold. The external service system listens for this event and notifies the player of newly unlocked benefits.
transferScore — point transfer
function transferScore(
bytes32 player,
uint score,
LeasingScoreManager toContract,
string describe
) public onlyCalculatorOrAdminTransfers points to a contract in another city when a player relocates. Internally, it calls exchangeScore to deduct from the source contract, then calls awardScore on the target contract.
Two requirements must be met before calling transferScore:
The target contract must implement
awardScore.The source contract's address must be registered as an administrator or calculator on the target contract — otherwise the
awardScorecall will be rejected by theonlyCalculatorOrAdminmodifier.
Smart contract code
The full contract for reference:
pragma solidity ^0.4.20;
contract LeasingScoreManager {
identity[] adminList;
identity[] observerList;
identity[] calculatorList;
// Tracks deleted slots using a bitmask, so new entries reuse freed positions
// instead of always appending — avoids unbounded array growth.
uint adminDeled;
uint observerDeled;
uint calculatorDeled;
// Maps a player ID (bytes32) to their current point balance.
mapping (bytes32 => uint) leasingScore;
enum ScoreAction {
//Perform point reward based on behavior.
ActionAwardScore,
//Exchange specified points.
ActionExchangeScore,
//Deduct points due to violations.
ActionDeductScore,
//Query points.
ActionQueryScore
//Transfer points to another contract.
}
enum UserRole {
RoleAdmin, //Administrator
RoleCalculator, //Calculator
RoleObserver, //Observer
RolePlayer //Player
}
//Whether the calculator is valid.
event VALID(bool valid, UserRole role);
//Whether the administrator, observer, or calculator exists.
event USER_EXIST(bool exist, UserRole role);
//Point event
event SCORE_OPERATOR(ScoreAction action, string describe);
//Incorrect operation on points
event SCORE_ERROR(ScoreAction action, string describe);
//Notification of rights and interests
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");
}
}
//Point reward
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);
}
//Point exchange by the player. If points are not enough, points will not be deducted, and a failure event will be reported.
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;
}
//Point deduction that is not initiated by the player. If points are not enough for the deduction, points will be cleared, and a failure event will be reported.
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");
}
}
//Point query
function queryScore(bytes32 player, string describe) view public onlyObserverOrAdmin returns (uint) {
emit SCORE_OPERATOR(ScoreAction.ActionQueryScore, describe);
return leasingScore[player];
}
//String combination
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);
}
//Point transfer to another contract
function transferScore(bytes32 player, uint score, LeasingScoreManager toContract, string describe) public onlyCalculatorOrAdmin {
//Point deduction if points are enough
describe = stringAdd("Transfer score: ", describe);
require(exchangeScore(player, score, describe));
toContract.awardScore(player, score, describe);
}
}