- 参赛球队一经设定不可改变,整个活动结束后无法投票;
- 全⺠均可参与,无权限控制;
- 每次投票为 1 ether,且只能选择一支球队;
- 每个人可以投注多次;
- 仅管理员公布最终结果,完成奖金分配,开奖后逻辑:
- winner 共享整个奖金池(一部分是自己的本金,一部分是利润);
- winner 需自行领取奖金(因为有手续费);
- 下一期自行开始
- w8 ^/ ?8 S" E% l3 V8 j" O
- # c! [0 ^3 p5 q& g
- // SPDX-License-Identifier: GPL-3.0( D1 b( t B1 h* P0 t# N
- " }! G% c. \; Z; \) d8 A8 ?
- pragma solidity >=0.7.0 <0.9.0;
- import "hardhat/console.sol";- ]+ ^6 q) ]2 B b2 B) q
- contract WorldCup {1 `( |. O- z5 d# e
- // 1. 状态变量:管理员、所有玩家、获奖者地址、第几期、参赛球队
- // 2. 核心方法:下注、开奖、兑现
- // 3. 辅助方法:获取奖金池金额、管理员地址、当前期数、参与人数、所有玩家、参赛球队
- // 管理员$ [. g- ~2 H- w+ R% |
- address public admin;* ~! g. B% V, v; s+ b
- // 第几期
- uint8 public currRound;) T) w. v# ]/ Z9 H/ q/ \( w
- ' W$ P: j, n6 b) P) J
- // 参赛球队
- string[] public countries = ["GERMANY", "FRANCH", "CHINA", "BRIZAL", "KOREA"];) G; ?+ q" l6 }3 f
- // 期数 => 玩家) G6 M0 s) X* o* N! V; C, z4 \
- mapping(uint8 => mapping(address => Player)) players;% t' o2 w L* L& a" h+ c
- // 期数 => 投注各球队的玩家7 G# G ?/ Z6 O0 A0 S4 `4 ^& k
- mapping(uint8 => mapping(Country => address[])) public countryToPlayers;9 t* h4 t( _$ Q0 l0 T, m; \
- // 玩家对应赢取的奖金
- mapping(address => uint256) public winnerVaults;
- , Q& E$ A1 K7 {7 e5 _4 n" p2 {
- // 投注截止时间-使用不可变量,可通过构造函数传值,部署后无法改变' ~$ K: A9 j7 N
- uint256 public immutable deadline;
- // 所有玩家待兑现的奖金
- uint256 public lockedAmts;
- enum Country {
- GERMANY,
- FRANCH,
- CHINA,
- BRAZIL,' f9 X5 Q) M- Q
- KOREA. J2 F y0 G6 Y0 B$ T
- }
- g3 A* v0 c; m, U, H" C
- event Play(uint8 _currRound, address _player, Country _country);
- event Finialize(uint8 _currRound, uint256 _country);
- event ClaimReward(address _claimer, uint256 _amt);
- 8 O7 s2 {6 i$ s' r9 ?6 ]7 d
- // 验证管理员身份3 {7 C; d2 y& B# f* ?
- modifier onlyAdmin {
- require(msg.sender == admin, "not authorized!");" z7 E9 m0 p% j
- _;
- }+ r2 i$ U: C0 k* C- D. b+ n
- : {, |0 {7 ?; ] R. _
- // 玩家投注信息
- struct Player {6 B( t8 C8 O7 x9 ]- N* r
- // 是否开奖
- bool isSet;. ?# C' F+ k- d8 u
- // 投注的球队份额
- mapping(Country => uint256) counts;
- }
- constructor(uint256 _deadline) {
- admin = msg.sender;3 x0 M6 E* G$ {
- require(_deadline > block.timestamp, "WorldCupLottery: invalid deadline!");
- deadline = _deadline;9 X% y6 g# L0 K! p. [! {" v" Y
- }' ?+ Z$ G5 ~- F. w: Z$ l
- // 下注过程2 L) u& y1 s$ e
- function play(Country _selected) payable external {4 w: e7 e6 U) k2 U' \. ~8 Q8 _0 x/ a
- // 参数校验
- require(msg.value == 1 gwei, "invalid funds provided!");8 V/ k* ~/ `6 M) U
- 8 X% ~/ D9 G6 ~
- require(block.timestamp < deadline, "it's all over!");
- & E5 I% d' i3 @7 W) [
- // 更新 countryToPlayers
- countryToPlayers[currRound][_selected].push(msg.sender);
- // 更新 players(storage 是引用传值,修改会同步修改原变量)5 f- p4 D, j5 U1 d: r) C
- Player storage player = players[currRound][msg.sender];% u# e' J! u2 A9 D# B
- // player.isSet = false;
- player.counts[_selected] += 1;
- emit Play(currRound, msg.sender, _selected);
- }1 g- W, m$ b+ Z9 K4 F
- + M/ X/ _! q; K1 m
- // 开奖过程6 g, d8 T( H" {' i9 V9 l, g
- function finialize(Country _country) onlyAdmin external {/ G2 @% b' L) M, C! e
- // 找到 winners
- address[] memory winners = countryToPlayers[currRound][_country];/ `8 Z$ Z5 r/ ?( R; }* N
- // 分发给所有压中玩家的实际奖金5 p N$ A3 p2 o$ ?' W- N
- uint256 distributeAmt;
- / E& e. O, r6 ~! ]$ m
- // 本期总奖励金额(奖池金额 - 所有玩家待兑现的奖金)
- uint currAvalBalance = getVaultBalance() - lockedAmts;, @* L8 ]& @0 E# J$ X* ?) a
- console.log("currAvalBalance:", currAvalBalance, "winners count:", winners.length);
- for (uint i = 0; i < winners.length; i++) {
- address currWinner = winners[i];
- // 获取每个地址应该得到的份额
- Player storage winner = players[currRound][currWinner];
- if (winner.isSet) {
- console.log("this winner has been set already, will be skipped!");
- continue;
- }
- ! n L9 m' A/ Y) C# F$ [
- winner.isSet = true;
- // 玩家购买的份额, D8 |/ q( S7 ~: L- ]- O; v4 k
- uint currCounts = winner.counts[_country];
- // (本期总奖励 / 总获奖人数)* 当前地址持有份额% d9 _! d) c: _/ W) n
- uint amt = (currAvalBalance / countryToPlayers[currRound][_country].length) * currCounts; A R/ v6 }5 O" O
- // 玩家对应赢取的奖金
- winnerVaults[currWinner] += amt;; l& _7 p9 x6 ^
- distributeAmt += amt;
- // 放入待兑现的奖金池% i% K$ K) P- F9 \' ]
- lockedAmts += amt;& ~+ R2 ~/ b5 r& P1 x1 G
- I" U7 [4 P8 y# G( U8 w
- console.log("winner:", currWinner, "currCounts:", currCounts);
- console.log("reward amt curr:", amt, "total:", winnerVaults[currWinner]);! s$ ]% J7 e5 ^! p. y
- }
- // 未分完的奖励即为平台收益5 q8 i. V& ~( z* s, j! f S& e
- uint giftAmt = currAvalBalance - distributeAmt;
- if (giftAmt > 0) {
- winnerVaults[admin] += giftAmt;- Y' S1 |3 _ T) h, d: P
- }
- 1 G7 |! S- a0 U7 i0 C5 u
- emit Finialize(currRound++, uint256(_country));
- }) Y: `! ? ?$ j& q& [
- // 奖金兑现
- function claimReward() external {) C; z* A0 ^7 ~, n; q, L
- uint256 rewards = winnerVaults[msg.sender];) Q0 L- I0 u4 v+ H0 Y
- require(rewards > 0, "nothing to claim!"); l4 I2 c/ v$ \' o
- // 玩家领取完奖金置为 0, o2 [% X) \1 t+ r" p
- winnerVaults[msg.sender] = 0;
- // 从待兑现奖金池中移除该玩家份额
- lockedAmts -= rewards;; J4 x8 j" y) `# a
- (bool succeed,) = msg.sender.call{value: rewards}("");# ~- }6 }* s" C6 g$ u, T
- require(succeed, "claim reward failed!");
- ! E- R+ |6 n, v, e9 i
- console.log("rewards:", rewards);. ]% j N8 X+ b
- 6 M# E6 m+ ?. G: M+ z, z
- emit ClaimReward(msg.sender, rewards);/ r& i, H1 w1 U: Y
- }
- // 获取奖池金额
- function getVaultBalance() public view returns(uint256 bal) {
- bal = address(this).balance;' ?, \, P1 K3 \
- }3 Z! w2 Z0 J' E# _* Q" _" P) w- ?( o
- 7 ^. X, |2 K K' Z
- // 获取当期下注当前球队的人数
- function getCountryPlayers(uint8 _round, Country _country) external view returns(uint256) {8 s9 p8 k$ z( u+ I' T* Q% M% i
- return countryToPlayers[_round][_country].length;& s1 c+ f, s( }1 c: a
- }$ @/ g4 K1 ?- {. o
- // 获取当前玩家当期押注份额
- function getPlayerInfo(uint8 _round, address _player, Country _country) external view returns(uint256 _counts) {
- return players[_round][_player].counts[_country];) c# \5 o8 h T% y$ I( \/ d
- }
- }
' _* d6 O: j9 y" V1 a$ K7 ~
- D0 T H; |4 B0 k3 T: e2 h4 l
7 X9 g/ C2 b9 I/ e