Contract Examples

更新时间:
复制 MD 格式

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.

Note

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.

RoleWho they areRole modifierPoint operations
AdministratorSuper admin; manages all rolesonlyAdminAward, query, transfer, deduct, exchange
CalculatorAssigned by admin; a person or automated serviceonlyCalculatorOrAdminAward, transfer, deduct, exchange
ObserverAssigned by admin; a person or automated serviceonlyObserverOrAdminQuery only
PlayerTenant; 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.

12

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:

EventWhen 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.

3

awardScore — point reward

function awardScore(bytes32 player, uint score, string describe)
    public onlyCalculatorOrAdmin

Adds 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 reachedBenefit triggered
10090% discount on rental housing (RentConcessions)
20080% discount on rental housing (RentConcessions_1)
30090% 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 onlyCalculatorOrAdmin

Deducts 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.

ScenarioBehavior
Balance >= deduction amountBalance reduced by the deduction amount
Balance < deduction amountBalance 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 onlyCalculatorOrAdmin

Transfers 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 awardScore call will be rejected by the onlyCalculatorOrAdmin modifier.

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);
    }
}