最近準備完全用智能合約實現一個系統,先看一些智能合約的源碼學習一下。
源碼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;
}
}