美文网首页
Solidity 智能合约实例分析——多方投票决选提案

Solidity 智能合约实例分析——多方投票决选提案

作者: FinJmy | 来源:发表于2018-12-12 17:28 被阅读0次

    1 场景

    在投票的应用场景中,我们定义如下几个关键要素:

    • 发起人,投票的发起人,具有管理权限和能力
    • 参与者,拥有投票权利的人
    • 旁观者,不参与投票的人,但是可以获知投票结果
    • 提案,对多个候选提案进行投票
    多方投票

    2 逻辑

    1. 所有参与者持有一个区块链账户
    2. 发起人创建投票合约,创建时指定多个提案
    3. 发起人为有权投票的账户进行赋权
    4. 投票人可以选择委托投票或自主投票
    5. 投票结束,得票多者胜出,任意人可查看结果

    3 完整代码

    源代码地址 https://solidity.readthedocs.io/en/v0.5.1/solidity-by-example.html

    pragma solidity >=0.4.22 <0.6.0;
    
    contract Ballot {
    
        struct Voter {
            uint weight;
            bool voted;
            address delegate;
            uint vote;
        }
    
        struct Proposal {
            bytes32 name;
            uint voteCount;
        }
    
        address public chairperson;
    
        mapping(address => Voter) public voters;
    
        Proposal[] public proposals;
    
        constructor(bytes32[] memory proposalNames) public {
            chairperson = msg.sender;
            voters[chairperson].weight = 1;
    
            for (uint i = 0; i < proposalNames.length; i++) {
                proposals.push(Proposal({
                    name: proposalNames[i],
                    voteCount: 0
                }));
            }
        }
    
        function giveRightToVote(address voter) public {
            require(
                msg.sender == chairperson,
                "Only chairperson can give right to vote."
            );
            require(
                !voters[voter].voted,
                "The voter already voted."
            );
            require(voters[voter].weight == 0);
            voters[voter].weight = 1;
        }
    
        function delegate(address to) public {
    
            Voter storage sender = voters[msg.sender];
            require(!sender.voted, "You already voted.");
            require(to != msg.sender, "Self-delegation is disallowed.");
    
            while (voters[to].delegate != address(0)) {
                to = voters[to].delegate;
    
                require(to != msg.sender, "Found loop in delegation.");
            }
    
            sender.voted = true;
            sender.delegate = to;
            Voter storage delegate_ = voters[to];
            if (delegate_.voted) {
                proposals[delegate_.vote].voteCount += sender.weight;
            } else {
                delegate_.weight += sender.weight;
            }
        }
    
        function vote(uint proposal) public {
            Voter storage sender = voters[msg.sender];
            require(sender.weight != 0, "Has no right to vote");
            require(!sender.voted, "Already voted.");
            sender.voted = true;
            sender.vote = proposal;
    
            proposals[proposal].voteCount += sender.weight;
        }
    
        function winningProposal() public view
                returns (uint winningProposal_)
        {
            uint winningVoteCount = 0;
            for (uint p = 0; p < proposals.length; p++) {
                if (proposals[p].voteCount > winningVoteCount) {
                    winningVoteCount = proposals[p].voteCount;
                    winningProposal_ = p;
                }
            }
        }
    
        function winnerName() public view
                returns (bytes32 winnerName_)
        {
            winnerName_ = proposals[winningProposal()].name;
        }
    }
    

    4 解析

    4.1 数据结构

    每个投票人,在此处用solidity的 struct 数据结构来表示。注意,此处 voted 只能是 true or false,因此,不管委托投票还是自主投票,只能一次性用掉所有的 weight,不可拆分。

    struct Voter {
        uint weight; // 256bit 的非负整数投票权重
        bool voted; // 用户是否已经投票
        address delegate; // 被委托人账户
        uint vote; // 投票提案编号
    }
    

    提案的数据结构相对简单,一个是提案名称,一个是得票数。

    struct Proposal {
        bytes32 name; // 提案名称
        uint voteCount; // 提案票数
    }
    

    下面三项全局变量(在 solidity 中,又称状态 state),都声明为 public,这样做的好处是,部署后,直接可以有类似于 Java 中的 getter 这样的查询函数供调用,不用再手动编写。

    address public chairperson;
    mapping(address => Voter) public voters;
    Proposal[] public proposals;
    

    基础类型状态查询函数没有参数,mapping 状态查询函数参数为 keyarray[] 状态查询函数参数为序号。如下图的合约列表所示,红框中的三个查询类函数(浅蓝背景),就是编译后自动生成的。

    functions.jpg

    4.2 构造函数

    此处 proposalNames 变量被声明为 memory ,表示该变量的声明周期只在函数调用期间,函数退出将被销毁。这样做的好处是节省空间,消耗的 gas 也更少。相对的,状态变量 state 是存储在 storage 中的。

    在提案列表初始化时,使用了 struct 数据的创建语句,注意相关语法。

    constructor(bytes32[] memory proposalNames) public {
        chairperson = msg.sender; // 指定合约部署账户为发起人
        voters[chairperson].weight = 1;
    
        for (uint i = 0; i < proposalNames.length; i++) {
            // 提案列表初始化
            proposals.push(Proposal({
                name: proposalNames[i],
                voteCount: 0
            }));
        }
    }
    

    4.3 赋权函数

    该函数的第一个 require 限制了只能由投票发起人调用。第二个 require 限制了赋权人尚未进行投票且权重为 0。

    function giveRightToVote(address voter) public {
        require(
            msg.sender == chairperson,
            "Only chairperson can give right to vote."
        );
        require(
            !voters[voter].voted,
            "The voter already voted."
        );
        require(voters[voter].weight == 0);
        // 默认每个账户的初始权重一样,都是1
        voters[voter].weight = 1;
    }
    

    注意,此处在赋权时,只设置了 Voter 结构体的 weight 变量。其余变量没设置,代表使用默认值。我们查询某赋权账户的信息如下。可以发现,bool 的默认值为 false; address 的默认值为 0x0; uint 的默认值为 0

    • uint256: weight 1
    • bool: voted false
    • address: delegate 0x0000000000000000000000000000000000000000
    • uint256: vote 0

    4.4 委托函数

    这里的 senderdelegate_ 变量使用了 storage 修饰,是因为他们都指向了全局的状态变量,后续对他们的修改,将引起状态变量的改变。前面两个 require,限制了没投票才能委托且不能委托自己。下面的 while 循环,是为了实现冒泡式的委托,即如果 A 委托 B 投票,B 又委托了 C 投票,那么最终,A 的投票权应该交接给 C。

    function delegate(address to) public {
        // 从状态变量取值,用 storage 修饰
        Voter storage sender = voters[msg.sender];
        require(!sender.voted, "You already voted.");
        require(to != msg.sender, "Self-delegation is disallowed.");
    
        // 找出最上游的被委托方(不一定是入参 `to`)
        while (voters[to].delegate != address(0)) {
            to = voters[to].delegate;
            // 受委托人不能又将自己的票委托给委托人,形成循环
            require(to != msg.sender, "Found loop in delegation.");
        }
    
        sender.voted = true;    // 委托等同于投票
        sender.delegate = to; 
        Voter storage delegate_ = voters[to];
    
        if (delegate_.voted) {
            // 如果被委托人已经投票,则直接行使委托人的投票权到相同提案
            proposals[delegate_.vote].voteCount += sender.weight;
        } else {
            delegate_.weight += sender.weight;
        }
    }
    

    4.5 投票函数

    投票函数很好理解,一次性行使完所有权重。

    function vote(uint proposal) public {
        Voter storage sender = voters[msg.sender];
        require(sender.weight != 0, "Has no right to vote");
        require(!sender.voted, "Already voted.");
        sender.voted = true;
        sender.vote = proposal;
    
        proposals[proposal].voteCount += sender.weight;
    }
    

    4.6 结果统计函数

    这两个函数都使用了 view 关键字修饰,表示他们是查询类函数,不会改变状态变量。.length 可以直接获取数组的长度。此处有一个小 bug 是,如果多个提案最终得票数相同,则认为循环中先被访问到的提案胜出。

    function winningProposal() public view
            returns (uint winningProposal_)
    {
        uint winningVoteCount = 0;
        for (uint p = 0; p < proposals.length; p++) {
            if (proposals[p].voteCount > winningVoteCount) {
                winningVoteCount = proposals[p].voteCount;
                winningProposal_ = p;
            }
        }
    }
    
    function winnerName() public view
            returns (bytes32 winnerName_)
    {
        winnerName_ = proposals[winningProposal()].name;
    }
    

    5 执行结果

    一些 remix 下的调试结果。

    ballot_res.jpg

    (完)

    相关文章

      网友评论

          本文标题:Solidity 智能合约实例分析——多方投票决选提案

          本文链接:https://www.haomeiwen.com/subject/biephqtx.html