以太坊智能合約源碼解讀(1)——排行榜

最近準備完全用智能合約實現一個系統,先看一些智能合約的源碼學習一下。

源碼github地址:https://github.com/Rello/BlockScores

該智能合約的基本功能

  • 註冊新遊戲
  • 增加玩家
  • 給每個玩家增加分數
  • 其他玩家可確認分數的增加,採用四眼原則 four-eyes principle
  • 管理玩家
  • 收取手續費
pragma solidity ^0.4.20;
/// @title Store lederboards in the Blockchain
/// @author Marcel Scherello [email protected]
/// @notice Create a custom leaderboard and start counting the scores
/// @dev All function calls are currently implement without side effects
/// @dev v1.1.0
contract BlockScores {
		//定義玩家結構體
    struct Player {
        bytes32  playerName;//玩家名稱
        address playerAddress;//玩家地址
        uint  score;//分數
        uint  score_unconfirmed;//未確認分數
        uint   isActive;//玩家狀態 1爲激活 0爲未激活
    }
    //定義榜單    
    struct Board {
        bytes32  boardName;//榜單名稱
        string  boardDescription;//榜單描述
        uint   numPlayers;//玩家數量
        address boardOwner;//榜單擁有者
        mapping (uint => Player) players;//定義到玩家的映射
    }
    mapping (bytes32 => Board) boards;//定義到榜單的映射
    uint public numBoards;//榜單數量
    address owner = msg.sender;//合約擁有者地址

    uint public balance;
    uint public boardCost = 1000000000000000;
    uint public playerCost = 1000000000000000;
		
		//被該modifier標記的函數,只有合約擁有者可以調用
    modifier isOwner {
        assert(owner == msg.sender);
        _;
    }

    /**
    Funding Functions
    */

    /// @notice 提取所有金額給合約擁有者
    /// @return true
    function withdraw() isOwner public returns(bool) {
        uint _amount = address(this).balance;
        emit Withdrawal(owner, _amount);
        owner.transfer(_amount);
        balance -= _amount;
        return true;
    }

    /// @notice 改變使用合約的手續費
    /// @param costBoard 創建新榜單使用的手續費
    /// @param costPlayer 創建新玩家的手續費
    /// @return true
    function setCosts (uint costBoard, uint costPlayer) isOwner public returns(bool) {
        boardCost = costBoard;
        playerCost = costPlayer;
        return true;
    }

    /// @notice 在榜單擁有者和合約擁有者之間分配新玩家繳納的手續費
    /// @param boardOwner 榜單擁有者
    /// @param _amount 待分配的費用
    /// @return true
    function split(address boardOwner, uint _amount) internal returns(bool) {
        emit Withdrawal(owner, _amount/2);
        owner.transfer(_amount/2);
        //emit Withdrawal(boardOwner, _amount/2);
        boardOwner.transfer(_amount/2);
        return true;
    }

    /// @notice 提現監聽事件
    event Withdrawal(address indexed _from, uint _value);

    /**
    Board Functions
    */

    /// @notice 新增一個榜單. 榜單 hash值通過榜單名稱和創建者生成
    /// @notice 創建新的榜單需要資金
    /// @param name The name of the leaderboard
    /// @param boardDescription A subtitle for the leaderboard
    /// @return The hash of the newly created leaderboard
    //新增一個榜單,返回榜單hash值
    function addNewBoard(bytes32 name, string boardDescription) public payable returns(bytes32 boardHash){
        require(msg.value >= boardCost);//創建者必須發送大於boardCost的ether,單位爲wei
        balance += msg.value;//增加合約餘額
        boardHash = keccak256(abi.encodePacked(name, msg.sender));//使用name和創建者address生成榜單hash
        numBoards++;//榜單數量加一
        boards[boardHash] = Board(name, boardDescription, 0, msg.sender);//創建榜單
        emit newBoardCreated(boardHash);//打印榜單哈希值
    }

    /// @notice 模擬榜單hash的生成
    /// @param name 榜單名稱
    /// @param admin 榜單擁有者地址
    /// @return 返回可能的榜單hash值,因爲榜單可能修改名稱
    function createBoardHash(bytes32 name, address admin) pure public returns (bytes32){
        return keccak256(abi.encodePacked(name, admin));
    }

    /// @notice 獲取榜單原數據
    /// @param boardHash 榜單hash值
    /// @return 榜單名稱, 描述 和玩家數量
    function getBoardByHash(bytes32 boardHash) constant public returns(bytes32,string,uint){
        return (boards[boardHash].boardName, boards[boardHash].boardDescription, boards[boardHash].numPlayers);
    }

    /// @notice 榜單擁有者修改名稱和描述
    /// @param boardHash 要被修改的榜單hash
    /// @param name 榜單新的名稱
    /// @param boardDescription 榜單的新描述
    /// @return true
    function changeBoardMetadata(bytes32 boardHash, bytes32 name, string boardDescription) public returns(bool) {
        require(boards[boardHash].boardOwner == msg.sender);
        boards[boardHash].boardName = name;
        boards[boardHash].boardDescription = boardDescription;
    }

    /// @notice 創建榜單監聽事件
    event newBoardCreated(bytes32 boardHash);


    /**
    Player Functions
    */

    /// @notice 玩家加入已存在的榜單
    /// @param boardHash 榜單hash
    /// @param playerName 玩家名稱
    /// @return 返回成功或失敗
    function addPlayerToBoard(bytes32 boardHash, bytes32 playerName) public payable returns (bool) {
        require(msg.value >= playerCost);//需要玩家轉入大於等於playerCost的資金才能加入
        Board storage g = boards[boardHash];//獲取榜單信息
        split (g.boardOwner, msg.value);//收入分成
        uint newPlayerID = g.numPlayers++;//玩家ID等於當前榜單玩家數量加1
        g.players[newPlayerID] = Player(playerName, msg.sender,0,0,1);//創建一個玩家
        return true;
    }

    /// @notice 根據榜單hash和玩家ID獲取玩家信息
    /// @param boardHash 榜單hash
    /// @param playerID 玩家ID
    /// @return 返回玩家姓名, 已確認分數, 未確認分數
    function getPlayerByBoard(bytes32 boardHash, uint8 playerID) constant public returns (bytes32, uint, uint){
        Player storage p = boards[boardHash].players[playerID];//獲取玩家信息
        require(p.isActive == 1);//玩家狀態必須爲1
        return (p.playerName, p.score, p.score_unconfirmed);
    }

    /// @notice 榜單擁有者可以移除玩家,就是將狀態修改爲0
    /// @param boardHash 榜單hash
    /// @param playerName 玩家名稱
    /// @return true/false
    function removePlayerFromBoard(bytes32 boardHash, bytes32 playerName) public returns (bool){
        Board storage g = boards[boardHash];
        require(g.boardOwner == msg.sender);//必須是榜單擁有者
        uint8 playerID = getPlayerId (boardHash, playerName, 0);//根據名稱獲取玩家ID
        require(playerID < 255 );//玩家ID須小於255
        g.players[playerID].isActive = 0;//將玩家狀態改爲0
        return true;
    }

    /// @notice 根據榜單hash、玩家名稱、玩家地址獲取玩家ID
    /// @param boardHash 榜單hash
    /// @param playerName 玩家名稱
    /// @param playerAddress 玩家地址
    /// @return ID or 999 in case of false
    function getPlayerId (bytes32 boardHash, bytes32 playerName, address playerAddress) constant internal returns (uint8) {
        Board storage g = boards[boardHash];
        for (uint8 i = 0; i <= g.numPlayers; i++) {
            if ((keccak256(abi.encodePacked(g.players[i].playerName)) == keccak256(abi.encodePacked(playerName)) || playerAddress == g.players[i].playerAddress) && g.players[i].isActive == 1) {
                return i;
                break;
            }
        }
        return 255;
    }

    /**
    Score Functions
    */

    /// @notice 給榜單玩家增加未確認分數. 或者覆蓋已存在的未確認分數,這個合約
    /// @param boardHash 榜單hash
    /// @param playerName 玩家名稱
    /// @param score 分數
    /// @return true/false
    function addBoardScore(bytes32 boardHash, bytes32 playerName, uint score) public returns (bool){
        uint8 playerID = getPlayerId (boardHash, playerName, 0);
        require(playerID < 255 );
        boards[boardHash].players[playerID].score_unconfirmed = score;
        return true;
    }

    /// @notice 將榜單玩家未確認分數變爲已確認分數. 將未確認分數加到用戶存在分數上. 玩家無法確認他自己的分數
    /// @param boardHash 榜單hash
    /// @param playerName 需要確認分數的玩家名稱
    /// @return true/false
    function confirmBoardScore(bytes32 boardHash, bytes32 playerName) public returns (bool){
        uint8 playerID = getPlayerId (boardHash, playerName, 0);
        uint8 confirmerID = getPlayerId (boardHash, "", msg.sender);
        require(playerID < 255); // 玩家狀態必須正常
        require(confirmerID < 255); // 確認玩家狀態必須正常
        require(boards[boardHash].players[playerID].playerAddress != msg.sender); //需要由其他玩家來確認
        boards[boardHash].players[playerID].score += boards[boardHash].players[playerID].score_unconfirmed;//給待確認分數用戶確認分數
        boards[boardHash].players[playerID].score_unconfirmed = 0;//將待確認分數置爲0
        return true;
    }

    /**
    Migration Functions
    */
    /// @notice 合約擁有者獲取榜單明細信息
    /// @param boardHash 榜單hash
    /// @return 返回榜單名稱、榜單描述、榜單玩家數、榜單擁有者
    function migrationGetBoard(bytes32 boardHash) constant isOwner public returns(bytes32,string,uint,address) {
        return (boards[boardHash].boardName, boards[boardHash].boardDescription, boards[boardHash].numPlayers, boards[boardHash].boardOwner);
    }

    /// @notice 合約擁有者修改榜單信息
    /// @param boardHash 需要修改的榜單hash值
    /// @param name 榜單的新名稱
    /// @param boardDescription 榜單的新描述
    /// @param numPlayers 榜單的新用戶數
    /// @param boardOwner 榜單新的擁有者
    /// @return true
    function migrationSetBoard(bytes32 boardHash, bytes32 name, string boardDescription, uint8 numPlayers, address boardOwner) isOwner public returns(bool) {
        boards[boardHash].boardName = name;
        boards[boardHash].boardDescription = boardDescription;
        boards[boardHash].numPlayers = numPlayers;
        boards[boardHash].boardOwner = boardOwner;
        return true;
    }

    /// @notice 合同擁有者讀取玩家數據
    /// @param boardHash 榜單hash
    /// @param playerID 玩家ID
    /// @return Player metadata
    function migrationGetPlayer(bytes32 boardHash, uint8 playerID) constant isOwner public returns (uint, bytes32, address, uint, uint, uint){
        Player storage p = boards[boardHash].players[playerID];
        return (playerID, p.playerName, p.playerAddress, p.score, p.score_unconfirmed, p.isActive);
    }

    /// @notice 合約擁有者重寫玩家數據
    /// @param boardHash 榜單hash
    /// @param playerID 玩家ID
    /// @param playerName 玩家名稱
    /// @param playerAddress 玩家地址
    /// @param score 玩家分數
    /// @param score_unconfirmed 未確認分數
    /// @param isActive 玩家狀態
    /// @return true
    function migrationSetPlayer(bytes32 boardHash, uint playerID, bytes32 playerName, address playerAddress, uint score, uint score_unconfirmed, uint isActive) isOwner public returns (bool) {
        Board storage g = boards[boardHash];
        g.players[playerID] = Player(playerName, playerAddress, score, score_unconfirmed, isActive);
        return true;
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章