最近准备完全用智能合约实现一个系统,先看一些智能合约的源码学习一下。
源码github地址:https://github.com/Rello/BlockScores
该智能合约的基本功能
- 注册新游戏
- 增加玩家
- 给每个玩家增加分数
- 其他玩家可确认分数的增加,采用四眼原则 four-eyes principle
- 管理玩家
- 收取手续费
pragma solidity ^0.4.20;
/// @title Store lederboards in the Blockchain
/// @author Marcel Scherello blockscores@scherello.de
/// @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;
}
}