- 参赛球队一经设定不可改变,整个活动结束后无法投票;
- 全⺠均可参与,无权限控制;
- 每次投票为 1 ether,且只能选择一支球队;
- 每个人可以投注多次;
- 仅管理员公布最终结果,完成奖金分配,开奖后逻辑:
- winner 共享整个奖金池(一部分是自己的本金,一部分是利润);
- winner 需自行领取奖金(因为有手续费);
- 下一期自行开始
|5 U6 k/ I0 f, B
- // SPDX-License-Identifier: GPL-3.0
- pragma solidity >=0.7.0 <0.9.0;5 J$ [0 w* A; H. f: \9 [: \
- 1 J! a; v t. J, m' J+ O( ?/ H6 l2 u
- import "hardhat/console.sol";. d% b. T) o2 L p9 |$ A
- contract WorldCup {
- // 1. 状态变量:管理员、所有玩家、获奖者地址、第几期、参赛球队1 p; _+ {# B' m
- // 2. 核心方法:下注、开奖、兑现
- // 3. 辅助方法:获取奖金池金额、管理员地址、当前期数、参与人数、所有玩家、参赛球队
- . g8 @; Z) Z5 A! f2 E1 [: t
- // 管理员
- address public admin;7 c' U, O% c, w
- // 第几期
- uint8 public currRound;! z1 I0 g5 a' r. Q( M0 f) j! B
- 7 u$ V- _% [4 k5 J- b5 X& b
- // 参赛球队- R* L1 H: T0 p1 k# V6 l' I
- string[] public countries = ["GERMANY", "FRANCH", "CHINA", "BRIZAL", "KOREA"];+ H6 k3 U8 x. K! `7 ?
- // 期数 => 玩家
- mapping(uint8 => mapping(address => Player)) players;
- // 期数 => 投注各球队的玩家
- mapping(uint8 => mapping(Country => address[])) public countryToPlayers;
- // 玩家对应赢取的奖金
- mapping(address => uint256) public winnerVaults;3 S3 w( b4 |( T! v+ j: E5 `8 w
- 7 A N9 F% m2 x$ ^9 P* P, O1 d9 e
- // 投注截止时间-使用不可变量,可通过构造函数传值,部署后无法改变0 S3 G! P6 U% h" M6 T& i) g
- uint256 public immutable deadline;; g% h" T, G9 c! }
- // 所有玩家待兑现的奖金
- uint256 public lockedAmts;& T3 H: w% o0 c/ `2 Z- l; {
- enum Country {
- GERMANY,
- FRANCH,/ r$ Y: {' N' C! A: g
- CHINA,
- BRAZIL,
- KOREA* j9 ^+ u( F! S
- }" C4 G! ^6 G, a/ J9 y6 R
- , e$ Y# B% ^$ R/ f
- event Play(uint8 _currRound, address _player, Country _country);" V& g$ u/ y. @$ c
- event Finialize(uint8 _currRound, uint256 _country);: A8 G8 t% g: T* X- O
- event ClaimReward(address _claimer, uint256 _amt);/ x$ l7 ]: r8 f( ^1 r+ e% V4 d X
- // 验证管理员身份& A* B' ?0 o3 C8 O9 F5 v
- modifier onlyAdmin {4 l E: O- g+ h+ K: S; X
- require(msg.sender == admin, "not authorized!");
- _;
- }
- // 玩家投注信息# J; L+ y; m9 B! V, a9 a/ g1 }$ k" c
- struct Player {7 D# L0 q2 c0 ?" X5 l6 p v5 u
- // 是否开奖
- bool isSet;
- // 投注的球队份额
- mapping(Country => uint256) counts;2 f; V6 V9 W6 }* J
- }
- : C# F4 k+ J' E: M& E
- constructor(uint256 _deadline) {
- admin = msg.sender;) w+ d! k, I2 |0 e6 g$ I
- require(_deadline > block.timestamp, "WorldCupLottery: invalid deadline!");
- deadline = _deadline;) _2 L1 P( M% I) f5 T0 L
- }
- // 下注过程2 l6 _( \8 R. A
- function play(Country _selected) payable external {
- // 参数校验
- require(msg.value == 1 gwei, "invalid funds provided!");- v a" Q. Y/ ?+ S- m
- require(block.timestamp < deadline, "it's all over!");7 c' e! K3 y0 M3 N; R
- 1 _2 v( b6 G+ T5 U$ a$ j
- // 更新 countryToPlayers0 J3 h, P- k5 L; {; { n
- countryToPlayers[currRound][_selected].push(msg.sender);
- // 更新 players(storage 是引用传值,修改会同步修改原变量)
- Player storage player = players[currRound][msg.sender];
- // player.isSet = false;! }7 Z1 |. i4 Q4 q$ c
- player.counts[_selected] += 1;
- 2 P+ H9 f e5 Q( v# ?( }: C
- emit Play(currRound, msg.sender, _selected);9 O. s2 ~, t1 N/ P( ^
- }
- // 开奖过程
- function finialize(Country _country) onlyAdmin external {! O" A& r6 Z* a: l
- // 找到 winners
- address[] memory winners = countryToPlayers[currRound][_country];
- // 分发给所有压中玩家的实际奖金- a' J1 L0 U$ U
- uint256 distributeAmt;' q7 U8 O6 c. M, @
- // 本期总奖励金额(奖池金额 - 所有玩家待兑现的奖金)
- uint currAvalBalance = getVaultBalance() - lockedAmts;3 z( a" `4 Y: m2 Z$ p1 v
- console.log("currAvalBalance:", currAvalBalance, "winners count:", winners.length);6 a7 A1 G1 W7 {1 Z% N. Y R# F
- 0 E# u. R s% [! d
- for (uint i = 0; i < winners.length; i++) {. U9 J9 {, U* M! G6 f3 ~
- address currWinner = winners[i];
- & L9 F6 J; y1 n! Q
- // 获取每个地址应该得到的份额
- Player storage winner = players[currRound][currWinner];
- if (winner.isSet) {
- console.log("this winner has been set already, will be skipped!");# U: o8 l Q* t% O
- continue;
- }3 d" M" G; B( i: b
- 0 `0 X4 M5 x4 a" y
- winner.isSet = true;
- // 玩家购买的份额
- uint currCounts = winner.counts[_country];
- // (本期总奖励 / 总获奖人数)* 当前地址持有份额
- uint amt = (currAvalBalance / countryToPlayers[currRound][_country].length) * currCounts;
- // 玩家对应赢取的奖金. W/ Q. b) V( l1 C K6 x1 v/ Y
- winnerVaults[currWinner] += amt;
- distributeAmt += amt;7 s* v' j- E; |0 z( K
- // 放入待兑现的奖金池- Z2 e# h- X+ _2 w9 f
- lockedAmts += amt;' }% r# G7 m7 @- A9 x& D) h
- console.log("winner:", currWinner, "currCounts:", currCounts);
- console.log("reward amt curr:", amt, "total:", winnerVaults[currWinner]);
- } M" J+ k$ a5 T
- // 未分完的奖励即为平台收益
- uint giftAmt = currAvalBalance - distributeAmt;
- if (giftAmt > 0) {
- winnerVaults[admin] += giftAmt;* j8 w" L* ^( D9 Z
- }
- emit Finialize(currRound++, uint256(_country));
- }
- // 奖金兑现, w8 @' I; O0 {% p0 \; p
- function claimReward() external {$ E: a. ^& I D2 M# t
- uint256 rewards = winnerVaults[msg.sender];) v- {, o' {( z8 g0 w e% A2 v
- require(rewards > 0, "nothing to claim!");/ F; Y: ^/ n2 X9 \, F, v
- 2 O, G$ j+ ?0 F) n
- // 玩家领取完奖金置为 0
- winnerVaults[msg.sender] = 0;& C4 y- O- w8 m6 G
- // 从待兑现奖金池中移除该玩家份额
- lockedAmts -= rewards;3 E+ o/ G! `( I% u5 P+ V
- (bool succeed,) = msg.sender.call{value: rewards}("");
- require(succeed, "claim reward failed!");
- console.log("rewards:", rewards); b5 U' l" C. k9 l
- emit ClaimReward(msg.sender, rewards);
- }+ S# Y$ p1 Z( f* F+ w
- // 获取奖池金额. y" d2 Q1 J* F. i/ O2 g! ^7 |
- function getVaultBalance() public view returns(uint256 bal) {- i, r- X; P) f1 v$ ?6 t
- bal = address(this).balance;( l4 w, ? J4 z6 m
- }0 j ?4 F9 G/ C+ h
- // 获取当期下注当前球队的人数. ^% R( q0 {7 A; u. U/ H
- function getCountryPlayers(uint8 _round, Country _country) external view returns(uint256) {
- return countryToPlayers[_round][_country].length;/ O8 [8 t& w& e# k3 | z$ G
- }; U+ x' z( A+ W$ s9 T5 X! T
- // 获取当前玩家当期押注份额
- function getPlayerInfo(uint8 _round, address _player, Country _country) external view returns(uint256 _counts) {7 ^# X* J( O+ m7 d
- return players[_round][_player].counts[_country];
- }+ D+ }; e, a& L4 j: V
- }
( ^' S& \) h; }+ E
0 d' A$ O' y+ `0 h( t4 j