- 参赛球队一经设定不可改变,整个活动结束后无法投票;
- 全⺠均可参与,无权限控制;
- 每次投票为 1 ether,且只能选择一支球队;
- 每个人可以投注多次;
- 仅管理员公布最终结果,完成奖金分配,开奖后逻辑:
- winner 共享整个奖金池(一部分是自己的本金,一部分是利润);
- winner 需自行领取奖金(因为有手续费);
- 下一期自行开始: V u% ?- K& {. H1 v
) g; [! W% {* E1 I1 y- U0 x/ N+ [! L
9 z! R. M" ^' b5 l% v9 ~
- // SPDX-License-Identifier: GPL-3.04 z. E/ W$ P e2 I6 q( }3 T
- 9 A& J& c% D4 h) I8 s$ k
- pragma solidity >=0.7.0 <0.9.0;% n# Z! `, F( P1 W ~9 E0 F* Y4 F& o' e
- import "hardhat/console.sol";, c" _, z% j& p6 V4 ~1 r5 y: [
- contract WorldCup {
- // 1. 状态变量:管理员、所有玩家、获奖者地址、第几期、参赛球队' l5 T" j$ l0 `# |; n: u6 y
- // 2. 核心方法:下注、开奖、兑现
- // 3. 辅助方法:获取奖金池金额、管理员地址、当前期数、参与人数、所有玩家、参赛球队
- // 管理员. g" Z& q! E3 C! d' D, {- r
- address public admin;
- // 第几期/ a" Z1 f+ w8 s( D5 U2 ]7 W2 X1 O/ }
- uint8 public currRound;: v+ q* V% e. {- I( w- S8 V
- ; V& H0 r. ^4 [% O! {
- // 参赛球队
- string[] public countries = ["GERMANY", "FRANCH", "CHINA", "BRIZAL", "KOREA"];. L! X6 m+ G7 j9 ?- d
- // 期数 => 玩家9 p) b+ L, X* C7 E3 u% }
- mapping(uint8 => mapping(address => Player)) players;0 J/ q' \, Y! @6 i" C# a$ a
- // 期数 => 投注各球队的玩家- _; ~% G' T: M" j! k/ _
- mapping(uint8 => mapping(Country => address[])) public countryToPlayers;
- // 玩家对应赢取的奖金
- mapping(address => uint256) public winnerVaults;
- 6 o3 {+ w' E0 {) e1 k
- // 投注截止时间-使用不可变量,可通过构造函数传值,部署后无法改变
- uint256 public immutable deadline;- u8 I. _8 o6 X2 d
- // 所有玩家待兑现的奖金: e+ z# D/ C) x; v- K6 O v9 T
- uint256 public lockedAmts;! c( J' V# ~% U$ B2 k# B
- enum Country {
- GERMANY,6 V) ~0 i+ m. Z! ^ N; f
- FRANCH,5 T3 f8 x& {6 f7 s& _1 j+ X
- CHINA,) |8 m$ z6 T' a" l. d& O
- BRAZIL,6 n Z$ o+ t# p- E" [2 ?
- KOREA/ B+ {: ~/ o6 a% ]
- }
- event Play(uint8 _currRound, address _player, Country _country);3 W; i: L3 D% M
- event Finialize(uint8 _currRound, uint256 _country);
- event ClaimReward(address _claimer, uint256 _amt);
- // 验证管理员身份
- modifier onlyAdmin {) j; [" f% e! g. [- K% |
- require(msg.sender == admin, "not authorized!");
- _;/ m; I8 j2 v3 C+ Q
- }
- - h5 R5 ?/ X7 n/ a% @
- // 玩家投注信息
- struct Player {! T+ T% o% E$ c/ F6 e4 j
- // 是否开奖' b( I, Z! D4 V& Q1 N, x. d8 `1 Y7 x
- bool isSet;6 h8 Y0 S7 H5 X% l9 Y/ l8 u0 _
- // 投注的球队份额( ]% Y) c6 l/ Y* [. ]# A
- mapping(Country => uint256) counts;+ i) e; C8 f0 Q o' I- d
- }, F; H9 V) X2 d
- constructor(uint256 _deadline) {! l6 ?" E) d# A5 Y) T
- admin = msg.sender;0 J2 j- i" V$ Y4 {
- require(_deadline > block.timestamp, "WorldCupLottery: invalid deadline!");
- deadline = _deadline;
- }5 B7 v: q: }+ F# C6 L1 x
- // 下注过程, Q& |9 p1 v9 j8 N
- function play(Country _selected) payable external {* j* L9 y3 W+ J( k. X
- // 参数校验
- require(msg.value == 1 gwei, "invalid funds provided!");
- $ W- A& O( ~& ]7 |9 y* k! H
- require(block.timestamp < deadline, "it's all over!");( J7 L s+ [- V0 B
- // 更新 countryToPlayers8 X0 E7 w$ R, \! f7 d2 i1 a& u
- countryToPlayers[currRound][_selected].push(msg.sender);
- // 更新 players(storage 是引用传值,修改会同步修改原变量)2 `' g3 t6 G' N/ | l
- Player storage player = players[currRound][msg.sender];* G' \6 V# ]2 N" |6 L5 u
- // player.isSet = false;
- player.counts[_selected] += 1;! E: _. v8 a. ]) T8 D- `$ _
- 6 I D, W/ M% U+ V8 [
- emit Play(currRound, msg.sender, _selected);
- }
- // 开奖过程
- function finialize(Country _country) onlyAdmin external {0 ]3 A# W- _4 q6 z E
- // 找到 winners" G6 ]0 U1 e4 N9 M9 h% P
- address[] memory winners = countryToPlayers[currRound][_country];
- // 分发给所有压中玩家的实际奖金& T1 Y( [3 \3 o' Y, p
- uint256 distributeAmt;* N4 V" S. \/ @& k; G& t2 [1 F9 e
- % \( M. P# Z j2 `" S
- // 本期总奖励金额(奖池金额 - 所有玩家待兑现的奖金)
- uint currAvalBalance = getVaultBalance() - lockedAmts;5 c( |7 `1 J" V0 [$ |! c
- console.log("currAvalBalance:", currAvalBalance, "winners count:", winners.length);
- for (uint i = 0; i < winners.length; i++) {: N4 x" u' b1 C" V7 h
- address currWinner = winners[i];
- // 获取每个地址应该得到的份额3 [# q; }. f, P/ M1 }$ g
- Player storage winner = players[currRound][currWinner];2 h3 j3 p! a3 o& h7 a& d
- if (winner.isSet) {
- console.log("this winner has been set already, will be skipped!");
- continue;
- }
- winner.isSet = true;2 R$ j- U! e* P( F5 Y3 r' a& _/ E
- // 玩家购买的份额
- uint currCounts = winner.counts[_country];! V+ l2 t( Q* U- U+ Q6 ^
- // (本期总奖励 / 总获奖人数)* 当前地址持有份额
- uint amt = (currAvalBalance / countryToPlayers[currRound][_country].length) * currCounts;! }) n: M$ r, Q
- // 玩家对应赢取的奖金
- winnerVaults[currWinner] += amt;: q" y( e2 i0 g" X( B5 u
- distributeAmt += amt;
- // 放入待兑现的奖金池
- lockedAmts += amt;2 x0 K# J7 s4 ?! L
- console.log("winner:", currWinner, "currCounts:", currCounts);, `, l: ?+ P* V! {
- console.log("reward amt curr:", amt, "total:", winnerVaults[currWinner]);
- }- ~+ L( K' k- S+ m. y5 ]
- ! {& h' Q! R. m6 a+ Q
- // 未分完的奖励即为平台收益; ^: b8 @" o& Y% Q6 E$ x, D4 i+ s
- uint giftAmt = currAvalBalance - distributeAmt;
- if (giftAmt > 0) {' n) E E# j) ]2 D6 Y R; U
- winnerVaults[admin] += giftAmt;
- }7 W. b: M+ W2 E
- ( c7 \. `- _7 q) x0 j8 w$ s: y
- emit Finialize(currRound++, uint256(_country));
- }
- // 奖金兑现
- function claimReward() external {" U! w, z/ k- b2 y2 K( L
- uint256 rewards = winnerVaults[msg.sender];
- require(rewards > 0, "nothing to claim!");
- // 玩家领取完奖金置为 0
- winnerVaults[msg.sender] = 0;" M( G- e3 \" D+ t; L
- // 从待兑现奖金池中移除该玩家份额
- lockedAmts -= rewards;0 D+ V& o& h- s) e }) D4 o5 q
- (bool succeed,) = msg.sender.call{value: rewards}("");
- require(succeed, "claim reward failed!");/ e. J: s$ U" P( `& |
- 8 U. I$ E, s1 ]! K7 ]* r8 h
- console.log("rewards:", rewards);, @) n' o) n6 ^6 E- m
- 2 Y. _3 Q0 h4 ~. z! |8 |' Y9 B
- emit ClaimReward(msg.sender, rewards);
- }" b, E2 S# m% O5 `+ W
- // 获取奖池金额6 j1 R+ X/ @! A: D1 k0 Y
- function getVaultBalance() public view returns(uint256 bal) {8 v$ W0 ?. D8 c8 i, ^$ @
- bal = address(this).balance;
- }5 v6 u) V8 Z: D6 T1 [
- 1 [0 K4 g1 j% n9 S/ m5 Z5 [
- // 获取当期下注当前球队的人数+ _0 _+ w4 G0 |% F* w$ z; y
- function getCountryPlayers(uint8 _round, Country _country) external view returns(uint256) {- E% s6 j! c9 l$ Q2 z/ {. E
- return countryToPlayers[_round][_country].length;
- }6 A" r" j2 n7 G- F+ W
- 4 g! Z: T" d0 N+ }8 z
- // 获取当前玩家当期押注份额
- function getPlayerInfo(uint8 _round, address _player, Country _country) external view returns(uint256 _counts) {
- return players[_round][_player].counts[_country];5 e1 Z$ Y ]1 T0 v/ d, J6 ?
- }
- }
4 }( A- V' O2 D @* @4 ^