- 参赛球队一经设定不可改变,整个活动结束后无法投票;
- 全⺠均可参与,无权限控制;
- 每次投票为 1 ether,且只能选择一支球队;
- 每个人可以投注多次;
- 仅管理员公布最终结果,完成奖金分配,开奖后逻辑:
- winner 共享整个奖金池(一部分是自己的本金,一部分是利润);
- winner 需自行领取奖金(因为有手续费);
- 下一期自行开始3 m. x! H/ y+ E6 I* J
; v& u& l* \' w5 q5 {
- + u& N& j! L$ K& j3 p
- // SPDX-License-Identifier: GPL-3.0
- 7 o+ }# n. h' Y1 y0 X" n* |
- pragma solidity >=0.7.0 <0.9.0;& y, {" [$ D5 R l+ @
- import "hardhat/console.sol";# F- C Q3 `7 [: `2 |* s
- 4 A, w( L. X* z0 u0 z6 F
- contract WorldCup {' i' ~1 v- R9 w
- // 1. 状态变量:管理员、所有玩家、获奖者地址、第几期、参赛球队6 R; N/ h/ p5 h" B' N9 G
- // 2. 核心方法:下注、开奖、兑现, I% K% d: P1 `* @; v" e5 I
- // 3. 辅助方法:获取奖金池金额、管理员地址、当前期数、参与人数、所有玩家、参赛球队+ @% f- v" t* V1 E" o9 ~( i7 y
- // 管理员
- address public admin;. t7 {+ o& M( N
- // 第几期
- uint8 public currRound; `4 w8 y K5 K
- // 参赛球队) I1 b9 T6 B4 w; P, e
- string[] public countries = ["GERMANY", "FRANCH", "CHINA", "BRIZAL", "KOREA"];
- // 期数 => 玩家
- mapping(uint8 => mapping(address => Player)) players;% t+ d0 g9 {2 T2 c4 D; N5 G, A# K
- // 期数 => 投注各球队的玩家" ?8 U7 M2 N4 s" u8 n
- mapping(uint8 => mapping(Country => address[])) public countryToPlayers;( v1 `9 i# c. F7 B
- // 玩家对应赢取的奖金2 I {2 m+ ]; w7 T: h9 c1 y
- mapping(address => uint256) public winnerVaults;; d; k7 \; J# |! L2 R0 I
- // 投注截止时间-使用不可变量,可通过构造函数传值,部署后无法改变
- uint256 public immutable deadline;
- // 所有玩家待兑现的奖金
- uint256 public lockedAmts;. |! h# W$ _1 q5 v/ x
- enum Country {
- GERMANY,: m& ?& w& _% p( y1 l( |$ Z
- FRANCH,5 D9 @1 Y, Z l& t4 d
- CHINA,
- BRAZIL,/ h% B' D$ }2 f3 U$ ]2 @
- KOREA
- }
- event Play(uint8 _currRound, address _player, Country _country);
- event Finialize(uint8 _currRound, uint256 _country);# h5 c) A3 b' K' R4 p5 Q- I) G
- event ClaimReward(address _claimer, uint256 _amt);
- // 验证管理员身份
- modifier onlyAdmin {
- require(msg.sender == admin, "not authorized!");" O: U, D8 j# [# b v
- _;
- }" A+ l* {3 P3 s4 p
- // 玩家投注信息/ P; _0 _4 t$ ?: K! ?# z
- struct Player {
- // 是否开奖
- bool isSet;6 ?0 m- i* W7 b, c" g9 Y/ P9 i+ x
- // 投注的球队份额
- mapping(Country => uint256) counts;
- }
- B) ~+ ~3 g& y* @6 D9 p) |4 w" M; \
- constructor(uint256 _deadline) {0 I+ c ~ K7 N$ S
- admin = msg.sender;/ F* o" l9 m" [" }0 x: W. ^
- require(_deadline > block.timestamp, "WorldCupLottery: invalid deadline!");
- deadline = _deadline;, k$ U+ d1 t H% v! @1 s% t& P( h$ A7 Z
- }7 H. m. r9 Q& O+ {! h5 [- p( I
- 1 J R, T$ m. y
- // 下注过程( W9 Y* _7 n. _8 |4 u9 ~# V
- function play(Country _selected) payable external {) x( X7 ^! @" M* H7 @
- // 参数校验6 _! f( k8 [# g0 D7 \' p
- require(msg.value == 1 gwei, "invalid funds provided!");
- require(block.timestamp < deadline, "it's all over!");
- // 更新 countryToPlayers
- countryToPlayers[currRound][_selected].push(msg.sender);# A& G! L- V/ e. m# B5 L
- // 更新 players(storage 是引用传值,修改会同步修改原变量)
- Player storage player = players[currRound][msg.sender];9 O2 H' Y8 K3 K) T: S( q# Q
- // player.isSet = false;
- player.counts[_selected] += 1;
- 3 \0 q( z. k6 ?) D* n- s) M
- emit Play(currRound, msg.sender, _selected);
- }
- // 开奖过程: n4 y6 U# O9 Q$ [: @/ G" A
- function finialize(Country _country) onlyAdmin external {4 i3 j1 N4 I$ {
- // 找到 winners
- address[] memory winners = countryToPlayers[currRound][_country];$ M$ P% f+ k2 s* A2 c) a
- // 分发给所有压中玩家的实际奖金
- uint256 distributeAmt;4 z/ }& o# }. H& I E8 e& ?
- 5 f1 h% h( q1 U p3 T- B
- // 本期总奖励金额(奖池金额 - 所有玩家待兑现的奖金)
- uint currAvalBalance = getVaultBalance() - lockedAmts;
- console.log("currAvalBalance:", currAvalBalance, "winners count:", winners.length);
- for (uint i = 0; i < winners.length; i++) {
- address currWinner = winners[i];
- + W! C. s1 r# M- y+ B
- // 获取每个地址应该得到的份额
- Player storage winner = players[currRound][currWinner];; T" R/ }5 Z9 Y; l- Q% g! o9 ^
- if (winner.isSet) {
- console.log("this winner has been set already, will be skipped!");* ?# H1 M! e4 W! y
- continue;
- }
- winner.isSet = true;
- // 玩家购买的份额
- uint currCounts = winner.counts[_country];2 B) P7 P& ?' @7 R0 O) e
- // (本期总奖励 / 总获奖人数)* 当前地址持有份额8 _! A; }8 i2 l# e& c1 s! G
- uint amt = (currAvalBalance / countryToPlayers[currRound][_country].length) * currCounts;
- // 玩家对应赢取的奖金
- winnerVaults[currWinner] += amt;
- distributeAmt += amt;; v$ g( @9 R) ^. u }
- // 放入待兑现的奖金池5 K( ~9 c8 ?" |5 H( m0 K: h0 P* p* i
- lockedAmts += amt;
- , K, k0 f J, j+ F6 r8 @
- console.log("winner:", currWinner, "currCounts:", currCounts);) d; u* L' m/ A/ ^
- console.log("reward amt curr:", amt, "total:", winnerVaults[currWinner]);
- }
- 6 J" v. Q: d; d z
- // 未分完的奖励即为平台收益& a' m: Z. N0 [6 n1 ~
- uint giftAmt = currAvalBalance - distributeAmt;
- if (giftAmt > 0) {
- winnerVaults[admin] += giftAmt;0 o) _4 x) j1 M. P# R
- }5 S, ?- E% S+ X1 M7 z6 r4 @. \1 |
- emit Finialize(currRound++, uint256(_country));, u$ }; c) `7 A4 z/ t3 r( B
- }
- // 奖金兑现
- function claimReward() external {+ E2 F3 Z" j1 C
- uint256 rewards = winnerVaults[msg.sender];
- require(rewards > 0, "nothing to claim!");4 S) P% d4 |4 f" N* y; b
- // 玩家领取完奖金置为 0) `$ _! W2 u6 d& M
- winnerVaults[msg.sender] = 0;8 ]* w: X( u/ X; d8 U' S7 e
- // 从待兑现奖金池中移除该玩家份额: k& X; L: [+ X
- lockedAmts -= rewards;! _4 c. t# }# C' v
- (bool succeed,) = msg.sender.call{value: rewards}("");
- require(succeed, "claim reward failed!");
- console.log("rewards:", rewards);; c5 Y+ R% U; b% U
- & o7 F0 p+ `0 |) W9 B* r
- emit ClaimReward(msg.sender, rewards);
- }0 K0 E) G/ E' `3 O/ |) g
- // 获取奖池金额+ ?+ \4 b1 I$ o/ {5 E$ V
- function getVaultBalance() public view returns(uint256 bal) {
- bal = address(this).balance;' N( z1 ~- z% ?# Q: L5 M
- }9 N) I9 i z }3 E# Z5 U
- 4 ? R& ]3 H+ G7 Q7 a! @
- // 获取当期下注当前球队的人数5 m9 U1 P% ^/ U- L
- function getCountryPlayers(uint8 _round, Country _country) external view returns(uint256) {
- return countryToPlayers[_round][_country].length;1 Z3 X* G3 h" V' Q1 P! a
- }% `! ~& C- V8 \
- 8 K% X% W7 `0 i( a; n% S
- // 获取当前玩家当期押注份额! v5 o8 |: j+ J5 c
- function getPlayerInfo(uint8 _round, address _player, Country _country) external view returns(uint256 _counts) {
- return players[_round][_player].counts[_country];9 Y+ w4 m9 z1 E( Z: z
- }6 M+ Y+ o3 ^ |5 `1 d0 [
- }
, L) I% x3 Y! v$ v! B; \6 T