- 参赛球队一经设定不可改变,整个活动结束后无法投票;
- 全⺠均可参与,无权限控制;
- 每次投票为 1 ether,且只能选择一支球队;
- 每个人可以投注多次;
- 仅管理员公布最终结果,完成奖金分配,开奖后逻辑:
- winner 共享整个奖金池(一部分是自己的本金,一部分是利润);
- winner 需自行领取奖金(因为有手续费);
- 下一期自行开始
8 c- { g: v+ j, f9 W
' f! `0 C2 q0 x- K1 _
- 2 m& w" M( O" f" T: T% `
- // SPDX-License-Identifier: GPL-3.0
- ! ]! o/ M4 x5 `$ P- S3 b
- pragma solidity >=0.7.0 <0.9.0;* H2 B& X; V! D0 I# u' w- ?) W3 @' H
- % h9 r8 F# {! z+ J9 g' M
- import "hardhat/console.sol";* v$ r" i* j6 X, Q
- contract WorldCup {8 U: o. A8 h- U* ^' t2 w8 ?
- // 1. 状态变量:管理员、所有玩家、获奖者地址、第几期、参赛球队
- // 2. 核心方法:下注、开奖、兑现
- // 3. 辅助方法:获取奖金池金额、管理员地址、当前期数、参与人数、所有玩家、参赛球队
- " X2 o( }1 F/ l8 _
- // 管理员1 Y& Q- [# Q8 P& u( p
- address public admin;
- // 第几期+ e( _9 ], R) y& S' V6 R
- uint8 public currRound;
- // 参赛球队
- string[] public countries = ["GERMANY", "FRANCH", "CHINA", "BRIZAL", "KOREA"];1 j# m H8 V) l% b+ F5 x" A# m
- // 期数 => 玩家
- mapping(uint8 => mapping(address => Player)) players;+ b3 B: W" C2 T9 Z
- // 期数 => 投注各球队的玩家
- mapping(uint8 => mapping(Country => address[])) public countryToPlayers;- N* D4 e/ f$ k; J u) e+ b
- // 玩家对应赢取的奖金+ N5 H1 z$ y/ B
- mapping(address => uint256) public winnerVaults;
- 3 z' K1 C- E2 X4 S$ w
- // 投注截止时间-使用不可变量,可通过构造函数传值,部署后无法改变
- uint256 public immutable deadline;9 A+ C& m) g( [9 I8 R! c
- // 所有玩家待兑现的奖金
- uint256 public lockedAmts;
- % u: o; r5 {" M4 M: q
- enum Country {" \5 H+ B4 P$ B+ I! I8 s2 ~( R
- GERMANY,& O9 m9 Q$ p" q3 B4 |1 y
- FRANCH,
- CHINA,) P: @8 l! U' \6 |# g- B) g
- BRAZIL,5 \% M5 E/ d% U( f9 l& \
- KOREA
- }# F5 ?/ @/ ~% q$ y5 m: z" L
- 4 ~9 M* R+ _/ @" e7 R7 H. V
- event Play(uint8 _currRound, address _player, Country _country);5 D$ P- d D! B% I, P
- event Finialize(uint8 _currRound, uint256 _country);
- event ClaimReward(address _claimer, uint256 _amt);0 a1 B/ y) K- D# Q* P/ k- d
- ; m+ Z2 P* x) O, C7 A0 U% N
- // 验证管理员身份
- modifier onlyAdmin {( r2 f$ C4 }* _( k K+ j
- require(msg.sender == admin, "not authorized!");$ G1 a" D& }# M- w( ~# J" Q- L
- _;1 } z5 p( K( n. N2 P! \9 k
- }; o( A& A: C ~$ H8 u0 x! g
- // 玩家投注信息
- struct Player {* q6 H6 `; K7 I$ |4 n9 p! k! `; ~( m
- // 是否开奖4 B5 T# L/ j9 m! g
- bool isSet;
- // 投注的球队份额
- mapping(Country => uint256) counts;- B+ f$ t* P$ I) {
- }
- constructor(uint256 _deadline) {
- admin = msg.sender;( Z. k* p3 i: `) F% o$ M" R
- require(_deadline > block.timestamp, "WorldCupLottery: invalid deadline!");: K/ K6 _1 r" a' E$ V6 s
- deadline = _deadline;
- }
- * H- L" \! [& ^( h7 F2 Q
- // 下注过程
- function play(Country _selected) payable external {5 G% ]5 I: D1 N
- // 参数校验
- require(msg.value == 1 gwei, "invalid funds provided!");
- $ V9 H2 i9 A5 ?% J; C
- require(block.timestamp < deadline, "it's all over!");; v; l( R+ v- F: {
- 0 Q/ s- Q4 i/ ~! U
- // 更新 countryToPlayers
- countryToPlayers[currRound][_selected].push(msg.sender);5 J( Q7 ~: ~6 t$ u
- // 更新 players(storage 是引用传值,修改会同步修改原变量)
- Player storage player = players[currRound][msg.sender];9 S8 E- g& A& Q# ~* X! ~; a
- // player.isSet = false;
- player.counts[_selected] += 1;5 _1 L- I7 }" b. x( j
- emit Play(currRound, msg.sender, _selected);1 X8 }7 J! d2 M5 ~# h J
- }8 w4 q' F2 h' ~2 v% ?% f4 B
- // 开奖过程# t# S2 J/ i' L# i: |! x
- function finialize(Country _country) onlyAdmin external {5 X! i, f- H. e: A- h0 W, P, D
- // 找到 winners
- address[] memory winners = countryToPlayers[currRound][_country];6 B. J: i6 G9 n; B( O
- // 分发给所有压中玩家的实际奖金8 Y* }9 W. y* T, F
- uint256 distributeAmt;
- // 本期总奖励金额(奖池金额 - 所有玩家待兑现的奖金)
- uint currAvalBalance = getVaultBalance() - lockedAmts;
- console.log("currAvalBalance:", currAvalBalance, "winners count:", winners.length);
- for (uint i = 0; i < winners.length; i++) {
- address currWinner = winners[i];! k+ d" T! a. S& F$ L! q) Z8 }
- // 获取每个地址应该得到的份额
- Player storage winner = players[currRound][currWinner];. h3 @) a/ ^8 ?% G: Z$ T! [ f, \9 Y
- if (winner.isSet) {8 N" }+ o- J3 y- T3 [
- console.log("this winner has been set already, will be skipped!");' R4 l, y) s3 D6 s
- continue;8 t& U4 n$ A9 ~, G$ F
- }
- winner.isSet = true;$ d8 ]7 |: o! c- D7 {
- // 玩家购买的份额
- uint currCounts = winner.counts[_country];
- 9 _4 Q) Y- @' @, K
- // (本期总奖励 / 总获奖人数)* 当前地址持有份额* G2 K7 u- W# `. z% O1 M
- uint amt = (currAvalBalance / countryToPlayers[currRound][_country].length) * currCounts;& X9 l# G# f# M. r
- // 玩家对应赢取的奖金+ }* n8 Y+ B2 t+ p! n! e. _
- winnerVaults[currWinner] += amt;
- distributeAmt += amt;
- // 放入待兑现的奖金池1 Y- H$ D- `# k, h$ ^1 G
- lockedAmts += amt;
- console.log("winner:", currWinner, "currCounts:", currCounts);% M$ m& _! w5 O; k3 O) @7 t+ u
- console.log("reward amt curr:", amt, "total:", winnerVaults[currWinner]);
- }( R8 r/ _' R3 v, Y+ k
- 9 J9 r% H* Y7 {3 g5 z
- // 未分完的奖励即为平台收益, d a6 |) [$ H0 Z) P9 {5 v% v
- uint giftAmt = currAvalBalance - distributeAmt;- y9 ]/ I8 l+ n8 Q c F
- if (giftAmt > 0) {
- winnerVaults[admin] += giftAmt;- H2 x# J1 v0 d! ?" W
- }
- emit Finialize(currRound++, uint256(_country));
- }7 _. u$ W& A+ f' j2 ^
- 3 i2 p$ C* h; U9 Q% P2 l3 F2 k
- // 奖金兑现$ o! Y: m; ]# {
- function claimReward() external {
- uint256 rewards = winnerVaults[msg.sender];
- require(rewards > 0, "nothing to claim!");$ K# E4 P& s& A# h. z5 {$ X
- 5 @" q+ @; B" m/ d- e2 O3 x0 J
- // 玩家领取完奖金置为 0
- winnerVaults[msg.sender] = 0;
- // 从待兑现奖金池中移除该玩家份额
- lockedAmts -= rewards;6 I2 D- L- g9 e) g5 E0 T
- (bool succeed,) = msg.sender.call{value: rewards}("");9 i$ b2 n: _' c' o% D, v/ e [
- require(succeed, "claim reward failed!");& V" }( E/ W+ v N; j5 ^" Q
- / ?4 J, |6 C1 Q* J7 Z8 [4 J8 P$ \
- console.log("rewards:", rewards);$ p* T' p9 H8 X( q, O
- 2 d8 H) T5 S% o7 D7 @) M( F
- emit ClaimReward(msg.sender, rewards);# [2 T4 o5 p5 x9 K4 M
- }
- , E, {: E; x% e( x, p/ V, v
- // 获取奖池金额
- function getVaultBalance() public view returns(uint256 bal) {1 n6 a) T& x! M7 O9 Y* c$ p# K
- bal = address(this).balance;1 C$ _9 m/ C9 [8 p7 `4 w4 U) W9 r, O
- }
- // 获取当期下注当前球队的人数
- function getCountryPlayers(uint8 _round, Country _country) external view returns(uint256) {
- return countryToPlayers[_round][_country].length;
- }
- , J7 b5 S: v- k0 P' x% W
- // 获取当前玩家当期押注份额
- function getPlayerInfo(uint8 _round, address _player, Country _country) external view returns(uint256 _counts) {- q' ]4 j5 X0 C3 a2 M" X
- return players[_round][_player].counts[_country];4 m+ K5 F+ w5 r( s( d1 R0 m
- }
- }
) P" O1 H* a% {( N7 ]1 ~
! \. z" } v3 \- p# d
" M$ [( B/ i# r. F. ~2 |