- 参赛球队一经设定不可改变,整个活动结束后无法投票;
- 全⺠均可参与,无权限控制;
- 每次投票为 1 ether,且只能选择一支球队;
- 每个人可以投注多次;
- 仅管理员公布最终结果,完成奖金分配,开奖后逻辑:
- winner 共享整个奖金池(一部分是自己的本金,一部分是利润);
- winner 需自行领取奖金(因为有手续费);
- 下一期自行开始
- // SPDX-License-Identifier: GPL-3.0( ?' X5 F+ R. y( t6 g5 l( \
- * D: Z! ]! n: P
- pragma solidity >=0.7.0 <0.9.0;8 c5 @) |8 }* E3 W
- * R5 d4 y Z" ?& S0 t8 N
- import "hardhat/console.sol";
- " s- {. s; w+ n- j' j. U& K, F
- contract WorldCup {
- // 1. 状态变量:管理员、所有玩家、获奖者地址、第几期、参赛球队
- // 2. 核心方法:下注、开奖、兑现
- // 3. 辅助方法:获取奖金池金额、管理员地址、当前期数、参与人数、所有玩家、参赛球队% J5 p- n- t n
- 9 E2 P5 E- P* r6 j+ P
- // 管理员
- address public admin;5 e6 z8 g4 F' u( m/ n
- // 第几期
- uint8 public currRound;* d0 Q6 z* ]/ K7 I; W
- 7 P' N4 E/ A8 E% B- H( k) P
- // 参赛球队, q) \. ?( E( T9 [8 o
- string[] public countries = ["GERMANY", "FRANCH", "CHINA", "BRIZAL", "KOREA"];! R% G: T8 K/ l7 r; Q
- // 期数 => 玩家. S; a; w% ^. n# T
- mapping(uint8 => mapping(address => Player)) players;
- // 期数 => 投注各球队的玩家- Z$ d/ M( ?3 [8 _6 O+ G: S2 A
- mapping(uint8 => mapping(Country => address[])) public countryToPlayers;6 y+ R4 i0 S' r) c5 L* U! `
- // 玩家对应赢取的奖金
- mapping(address => uint256) public winnerVaults;" P( Z& j5 T2 T+ M+ H- [8 |
- T2 A$ y" [# A& [* l
- // 投注截止时间-使用不可变量,可通过构造函数传值,部署后无法改变' v/ G4 ^6 \& A+ _/ d8 V% F
- uint256 public immutable deadline;
- // 所有玩家待兑现的奖金1 t6 [$ b1 M' H+ w- X
- uint256 public lockedAmts;( E6 ~! z/ Y. \7 d( z# p8 t( w( [8 O
- enum Country {
- GERMANY,! w3 i/ ^, s+ w5 H
- FRANCH,5 a6 G7 ^* G# Z& C: D9 K+ R
- CHINA,
- BRAZIL,+ x, I8 c n: _/ U$ Y
- KOREA' G- g+ z' I+ c% u3 d
- }
- " I( u! [- J* P, H' Q8 G. ]' X$ m
- event Play(uint8 _currRound, address _player, Country _country);) t* C$ o, U7 [
- event Finialize(uint8 _currRound, uint256 _country);
- event ClaimReward(address _claimer, uint256 _amt);
- // 验证管理员身份
- modifier onlyAdmin {: v* _4 J I2 G8 @1 e( u
- require(msg.sender == admin, "not authorized!");
- _;
- }
- ' X! d% F0 J! V* N5 `* ]6 O
- // 玩家投注信息$ v0 r, P) H7 W0 Y
- struct Player {
- // 是否开奖8 T; `" @1 I* O1 V3 v7 ~7 g
- bool isSet;9 f. T$ f, | y0 T3 k; ?
- // 投注的球队份额3 I+ t X# |& L% B
- mapping(Country => uint256) counts;
- }
- constructor(uint256 _deadline) { i }' e7 Z% k1 R3 l& N( O
- admin = msg.sender;
- require(_deadline > block.timestamp, "WorldCupLottery: invalid deadline!");6 p, g' |8 R4 Q. x' X( G/ m
- deadline = _deadline;9 d; a+ g- n! O9 H4 t2 Q
- }; s6 H c! H. H% f+ _* ?% d
- : [0 p2 l) r u$ f6 t9 V7 W+ F8 O& ?
- // 下注过程1 N; G1 ~8 I) p7 u# w/ {* U! J
- function play(Country _selected) payable external {
- // 参数校验
- require(msg.value == 1 gwei, "invalid funds provided!");
- require(block.timestamp < deadline, "it's all over!");
- ! a4 `3 U( U9 D
- // 更新 countryToPlayers, m# E, f& r: J5 r6 U8 t# @
- countryToPlayers[currRound][_selected].push(msg.sender);
- // 更新 players(storage 是引用传值,修改会同步修改原变量): @% m- ^6 C* A; h2 S5 Y
- Player storage player = players[currRound][msg.sender];
- // player.isSet = false;
- player.counts[_selected] += 1;9 Z- p' O8 L, U6 f- [' B$ z4 y
- 3 F% w: N) O1 x( F4 @) Q# {$ o
- emit Play(currRound, msg.sender, _selected);
- }
- - v7 Y. M/ I F3 W: _: f
- // 开奖过程
- function finialize(Country _country) onlyAdmin external {
- // 找到 winners
- address[] memory winners = countryToPlayers[currRound][_country];
- // 分发给所有压中玩家的实际奖金8 V9 e$ a9 l3 F1 g& w6 Q$ e1 E
- uint256 distributeAmt;$ a1 T4 S) P9 s. p
- ) A, t0 ^' ]9 {+ d2 K$ w+ L
- // 本期总奖励金额(奖池金额 - 所有玩家待兑现的奖金)
- uint currAvalBalance = getVaultBalance() - lockedAmts;
- console.log("currAvalBalance:", currAvalBalance, "winners count:", winners.length);
- for (uint i = 0; i < winners.length; i++) {& g# b% B5 [/ }! M
- address currWinner = winners[i];
- // 获取每个地址应该得到的份额/ F' s" ]3 E7 T* r5 b# r
- Player storage winner = players[currRound][currWinner];
- if (winner.isSet) {# d! S, s2 Z! {" a/ A( z' l6 D- U
- console.log("this winner has been set already, will be skipped!");+ z$ M' X" W0 m4 ]3 j( K$ L" w
- continue;* g2 J& G; {0 b
- }- G0 J3 a8 w( r" h5 S, c/ z7 _
- winner.isSet = true;
- // 玩家购买的份额
- uint currCounts = winner.counts[_country];* ]5 J6 k, `, W1 }6 R4 o9 L
- // (本期总奖励 / 总获奖人数)* 当前地址持有份额
- uint amt = (currAvalBalance / countryToPlayers[currRound][_country].length) * currCounts;# d# d* }* S# N
- // 玩家对应赢取的奖金
- winnerVaults[currWinner] += amt;
- distributeAmt += amt;, B% n6 H& K' I3 L& V; w9 g' p
- // 放入待兑现的奖金池
- lockedAmts += amt;/ n2 p1 T5 V3 h" I
- 8 z: l ]# {: f' _% T. [. |1 N
- console.log("winner:", currWinner, "currCounts:", currCounts);9 R; E* v, C- ^2 k; i4 G) W1 D
- console.log("reward amt curr:", amt, "total:", winnerVaults[currWinner]);7 u0 I; w7 E3 E h
- }' u$ _2 X' I+ R ]' D5 X
- // 未分完的奖励即为平台收益& r6 `/ U* z/ i! u) t
- uint giftAmt = currAvalBalance - distributeAmt;
- if (giftAmt > 0) {& x/ ?5 e. D1 z8 q; P
- winnerVaults[admin] += giftAmt;; h3 }/ E A0 b+ ~5 i
- }
- emit Finialize(currRound++, uint256(_country));
- }5 V' N, W: Z( y- S# g+ `
- . J+ p$ I; g0 H
- // 奖金兑现
- function claimReward() external {( l+ }2 g/ E! j/ G$ @; }# q
- uint256 rewards = winnerVaults[msg.sender];
- require(rewards > 0, "nothing to claim!");
- // 玩家领取完奖金置为 0- D, o0 }& {( u9 D
- winnerVaults[msg.sender] = 0;
- // 从待兑现奖金池中移除该玩家份额7 D! r$ t8 M: [) k$ J
- lockedAmts -= rewards;
- (bool succeed,) = msg.sender.call{value: rewards}("");
- require(succeed, "claim reward failed!");
- 4 B$ Y+ m% l6 Z; J
- console.log("rewards:", rewards);
- ( g# n: g, D/ {' b4 h
- emit ClaimReward(msg.sender, rewards);
- }
- 0 w' t$ w% v2 f8 H
- // 获取奖池金额
- function getVaultBalance() public view returns(uint256 bal) {
- bal = address(this).balance;9 P' l( n) m& }
- }
- * r" M7 O) y* S- W
- // 获取当期下注当前球队的人数
- function getCountryPlayers(uint8 _round, Country _country) external view returns(uint256) {
- return countryToPlayers[_round][_country].length;
- }$ X$ h. }. Z+ N+ G; I
- // 获取当前玩家当期押注份额4 M, n4 k3 a1 p; t* h' K. Z
- function getPlayerInfo(uint8 _round, address _player, Country _country) external view returns(uint256 _counts) {
- return players[_round][_player].counts[_country];4 }9 E1 O1 v( M8 x& b$ p+ e
- }) n! _% \* V/ V% t$ W
- }
. k/ ~5 V( d" D1 O/ {9 T* G
Q: k/ R- _1 M2 H7 I