• 世界杯竞猜项目Dapp-第一章(合约开发)


    前言

    最近卡塔尔世界杯如火如荼,让我们一起来尝试利用 solidity 语言做一个世界杯竞猜的 Dapp 实战项目,本次实战学习主要参考:https://github.com/dukedaily/solidity-expert,我会针对原始项目做更详尽的注解,持续更新中…

    业务需求

    • 参赛球队一经设定不可改变,整个活动结束后无法投票;
    • 全⺠均可参与,无权限控制;
    • 每次投票为 1 ether,且只能选择一支球队;
    • 每个人可以投注多次;
    • 仅管理员公布最终结果,完成奖金分配,开奖后逻辑:
    • winner 共享整个奖金池(一部分是自己的本金,一部分是利润);
    • winner 需自行领取奖金(因为有手续费);
    • 下一期自行开始

    基础合约实现

    // SPDX-License-Identifier: GPL-3.0
    
    pragma solidity >=0.7.0 <0.9.0;
    
    import "hardhat/console.sol";
    
    contract WorldCup {
        // 1. 状态变量:管理员、所有玩家、获奖者地址、第几期、参赛球队
        // 2. 核心方法:下注、开奖、兑现
        // 3. 辅助方法:获取奖金池金额、管理员地址、当前期数、参与人数、所有玩家、参赛球队
    
        // 管理员
        address public admin;
        // 第几期
        uint8 public currRound;
    
        // 参赛球队
        string[] public countries = ["GERMANY", "FRANCH", "CHINA", "BRIZAL", "KOREA"];
        // 期数 => 玩家
        mapping(uint8 => mapping(address => Player)) players;
        // 期数 => 投注各球队的玩家
        mapping(uint8 => mapping(Country => address[])) public countryToPlayers;
        // 玩家对应赢取的奖金
        mapping(address => uint256) public winnerVaults;
    
        // 投注截止时间-使用不可变量,可通过构造函数传值,部署后无法改变
        uint256 public immutable deadline;
        // 所有玩家待兑现的奖金
        uint256 public lockedAmts;
    
        enum Country {
            GERMANY,
            FRANCH,
            CHINA,
            BRAZIL,
            KOREA
        }
    
        event Play(uint8 _currRound, address _player, Country _country);
        event Finialize(uint8 _currRound, uint256 _country);
        event ClaimReward(address _claimer, uint256 _amt);
    
        // 验证管理员身份
        modifier onlyAdmin {
            require(msg.sender == admin, "not authorized!");
            _;
        }
    
        // 玩家投注信息
        struct Player {
            // 是否开奖
            bool isSet;
            // 投注的球队份额
            mapping(Country => uint256) counts;
        }
    
        constructor(uint256 _deadline) {
            admin = msg.sender;
            require(_deadline > block.timestamp, "WorldCupLottery: invalid deadline!");
            deadline = _deadline;
        }
    
        // 下注过程
        function play(Country _selected) payable external {
            // 参数校验
            require(msg.value == 1 gwei, "invalid funds provided!");
    
            require(block.timestamp < deadline, "it's all over!");
    
            // 更新 countryToPlayers
            countryToPlayers[currRound][_selected].push(msg.sender);
            // 更新 players(storage 是引用传值,修改会同步修改原变量)
            Player storage player = players[currRound][msg.sender];
            // player.isSet = false;
            player.counts[_selected] += 1;
    
            emit Play(currRound, msg.sender, _selected);
        }
    
        // 开奖过程
        function finialize(Country _country) onlyAdmin external {
            // 找到 winners
            address[] memory winners = countryToPlayers[currRound][_country];
            // 分发给所有压中玩家的实际奖金
            uint256 distributeAmt;
    
            // 本期总奖励金额(奖池金额 - 所有玩家待兑现的奖金)
            uint currAvalBalance = getVaultBalance() - lockedAmts;
            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;
                }
    
                winner.isSet = true;
                // 玩家购买的份额
                uint currCounts = winner.counts[_country];
    
                // (本期总奖励 / 总获奖人数)* 当前地址持有份额
                uint amt = (currAvalBalance / countryToPlayers[currRound][_country].length) * currCounts;
                // 玩家对应赢取的奖金
                winnerVaults[currWinner] += amt;
                distributeAmt += amt;
                // 放入待兑现的奖金池
                lockedAmts += amt;
    
                console.log("winner:", currWinner, "currCounts:", currCounts);
                console.log("reward amt curr:", amt, "total:", winnerVaults[currWinner]);
            }
    
            // 未分完的奖励即为平台收益
            uint giftAmt = currAvalBalance - distributeAmt;
            if (giftAmt > 0) {
                winnerVaults[admin] += giftAmt;
            }
    
            emit Finialize(currRound++, uint256(_country));
        }
    
        // 奖金兑现
        function claimReward() external {
            uint256 rewards = winnerVaults[msg.sender];
            require(rewards > 0, "nothing to claim!");
    
            // 玩家领取完奖金置为 0
            winnerVaults[msg.sender] = 0;
            // 从待兑现奖金池中移除该玩家份额
            lockedAmts -= rewards;
            (bool succeed,) = msg.sender.call{value: rewards}("");
            require(succeed, "claim reward failed!");
    
            console.log("rewards:", rewards);
    
            emit ClaimReward(msg.sender, rewards);
        }
    
        // 获取奖池金额
        function getVaultBalance() public view returns(uint256 bal) {
            bal = address(this).balance;
        }
    
        // 获取当期下注当前球队的人数
        function getCountryPlayers(uint8 _round, Country _country) external view returns(uint256) {
            return countryToPlayers[_round][_country].length;
        }
    
        // 获取当前玩家当期押注份额
        function getPlayerInfo(uint8 _round, address _player, Country _country) external view returns(uint256 _counts) {
            return players[_round][_player].counts[_country];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
  • 相关阅读:
    Vue选项: Methods方法
    &2_机器学习分类
    单机点:Redis可以连接,但是无法写入数据
    A/B 测试:Python 分步指南
    第一个简单爬虫:获取页面
    计算机组成原理--数据表示
    VS2019编译一个带qrc项目时出现的问题
    HTML+VUE+element-ui通过点击不同按钮展现不同页面
    Java之HashMap中getOrDefault()方法具有什么功能呢?
    Java多线程篇(4)——wait/notify和park/unPark
  • 原文地址:https://blog.csdn.net/qq_38685503/article/details/128176653