投票合约

本章讲解在 Solidity 中,什么是投票合约,以及投票合约的算法和实现。

视频Bilibili  |  Youtube
官网binschool.app
推特@BinSchool    DiscordBinDAO   微信:bkra50 

投票合约的目标是创建一个基于区块链的去中心化投票系统,以确保投票的公平性、透明性和安全性。

投票合约中被投票的对象,既可以是人员,又可以是提案,我们为了简单起见,统称为候选人。

合约部署者首先添加所有的候选人,然后发起一个投票。投票人在规定时间内,对候选人进行投票。投票的最终结果可以公开查询。

1. 投票合约的算法

投票合约具有以下主要功能和算法:

1.1 添加候选人

合约部署者可以通过 addCandidate 函数添加候选人。这些候选人将被存储在合约中,供投票人选择。

1.2 投票

任何人都可以使用 vote 函数投票给他们选择的候选人。投票人的地址将被记录,以确保他们不能多次投票。

1.3 计票和排名

合约允许投票人查询候选人的当前票数和排名。排名按照候选人获得的票数从高到低排列。

1.4 状态变量

合约包含多个状态变量来存储投票信息,例如:合约部署者、投票截止日期、投票人数、候选人信息等。

1.5 事件

投票事件被记录,以便后续审计和验证。这有助于确保投票的透明性。

1.6 排序算法

当查询候选人排名时,合约使用冒泡排序算法对候选人按照票数进行排序,以确定他们的排名。

2. 投票合约的实现

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Voting {
    // 代表候选人的结构体
    struct Candidate {
        uint id; // 候选人ID
        string name; // 候选人姓名
        uint voteCount; // 候选人获得的票数
    }

    // 存储候选人信息的数组
    Candidate[] public candidates;
    // 投票截止日期的时间戳
    uint public votingEndTime;

    // 用于标记是否投过票的映射
    mapping(address => bool) voters;
    // 记录总投票人数
    uint public totalVoters;
    
    // 合约部署者的地址
    address public owner;

    // 投票事件,记录投票人地址和候选人ID
    event VotedEvent(address 
      indexed voter, uint indexed candidateId);

    // 构造函数,初始化合约,设置投票截止日期和初始候选人
    constructor(uint _durationInMinutes) {
        // 记录合约部署者的地址
        owner = msg.sender;
        // 设置投票截止日期时间戳
        votingEndTime = block.timestamp + (_durationInMinutes * 1 minutes);
    }

    // 添加候选人,只能通过外部调用触发
    function addCandidate(string memory name) external {
        // 只有合约部署者可以添加候选人
        require(msg.sender == owner, 
             "Only the contract deployer can add candidates");
        
        // 将候选人信息添加到数组
        candidates.push(Candidate(candidates.length, name, 0));
    }

    // 投票函数,使用本地化的提示信息
    function vote(uint candidateId) external {
        // 检查候选人是否存在
        require(candidateId < candidates.length, 
            "Invalid candidate");
        // 检查是否已经投过票
        require(!voters[msg.sender], 
            "You have already voted");
        // 检查投票是否已结束
        require(block.timestamp < votingEndTime, 
            "Voting has ended");

        // 更新候选人的票数
        candidates[candidateId].voteCount++;
        // 标记投票人已投票
        voters[msg.sender] = true;
        // 增加总投票人数
        totalVoters++;
        // 触发投票事件,记录投票人地址和候选人ID
        emit VotedEvent(msg.sender, candidateId);
    }

    // 获取特定候选人的投票数
    function getCandidateVoteCount(uint candidateId) 
         public view returns (uint) {
        // 检查候选人是否存在
        require(candidateId < candidates.length, 
            "Invalid candidate");
        
        // 返回候选人的票数
        return candidates[candidateId].voteCount;
    }

    // 获取候选人总数
    function getCandidateCount() 
        public view returns (uint) {
        // 返回候选人数组的长度
        return candidates.length;
    }

    // 检查投票是否已经结束
    function isVotingClosed() public view returns (bool) {
        // 检查当前时间是否超过投票截止日期
        return block.timestamp >= votingEndTime;
    }

    // 获取当前的候选人排名
    function getCurrentCandidateRanking() 
        public view returns (Candidate[] memory) {
        // 检查投票是否已结束
        require(isVotingClosed(), "Voting is not closed yet");

        // 复制候选人数组以进行排序
        Candidate[] memory rankedCandidates = candidates;

        // 使用冒泡排序按票数排名
        for (uint i=0; i<rankedCandidates.length-1; i++) {
            for (uint j=0; j<rankedCandidates.length-i-1; j++) {
               if (rankedCandidates[j].voteCount<rankedCandidates[j+1].voteCount) {
                    (rankedCandidates[j], rankedCandidates[j+1]) = (rankedCandidates[j+1], rankedCandidates[j]);
                }
            }
        }

        // 返回排名后的候选人数组
        return rankedCandidates;
    }
}

3. 部署和测试

我们可以把上面编写的投票合约,复制到 Remix 里进行编译,然后部署到区块链上。

第一步:部署合约的时候需要填写投标有效期,以分钟为单位,比如:填写 1,就表示投标有效时间为 1 分钟。

合约部署后,我们会看到各个状态变量值都已经初始化,其中投标截止时间已经变为合约部署时间加上1分钟。

另外,我们还需要通过 addCandidate 添加候选人。

第二步:我们通过 vote 开始投标,填写想要投给的候选人的序号,进行投票。每个人只能投票一次,投过票后不允许再次投票。你可以切换不同的地址进行投票。

投票后,状态变量的值都按照规则进行了更新。

第三步:当超过截止投票截止时间后,也就是 isVotingClosed 返回 true 时,我们就可以查看投票结果。投票结果按照得票数从高到低排列。