最近英雄联盟全球总决赛落下帷幕,同时下一届的世界杯预选赛也已经开打,结合这些热点,我们可以开发一个投注预测比赛结果的DApp。
整个项目的架构分为:智能合约、前端界面、后端管理
这里需要讲一下,虽然DApp是一个去中心化应用,但是在实际运营的时候要配合UI界面,以及比赛的信息、结果等都需要一些额外的资源去支持,例如(图片、文字、甚至视频)这些额外的资源是不便存在链上的,所以我们通过智能合约完成核心的功能,保存交易记录及转账
项目需求
首先,我们要编写一个投注的智能合约,合约包含创建投注、开始、结束、下注、分配奖金的功能。同时,我们在用户下注后将下注信息保存在服务端,以便在分配奖金阶段可以准确的找到用户的下注结果,同时降低GAS费。
合约编写
按照上面的项目需求,我们先来完成合约
拥有者与手续费
- 合约的拥有者由
owner
变量表示,拥有者具备管理权限。 FEE_PERCENTAGE
常量定义了合约拥有者提取的手续费百分比,这可用于维护合约和服务。
// SPDX-License-Identifier: MIT
// 智能合约的许可证,表示该合约采用MIT许可证
pragma solidity ^0.8.0;
contract BettingContract {
// 合约拥有者的地址
address public owner;
//...
}
结构体定义
Bet 结构体
Bet
结构体用于表示用户的一次下注,包含下注金额、下注结果(0 表示“赢”,1 表示“输”)以及相关状态标记。
// 表示一个下注的结构体
struct Bet {
uint amount; // 下注金额
uint8 outcome; // 0表示“赢”,1表示“输”
bool exists; // 标记是否存在该下注
bool isWinner; // 是否为赢家
bool isPaid; // 奖金是否已支付
}
Match 结构体
Match
结构体表示一场比赛,包含唯一标识符、开始和结束时间、下注开启状态、总下注金额、不同结果的总下注金额数组以及用户下注的映射。
// 表示一场比赛的结构体
struct Match {
uint id; // 比赛的唯一标识符
uint startTime; // 比赛开始时间
uint endTime; // 比赛结束时间
bool isBettingOpen; // 标记是否允许下注
uint totalBetAmount; // 总下注金额
uint[2] totalBetAmountByOutcome; // 索引0为“赢”,索引1为“输”
mapping(address => Bet) bets; // 存储地址到对应的下注信息
}
合约状态
matches
映射将比赛 ID 与Match
结构体关联,用于存储和检索比赛信息。nextMatchId
记录下一个比赛的唯一标识符。- 各种事件
// 存储比赛ID到比赛结构体的映射
mapping(uint => Match) public matches;
// 下一个比赛的ID
uint public nextMatchId;
// 事件,用于通知外部系统发生的关键操作
event BetPlaced(uint matchId, address bettor, uint amount, uint8 outcome);
event MatchCreated(uint matchId, uint startTime, uint endTime);
event MatchBettingOpened(uint matchId);
event MatchBettingClosed(uint matchId);
event WinningsDistributed(uint matchId);
在部署合约的时候设置合约拥有者:
// 修饰符,限制只有拥有者可以调用的函数
modifier onlyOwner() {
require(msg.sender == owner, "只有拥有者可以调用此函数");
_;
}
关键功能
当然可以。以下是对给定代码的书面报告:
智能合约报告:体育比赛投注系统
摘要
本智能合约实现了一个简单而功能强大的体育比赛投注系统。该系统允许用户在不同比赛的各种结果上下注,同时提供了管理比赛、开启/关闭下注以及分发奖金的功能。此合约采用了MIT许可证。
合约结构
拥有者与手续费
- 合约的拥有者由
owner
变量表示,拥有者具备管理权限。 FEE_PERCENTAGE
常量定义了合约拥有者提取的手续费百分比,这可用于维护合约和服务。
结构体定义
Bet 结构体
Bet
结构体用于表示用户的一次下注,包含下注金额、下注结果(0 表示“赢”,1 表示“输”)以及相关状态标记。
Match 结构体
Match
结构体表示一场比赛,包含唯一标识符、开始和结束时间、下注开启状态、总下注金额、不同结果的总下注金额数组以及用户下注的映射。
合约状态
matches
映射将比赛 ID 与Match
结构体关联,用于存储和检索比赛信息。nextMatchId
记录下一个比赛的唯一标识符。
关键功能
创建新比赛
createMatch
函数由合约拥有者调用,用于创建新的比赛。指定比赛的开始时间,系统会自动生成唯一的比赛 ID。
function createMatch(uint startTime) public onlyOwner {
// 为新比赛分配唯一ID
uint matchId = nextMatchId++;
// 获取新比赛的引用
Match storage newMatch = matches[matchId];
// 设置新比赛的属性
newMatch.id = matchId;
newMatch.startTime = startTime;
newMatch.isBettingOpen = true;
// 发出比赛创建事件通知
emit MatchCreated(matchId, startTime, 0);
}
开启/关闭下注
openBetting
函数用于在比赛开始前24小时内开启下注,并在比赛开始后关闭下注。closeBetting
函数由拥有者调用,用于在比赛开始后关闭下注。
// 开启某场比赛的下注功能
function openBetting(uint matchId) public {
// 获取比赛的引用
Match storage match_ = matches[matchId];
// 要求只能在比赛开始前24小时内开启下注
require(block.timestamp <= match_.startTime - 24 hours, "只有在比赛开始前24小时内才能开启下注");
// 要求不能在比赛开始后开启下注
require(block.timestamp >= match_.startTime, "比赛开始后不能开启下注");
// 标记该比赛下注功能已开启
match_.isBettingOpen = true;
// 记录下注功能开启的时间
match_.endTime = block.timestamp;
// 发出比赛下注开启事件通知
emit MatchBettingOpened(matchId);
}
// 关闭某场比赛的下注功能
function closeBetting(uint matchId) public onlyOwner {
// 获取比赛的引用
Match storage match_ = matches[matchId];
// 要求只能在比赛开始后关闭下注
require(block.timestamp >= match_.startTime, "只有在比赛开始后才能关闭下注");
// 标记该比赛下注功能已关闭
match_.isBettingOpen = false;
// 发出比赛下注关闭事件通知
emit MatchBettingClosed(matchId);
}
用户下注
placeBet
函数允许用户在比赛开始前24小时内对不同结果下注。下注金额被累加到总下注金额和不同结果的总下注金额中。
// 用户下注的函数
function placeBet(uint matchId, uint8 outcome) public payable {
// 获取比赛的引用
Match storage match_ = matches[matchId];
// 要求只能在比赛开始前24小时内下注
require(block.timestamp >= match_.startTime - 24 hours, "还不能下注");
// 要求不能在比赛开始后下注
require(block.timestamp < match_.startTime, "下注已关闭");
// 要求该比赛下注功能已开启
require(match_.isBettingOpen, "该比赛下注已关闭");
// 要求下注金额大于0
require(msg.value > 0, "下注金额必须大于0");
// 要求下注结果为0或1
require(outcome <= 1, "无效的下注结果");
// 获取用户下注的引用
Bet storage userBet_ = match_.bets[msg.sender];
// 如果用户之前没有下注,则初始化下注信息
if (!userBet_.exists) {
userBet_.exists = true;
userBet_.amount = msg.value;
userBet_.outcome = outcome;
} else {
// 如果用户已经下注过,则累加下注金额
userBet_.amount += msg.value;
}
// 更新比赛的总下注金额和各结果的总下注金额
match_.totalBetAmount += msg.value;
match_.totalBetAmountByOutcome[outcome] += msg.value;
// 发出下注成功的事件通知
emit BetPlaced(matchId, msg.sender, msg.value, outcome);
}
分发奖金
distributeWinnings
函数由拥有者调用,用于在比赛结束后向赢家分发奖金。计算奖金时考虑手续费,并确保奖金未分发过。
// 分发奖金给赢家的函数
function distributeWinnings(uint matchId, address winnerAddress) public onlyOwner {
//待定 ...
}
至此我们完成了一个预测赛事的智能合约,需要注意的事,此合约只是示例功能,并不能用于生产环境