- 参赛球队一经设定不可改变,整个活动结束后无法投票;
- 全⺠均可参与,无权限控制;
- 每次投票为 1 ether,且只能选择一支球队;
- 每个人可以投注多次;
- 仅管理员公布最终结果,完成奖金分配,开奖后逻辑:
- winner 共享整个奖金池(一部分是自己的本金,一部分是利润);
- winner 需自行领取奖金(因为有手续费);
- 下一期自行开始; V7 H0 l1 i" e8 R
; c3 X& ^: a8 f3 j2 h# b) _
- a+ }. B0 l; _0 l: V9 F: s: r
- // SPDX-License-Identifier: GPL-3.0" Y; F5 }. p7 J# u! [
- pragma solidity >=0.7.0 <0.9.0;
- ( q% ?/ I( a; ~2 b4 \
- import "hardhat/console.sol";+ e% l8 `* ?1 z/ n; o
- contract WorldCup {
- // 1. 状态变量:管理员、所有玩家、获奖者地址、第几期、参赛球队7 k" i- Y1 S0 b) m3 h& M$ [
- // 2. 核心方法:下注、开奖、兑现
- // 3. 辅助方法:获取奖金池金额、管理员地址、当前期数、参与人数、所有玩家、参赛球队4 Y* Y' g, R) M" m* i
- // 管理员 ~4 z7 V: [& D4 t8 n* F
- address public admin;- n+ H" O( F6 n* f# v0 a6 ~% Q2 s1 ?5 ]
- // 第几期) i) g% D/ j9 m0 ?/ \
- uint8 public currRound;
- & h3 t6 |0 v0 |$ w7 E
- // 参赛球队
- string[] public countries = ["GERMANY", "FRANCH", "CHINA", "BRIZAL", "KOREA"];
- // 期数 => 玩家
- mapping(uint8 => mapping(address => Player)) players;
- // 期数 => 投注各球队的玩家
- mapping(uint8 => mapping(Country => address[])) public countryToPlayers;
- // 玩家对应赢取的奖金. S, s! F# ^$ s7 D8 s- b
- mapping(address => uint256) public winnerVaults;
- // 投注截止时间-使用不可变量,可通过构造函数传值,部署后无法改变( o; J7 | g) g& w
- uint256 public immutable deadline;
- // 所有玩家待兑现的奖金
- uint256 public lockedAmts;* V6 P3 L6 Z, ~8 _5 U9 O4 u
- enum Country {
- GERMANY,
- FRANCH,
- CHINA,( ?$ S: v* Y1 [# D% P
- BRAZIL,
- KOREA" @/ n" M; x: k5 _) D
- }
- % m0 M3 A( T$ o4 s% _
- event Play(uint8 _currRound, address _player, Country _country);
- event Finialize(uint8 _currRound, uint256 _country);
- event ClaimReward(address _claimer, uint256 _amt);
- // 验证管理员身份- w- S) Q! }# y. T1 S) e4 h
- modifier onlyAdmin {
- require(msg.sender == admin, "not authorized!");
- _;
- }
- F0 C- s, Q( { I3 S
- // 玩家投注信息: x6 R' i& ] f6 {0 K" ~
- struct Player {2 z7 d, H# q1 w1 M* \5 H
- // 是否开奖" [1 H% J! |+ j. b
- bool isSet;7 B% V5 U) O% I: K
- // 投注的球队份额
- mapping(Country => uint256) counts;/ o1 l7 G. B# C C: g
- }
- constructor(uint256 _deadline) {
- admin = msg.sender;$ J! R4 v8 i* q0 c" h
- require(_deadline > block.timestamp, "WorldCupLottery: invalid deadline!");
- deadline = _deadline;
- }
- // 下注过程+ z% @8 t7 B7 ]' \) N; }* M
- function play(Country _selected) payable external {
- // 参数校验
- require(msg.value == 1 gwei, "invalid funds provided!");5 z, _0 v, p/ ]/ C
- require(block.timestamp < deadline, "it's all over!");
- , [1 C7 T" w, G* @
- // 更新 countryToPlayers
- countryToPlayers[currRound][_selected].push(msg.sender);$ G2 U- r2 Z h6 j T
- // 更新 players(storage 是引用传值,修改会同步修改原变量)+ ~% c0 Q. ?! L5 {
- Player storage player = players[currRound][msg.sender];/ l, A9 Q! G6 ~' x9 P
- // player.isSet = false;, Z* T# n) d5 Y3 H9 w( j8 I
- player.counts[_selected] += 1;
- 6 ]8 R8 v2 Z( G" E
- emit Play(currRound, msg.sender, _selected);
- }9 s8 x5 d# n& Y0 g
- & } u) j' W; H( v: f9 C- e
- // 开奖过程
- function finialize(Country _country) onlyAdmin external {% g5 [: W0 ?1 k0 v5 S7 M% ~
- // 找到 winners
- address[] memory winners = countryToPlayers[currRound][_country];; |( g1 K' `# V4 X/ w
- // 分发给所有压中玩家的实际奖金
- uint256 distributeAmt;
- $ p+ r: r4 b5 f8 l0 i4 i* n: O( ~
- // 本期总奖励金额(奖池金额 - 所有玩家待兑现的奖金)
- uint currAvalBalance = getVaultBalance() - lockedAmts; W6 _2 J; T7 O
- console.log("currAvalBalance:", currAvalBalance, "winners count:", winners.length);
- for (uint i = 0; i < winners.length; i++) {6 A4 M! D& K! U6 z7 p) j' h2 m
- address currWinner = winners[i];
- 1 U& o, h- @8 N c9 }
- // 获取每个地址应该得到的份额( s+ x) y( {2 L; G. W
- Player storage winner = players[currRound][currWinner];; [' v0 B; l; j9 T
- if (winner.isSet) {
- console.log("this winner has been set already, will be skipped!");) q% o7 } u, w- U& o0 \( L5 H- n; I
- continue;
- }. U. D) f; x5 e* ?" B, s
- winner.isSet = true;
- // 玩家购买的份额+ f2 J" P- L2 [: \" t
- uint currCounts = winner.counts[_country]; K& h" ?; M) ~% @- C
- ) g1 H, ^0 i: q2 ] L/ B
- // (本期总奖励 / 总获奖人数)* 当前地址持有份额
- uint amt = (currAvalBalance / countryToPlayers[currRound][_country].length) * currCounts;
- // 玩家对应赢取的奖金2 g$ k( f4 |0 P4 k
- winnerVaults[currWinner] += amt;8 Q& \2 ~% D8 T* Z: C$ g) k1 }
- distributeAmt += amt;
- // 放入待兑现的奖金池# v) l" V& [6 j) F
- lockedAmts += amt;
- 5 F7 B! Z+ `" t! V
- console.log("winner:", currWinner, "currCounts:", currCounts);
- console.log("reward amt curr:", amt, "total:", winnerVaults[currWinner]);
- }- C% t9 ^7 p- x4 `- C
- // 未分完的奖励即为平台收益' J! P2 T+ k3 K3 C( K
- uint giftAmt = currAvalBalance - distributeAmt;
- if (giftAmt > 0) {
- winnerVaults[admin] += giftAmt;
- }
- emit Finialize(currRound++, uint256(_country));. t n" i) d7 e& j' I5 I
- }
- // 奖金兑现
- function claimReward() external {$ o0 {4 |' D1 w
- uint256 rewards = winnerVaults[msg.sender];
- require(rewards > 0, "nothing to claim!");
- / n9 D5 k" w k1 T8 _7 `6 B
- // 玩家领取完奖金置为 0
- winnerVaults[msg.sender] = 0;
- // 从待兑现奖金池中移除该玩家份额: ?8 d7 T: y0 M8 `9 |- M+ {
- lockedAmts -= rewards;
- (bool succeed,) = msg.sender.call{value: rewards}("");
- require(succeed, "claim reward failed!");
- console.log("rewards:", rewards);
- emit ClaimReward(msg.sender, rewards);
- }
- // 获取奖池金额) n- u& i" A) C/ H
- function getVaultBalance() public view returns(uint256 bal) {6 {. X8 s( w) z/ J
- bal = address(this).balance;# i) b! e+ {9 C1 I# } E {
- }* z; H) V. Z b. u9 N( u
- # X1 q: r- E6 w9 o7 k
- // 获取当期下注当前球队的人数$ A5 z! K1 Z1 D2 z& F$ s6 Y
- function getCountryPlayers(uint8 _round, Country _country) external view returns(uint256) {
- return countryToPlayers[_round][_country].length;
- }
- # z( A) h+ ?6 l) c/ m4 ~+ ^
- // 获取当前玩家当期押注份额
- function getPlayerInfo(uint8 _round, address _player, Country _country) external view returns(uint256 _counts) {' A* H2 x) t1 D' \5 D/ ]
- return players[_round][_player].counts[_country];
- }
- }
# _/ u( Q6 `- Q; l% v ^# K+ Q( K# I8 a
- i a: [' }# b/ o j
' e0 g' v5 [- f0 J( }( j