- 参赛球队一经设定不可改变,整个活动结束后无法投票;
- 全⺠均可参与,无权限控制;
- 每次投票为 1 ether,且只能选择一支球队;
- 每个人可以投注多次;
- 仅管理员公布最终结果,完成奖金分配,开奖后逻辑:
- winner 共享整个奖金池(一部分是自己的本金,一部分是利润);
- winner 需自行领取奖金(因为有手续费);
- 下一期自行开始/ f& V' y) `- ~3 ^. l5 A
) K: Q* U+ @* {$ q% x4 Y( D3 g
- ) I. {' m% h/ Y5 ~1 R) [$ t% t
- // SPDX-License-Identifier: GPL-3.05 j- N# m% K* `1 V; G0 I6 A9 y
- pragma solidity >=0.7.0 <0.9.0;3 p% c j( c# E' E7 g; {* A5 _
- import "hardhat/console.sol";
- 9 R. ]7 O. t# O7 E: I O
- contract WorldCup {1 z7 f# S1 r3 F' `6 ~
- // 1. 状态变量:管理员、所有玩家、获奖者地址、第几期、参赛球队5 e8 w6 D/ k" c e0 y2 j
- // 2. 核心方法:下注、开奖、兑现
- // 3. 辅助方法:获取奖金池金额、管理员地址、当前期数、参与人数、所有玩家、参赛球队6 J) @- `4 s# l. {6 }
- // 管理员+ V, N. U4 ~. B2 y0 t
- address public admin;5 P* }$ |8 B `0 W, B( J3 Q/ A4 _7 }7 [0 a
- // 第几期% M/ Z& v, l) V$ @: v, E
- uint8 public currRound;) D K4 g. T* j) C
- ( Q5 |) @5 i# h
- // 参赛球队; E0 W6 X+ Q# i2 |) d
- string[] public countries = ["GERMANY", "FRANCH", "CHINA", "BRIZAL", "KOREA"];* @! {+ n+ g! u' [6 D
- // 期数 => 玩家5 x0 r* T( n' l& d/ D: h
- mapping(uint8 => mapping(address => Player)) players;, |, l6 U5 u# l
- // 期数 => 投注各球队的玩家" L# _4 ~/ r4 t1 a
- mapping(uint8 => mapping(Country => address[])) public countryToPlayers;
- // 玩家对应赢取的奖金
- mapping(address => uint256) public winnerVaults;
- // 投注截止时间-使用不可变量,可通过构造函数传值,部署后无法改变
- uint256 public immutable deadline;
- // 所有玩家待兑现的奖金
- uint256 public lockedAmts;* P! Z5 j2 Y% p& z) ?! Z$ ^
- U+ m! C: ]. d X& u. H0 S
- enum Country {' @( _2 t- N2 s5 @) m A
- GERMANY,
- FRANCH,
- CHINA,0 ~/ C8 a# [/ a( S' h
- BRAZIL," M' q7 A1 N7 r# t
- KOREA4 K4 |) n5 _% Y/ G
- }
- . I1 x, W5 G0 F j3 A3 d
- event Play(uint8 _currRound, address _player, Country _country);' F0 P: A4 F% s8 D: [$ t3 G
- event Finialize(uint8 _currRound, uint256 _country);5 X* S' Z9 B' o9 t0 R! G
- event ClaimReward(address _claimer, uint256 _amt);
- + N, ?8 n5 F) I% o3 r8 d
- // 验证管理员身份; H& b2 U9 U# p( x/ }
- modifier onlyAdmin {
- require(msg.sender == admin, "not authorized!");
- _;
- }
- // 玩家投注信息: f% A0 n& { |& c$ S* }9 g1 n
- struct Player {4 b/ O- N9 _6 |
- // 是否开奖) R: I- o n& N+ E
- bool isSet;
- // 投注的球队份额
- mapping(Country => uint256) counts;/ r+ l `% a6 y: Y4 v8 ~
- }
- constructor(uint256 _deadline) {
- admin = msg.sender;
- require(_deadline > block.timestamp, "WorldCupLottery: invalid deadline!");3 D$ U; f( ^9 r, h
- deadline = _deadline;
- }4 @: b) b0 I. Z. d5 r# k
- // 下注过程1 Z( C' _8 b Y
- function play(Country _selected) payable external {! U- {4 j: p; n+ p1 F
- // 参数校验
- require(msg.value == 1 gwei, "invalid funds provided!");+ `2 y# F& t# `9 z5 D+ V
- : u' Z7 q5 D; y0 F [- t
- require(block.timestamp < deadline, "it's all over!");' k$ Z' P9 A/ k5 n8 M# R
- : X, O, W) r) P" f0 }' ]2 }
- // 更新 countryToPlayers
- countryToPlayers[currRound][_selected].push(msg.sender);
- // 更新 players(storage 是引用传值,修改会同步修改原变量)
- Player storage player = players[currRound][msg.sender];
- // player.isSet = false;" h R! {& ~/ G& ]! }
- player.counts[_selected] += 1;
- 1 O$ F6 `: D* p
- emit Play(currRound, msg.sender, _selected);! p, {$ ?# \- O5 i. o
- }
- // 开奖过程
- function finialize(Country _country) onlyAdmin external {
- // 找到 winners3 _% V- ~' u! |/ H5 ]- p$ y
- address[] memory winners = countryToPlayers[currRound][_country];; E7 M/ n0 D2 d/ J2 M
- // 分发给所有压中玩家的实际奖金3 D2 u) D% m5 _# r+ p% z
- uint256 distributeAmt;
- // 本期总奖励金额(奖池金额 - 所有玩家待兑现的奖金)# B' P" J% P: ]0 f
- uint currAvalBalance = getVaultBalance() - lockedAmts;
- console.log("currAvalBalance:", currAvalBalance, "winners count:", winners.length);; {9 K& L* Q. a! O# S- h
- - b: [4 }# |! p! h
- for (uint i = 0; i < winners.length; i++) {) j! Y" x/ t$ l
- address currWinner = winners[i];) p' q% y) D: \
- // 获取每个地址应该得到的份额
- Player storage winner = players[currRound][currWinner];
- if (winner.isSet) {' v4 f; Y6 Y6 j" W
- console.log("this winner has been set already, will be skipped!");) j4 q6 Q0 X, b# {# v# \
- continue;
- }9 Z0 W" }6 o4 U: j5 V
- 5 i9 @( a+ I$ j+ M1 I6 h$ r
- winner.isSet = true;" S5 _+ v5 x/ F$ k+ ?
- // 玩家购买的份额, V8 P1 g) ?" o t( `. ~
- uint currCounts = winner.counts[_country];
- 7 j& q7 a, m7 }7 |7 A
- // (本期总奖励 / 总获奖人数)* 当前地址持有份额, W) L+ w6 {) V- G% j
- uint amt = (currAvalBalance / countryToPlayers[currRound][_country].length) * currCounts;
- // 玩家对应赢取的奖金( Z. u+ b$ H( T
- winnerVaults[currWinner] += amt;
- distributeAmt += amt;, p# ^- Z h) e( f3 Y
- // 放入待兑现的奖金池
- lockedAmts += amt;' [+ T. r! U% Q
- console.log("winner:", currWinner, "currCounts:", currCounts);# J) C: ^0 N: r2 Q' Z3 i# `
- console.log("reward amt curr:", amt, "total:", winnerVaults[currWinner]);4 k- o" X( i# j: @- u0 u" D( D
- }
- ( P, M- w9 f7 T2 w% C, n: M, t
- // 未分完的奖励即为平台收益; V" J& M" _, m
- uint giftAmt = currAvalBalance - distributeAmt;/ n# E. B1 \1 r1 s) p
- if (giftAmt > 0) {
- winnerVaults[admin] += giftAmt;
- }
- 7 }6 \+ T9 O+ S9 n$ K* U
- emit Finialize(currRound++, uint256(_country));
- }/ s7 @7 j$ g9 Q% O4 H
- // 奖金兑现8 ]& d* w* E* Q4 p: v# O
- function claimReward() external {1 w4 R3 ]' X* {
- uint256 rewards = winnerVaults[msg.sender];
- require(rewards > 0, "nothing to claim!");
- / E- J+ m# C9 z3 i
- // 玩家领取完奖金置为 0 v# p( B7 L& t! |/ _3 b* S% W. a
- winnerVaults[msg.sender] = 0;
- // 从待兑现奖金池中移除该玩家份额7 r5 l) P. B2 B7 t2 s
- lockedAmts -= rewards;; J9 E: |3 L: T. O2 Q
- (bool succeed,) = msg.sender.call{value: rewards}("");
- require(succeed, "claim reward failed!");& T9 G1 T( k8 X: C. u
- 6 P O* _+ S; B$ T# {$ J
- console.log("rewards:", rewards);9 X) X0 m6 b2 S$ @. T) T& w; y
- , N8 U+ l, F, \6 P3 n. E
- emit ClaimReward(msg.sender, rewards);
- }
- 4 h. h/ q! `9 w: S2 l
- // 获取奖池金额 l' B, S- c. Y1 i6 B. P. b
- function getVaultBalance() public view returns(uint256 bal) {
- bal = address(this).balance;1 v! o+ r3 ?9 F3 I1 |) o
- }& d9 ~0 G: |0 z( c
- // 获取当期下注当前球队的人数8 F# {! x- D3 }) T
- function getCountryPlayers(uint8 _round, Country _country) external view returns(uint256) {
- return countryToPlayers[_round][_country].length;. \0 `/ Y4 ]0 k2 H( g7 \6 y
- }
- // 获取当前玩家当期押注份额 F& y2 E6 }/ H4 M3 X8 D
- function getPlayerInfo(uint8 _round, address _player, Country _country) external view returns(uint256 _counts) {
- return players[_round][_player].counts[_country];
- }% L0 d5 C6 d1 _! k* X
- }
8 }4 s8 N( `2 J& p+ ~