- 参赛球队一经设定不可改变,整个活动结束后无法投票;
- 全⺠均可参与,无权限控制;
- 每次投票为 1 ether,且只能选择一支球队;
- 每个人可以投注多次;
- 仅管理员公布最终结果,完成奖金分配,开奖后逻辑:
- winner 共享整个奖金池(一部分是自己的本金,一部分是利润);
- winner 需自行领取奖金(因为有手续费);
- 下一期自行开始
; w# _' f! _( e
- 0 ?0 r4 [& B% D# Y9 w% ]
- // SPDX-License-Identifier: GPL-3.07 o4 r6 D8 X+ e; B, {, @ s: a
- ; H- P: C! B$ Y) }- B- @8 D2 }# C
- pragma solidity >=0.7.0 <0.9.0;+ g% \4 a0 E8 |" f3 k: Y
- " A. o& l9 C: B6 X2 y6 t
- import "hardhat/console.sol";; v" I1 Y G; Q; I. j9 |% i/ J
- contract WorldCup {* Y; h- C; ?' X
- // 1. 状态变量:管理员、所有玩家、获奖者地址、第几期、参赛球队 |$ I ]7 ?! N& r* B- B6 G" v0 r
- // 2. 核心方法:下注、开奖、兑现
- // 3. 辅助方法:获取奖金池金额、管理员地址、当前期数、参与人数、所有玩家、参赛球队
- // 管理员- P4 K) i. q9 p9 A! T9 u( x
- address public admin;, o+ D( b9 w+ q \. Y
- // 第几期
- uint8 public currRound;
- / V$ @5 T$ b) d& Q# P, Y
- // 参赛球队7 S( q: A! c% J( _) i- O! U& e
- string[] public countries = ["GERMANY", "FRANCH", "CHINA", "BRIZAL", "KOREA"];
- // 期数 => 玩家
- mapping(uint8 => mapping(address => Player)) players;
- // 期数 => 投注各球队的玩家
- mapping(uint8 => mapping(Country => address[])) public countryToPlayers;
- // 玩家对应赢取的奖金
- mapping(address => uint256) public winnerVaults;7 ?7 n8 O, d9 P2 X v
- 3 s9 U6 _, Y4 }. D7 k5 l0 x/ T" H
- // 投注截止时间-使用不可变量,可通过构造函数传值,部署后无法改变% F1 }, g$ U0 B& m1 o
- uint256 public immutable deadline;
- // 所有玩家待兑现的奖金/ s6 N) C# ?2 K% r, c5 S$ J9 D
- uint256 public lockedAmts;5 N) i) M7 P$ `" I: }
- : R1 g$ }& N; f; k/ T) L
- enum Country {
- GERMANY,
- FRANCH,% g% l. v* _4 m) _/ i
- CHINA,
- BRAZIL,
- KOREA8 [1 g. M0 W, S
- }7 u$ o- D8 c7 S9 j/ m" @
- event Play(uint8 _currRound, address _player, Country _country);, x& J4 z# P$ M
- event Finialize(uint8 _currRound, uint256 _country);# W7 N# W) E9 G& W1 P
- event ClaimReward(address _claimer, uint256 _amt);5 q8 ^/ l6 y; u, m5 d' S
- // 验证管理员身份# m0 @$ W, [& d" a0 R& L7 Q! o
- modifier onlyAdmin {
- require(msg.sender == admin, "not authorized!");9 E6 c: I7 _0 X2 X0 c" n) s* R3 }+ n4 w
- _;1 X9 k1 L+ e! P) v5 p
- }
- # X/ A+ J' k( c. q' X' c7 A) t" [
- // 玩家投注信息
- struct Player {
- // 是否开奖( t7 S4 O, i$ m1 L0 O% ?$ j9 [
- bool isSet;
- // 投注的球队份额
- mapping(Country => uint256) counts;) W/ {; r$ u+ [9 M
- }1 g- K/ w0 U4 @% ~8 M
- 2 [% `0 s% R9 G& V
- constructor(uint256 _deadline) {4 P7 r! w# y/ m4 T' D
- admin = msg.sender;
- require(_deadline > block.timestamp, "WorldCupLottery: invalid deadline!");0 w, V5 c7 ]. Z: b; A
- deadline = _deadline;
- }
- // 下注过程
- function play(Country _selected) payable external {
- // 参数校验
- require(msg.value == 1 gwei, "invalid funds provided!");) }# R2 f; u8 `8 L9 _: U
- 1 w! C: D; Z0 l* Y: ^; \& L
- require(block.timestamp < deadline, "it's all over!");9 p' A% \2 r; q4 S0 K. M
- * [ m2 N6 e- [" B5 x# l0 R
- // 更新 countryToPlayers/ ^* Y! m$ @% R: J
- countryToPlayers[currRound][_selected].push(msg.sender);
- // 更新 players(storage 是引用传值,修改会同步修改原变量)! V) K/ j1 S4 l' ]2 M3 ~7 z0 F- m
- Player storage player = players[currRound][msg.sender];
- // player.isSet = false;; v/ \/ J# L' v9 ~( c: k0 S
- player.counts[_selected] += 1;
- 2 L! b4 c/ @1 s& B; \1 Y& Q
- emit Play(currRound, msg.sender, _selected);
- }& n/ h1 F8 m0 m& U* F" S
- ; I5 Q: ?: V3 e$ X8 {5 V: {& `6 k
- // 开奖过程8 y7 i k; ^# A' m+ m# \+ q
- function finialize(Country _country) onlyAdmin external { }* J! K6 H% k' a4 F& m9 Q
- // 找到 winners4 M' B) }1 V) Q. e& T% Q/ T- w* h
- address[] memory winners = countryToPlayers[currRound][_country];
- // 分发给所有压中玩家的实际奖金
- uint256 distributeAmt;5 N+ Y. {0 V5 ?6 S) ?. Z6 f
- ) y/ F* z0 [ s* y* V, C
- // 本期总奖励金额(奖池金额 - 所有玩家待兑现的奖金); j3 Z1 l, O: q0 G7 G7 u, s
- uint currAvalBalance = getVaultBalance() - lockedAmts;9 T2 R0 y: P6 }1 U
- console.log("currAvalBalance:", currAvalBalance, "winners count:", winners.length);1 G9 v/ d/ x! P
- for (uint i = 0; i < winners.length; i++) {& y. B' Q9 r ]3 n2 U% f- V. o
- address currWinner = winners[i];
- // 获取每个地址应该得到的份额
- Player storage winner = players[currRound][currWinner];
- if (winner.isSet) {
- console.log("this winner has been set already, will be skipped!");% {5 U% N+ Y1 V
- continue;- |( k# o" ` `& m. U1 u5 a
- }; ~2 M) N" J3 k$ M
- winner.isSet = true;
- // 玩家购买的份额8 P+ o* ]) O( i# s8 }
- uint currCounts = winner.counts[_country];+ d2 b2 i$ a. B* @
- // (本期总奖励 / 总获奖人数)* 当前地址持有份额
- uint amt = (currAvalBalance / countryToPlayers[currRound][_country].length) * currCounts;0 A/ t" i5 l4 a
- // 玩家对应赢取的奖金* ?7 q9 y! y! j1 C9 O
- winnerVaults[currWinner] += amt;
- distributeAmt += amt;
- // 放入待兑现的奖金池" ?! S, q- u3 ~7 C
- lockedAmts += amt;
- % X+ [* C8 N. Y1 I2 t
- console.log("winner:", currWinner, "currCounts:", currCounts);
- console.log("reward amt curr:", amt, "total:", winnerVaults[currWinner]);
- }
- // 未分完的奖励即为平台收益
- uint giftAmt = currAvalBalance - distributeAmt;
- if (giftAmt > 0) {
- winnerVaults[admin] += giftAmt;5 e a5 N' H7 D4 \ b! j
- }
- emit Finialize(currRound++, uint256(_country));
- }
- // 奖金兑现
- function claimReward() external {
- uint256 rewards = winnerVaults[msg.sender];
- require(rewards > 0, "nothing to claim!");$ N Q0 Q& t' F/ D
- // 玩家领取完奖金置为 0' T' l1 c* J1 |0 k x
- winnerVaults[msg.sender] = 0;
- // 从待兑现奖金池中移除该玩家份额3 x; f/ \7 X" d
- lockedAmts -= rewards;9 q3 ^; V+ L" j1 a; J( J
- (bool succeed,) = msg.sender.call{value: rewards}("");
- require(succeed, "claim reward failed!");% v: \) w5 ?4 }2 G
- console.log("rewards:", rewards);
- 1 S# x2 P2 o H! V6 @. L. n
- emit ClaimReward(msg.sender, rewards);
- }3 t$ g% d$ P7 a4 K
- + V. e6 R% |; ]! \$ z
- // 获取奖池金额
- function getVaultBalance() public view returns(uint256 bal) {/ K k& L: j+ R5 n9 O
- bal = address(this).balance;
- }
- . v- l' J3 e7 X6 l. Z9 e" M# n
- // 获取当期下注当前球队的人数
- function getCountryPlayers(uint8 _round, Country _country) external view returns(uint256) {
- return countryToPlayers[_round][_country].length;
- }6 `& i- e+ X8 ^0 a; }
- 3 j- ~4 i* e1 Q# x
- // 获取当前玩家当期押注份额. S0 U9 z5 ?6 c: r1 M* k8 G, U
- function getPlayerInfo(uint8 _round, address _player, Country _country) external view returns(uint256 _counts) {
- return players[_round][_player].counts[_country];
- }" l7 H1 K- O, i7 g/ [
- }
! u, ~" v- [9 ^: U) d