# SolidityCaseBallotAnalysis

By [weijunzi](https://paragraph.com/@weijunzi) · 2022-06-06

---

official case `Ballot`
----------------------

过去几个月,每次说学习`solidity`都无动于衷,这次终于行动起来了,希望一直坚持下去.

### 0x1

来自官方案例:

    pragma solidity ^0.4.22;
    
    /// @title 委托投票
    contract Ballot {
        // 这里声明了一个新的复合类型用于稍后的变量
        // 它用来表示一个选民
        struct Voter {
            uint weight; // 计票的权重
            bool voted;  // 若为真，代表该人已投票
            address delegate; // 被委托人
            uint vote;   // 投票提案的索引
        }
    
        // 提案的类型
        struct Proposal {
            bytes32 name;   // 简称（最长32个字节）
            uint voteCount; // 得票数
        }
    
        address public chairperson;
    
        // 这声明了一个状态变量，为每个可能的地址存储一个 `Voter`。
        mapping(address => Voter) public voters;
    
        // 一个 `Proposal` 结构类型的动态数组
        Proposal[] public proposals;
    
        /// 为 `proposalNames` 中的每个提案，创建一个新的（投票）表决
        constructor(bytes32[] proposalNames) public {
            chairperson = msg.sender;
            voters[chairperson].weight = 1;
            //对于提供的每个提案名称，
            //创建一个新的 Proposal 对象并把它添加到数组的末尾。
            for (uint i = 0; i < proposalNames.length; i++) {
                // `Proposal({...})` 创建一个临时 Proposal 对象，
                // `proposals.push(...)` 将其添加到 `proposals` 的末尾
                proposals.push(Proposal({
                    name: proposalNames[i],
                    voteCount: 0
                }));
            }
        }
    
        // 授权 `voter` 对这个（投票）表决进行投票
        // 只有 `chairperson` 可以调用该函数。
        function giveRightToVote(address voter) public {
            // 若 `require` 的第一个参数的计算结果为 `false`，
            // 则终止执行，撤销所有对状态和以太币余额的改动。
            // 在旧版的 EVM 中这曾经会消耗所有 gas，但现在不会了。
            // 使用 require 来检查函数是否被正确地调用，是一个好习惯。
            // 你也可以在 require 的第二个参数中提供一个对错误情况的解释。
            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;
        }
    
        /// 把你的投票委托到投票者 `to`。
        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.");
    
            // 委托是可以传递的，只要被委托者 `to` 也设置了委托。
            // 一般来说，这种循环委托是危险的。因为，如果传递的链条太长，
            // 则可能需消耗的gas要多于区块中剩余的（大于区块设置的gasLimit），
            // 这种情况下，委托不会被执行。
            // 而在另一些情况下，如果形成闭环，则会让合约完全卡住。
            while (voters[to].delegate != address(0)) {
                to = voters[to].delegate;
    
                // 不允许闭环委托
                require(to != msg.sender, "Found loop in delegation.");
            }
    
            // `sender` 是一个引用, 相当于对 `voters[msg.sender].voted` 进行修改
            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;
            }
        }
    
        /// 把你的票(包括委托给你的票)，
        /// 投给提案 `proposals[proposal].name`.
        function vote(uint proposal) public {
            Voter storage sender = voters[msg.sender];
            require(!sender.voted, "Already voted.");
            sender.voted = true;
            sender.vote = proposal;
    
            // 如果 `proposal` 超过了数组的范围，则会自动抛出异常，并恢复所有的改动
            proposals[proposal].voteCount += sender.weight;
        }
    
        /// @dev 结合之前所有的投票，计算出最终胜出的提案
        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;
                }
            }
        }
    
        // 调用 winningProposal() 函数以获取提案数组中获胜者的索引，并以此返回获胜者的名称
        function winnerName() public view
                returns (bytes32 winnerName_)
        {
            winnerName_ = proposals[winningProposal()].name;
        }
    }
    

跟`nodejs`有那么一点相似语法上有一点相似.

**官方推荐** [**Remix IDE**](https://remix.ethereum.org/) web端就能使用 不用下载

### 0x2

**remix 部署合约**

![](https://storage.googleapis.com/papyrus_images/b71c45398df3a36347dc6c68670ab47eaa6ccf51458f0c6dd476cdce6dc5a75e.png)

先`compile` 然后在部署里可以看到刚刚编译好的合约

![](https://storage.googleapis.com/papyrus_images/a02610807db2543bf10f328776a4058aefe5b88d7009a83e21fca1a3ab6bb2a0.png)

这里部署需要传入`bytes32[]`类型的数组,其实就是提案数组

     constructor(bytes32[] 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
                }));
    }
        
    

**合约部署者 就是proposal的chairperson**

remix 这里是不会自动补全的

传入的`bytes32`的`length`应该为`66`\=`0x`+`64hex`

    const web3 = require('web3');
    // change string to hex
    const hex1 = web3.utils.toHex('proposal 1');
    const hex2 = web3.utils.toHex('proposal 2');
    // fill 0 in the end
    const filledHex1 = web3.utils.padRight(hex1, 64);
    const filledHex2 = web3.utils.padRight(hex2, 64);
    console.log("filledHex1",filledHex1);
    console.log("filledHex2",filledHex2);
    //filledHex1 0x70726f706f73616c203100000000000000000000000000000000000000000000
    //filledHex2 0x70726f706f73616c203200000000000000000000000000000000000000000000
    // change hex to string
    const str = web3.utils.hexToAscii(hex2);
    console.log("str",str);
    

`deploy`的入参就传入`["0x70726f706f73616c203100000000000000000000000000000000000000000000","0x70726f706f73616c203200000000000000000000000000000000000000000000"]`

如果`bytes32`不补全的化会出现如下错误

`creation of Ballot errored: Error encoding arguments: Error: incorrect data length (argument=null, value="0x1231", code=INVALID_ARGUMENT, version=abi/5.5.0)`

**部署完成**

![](https://storage.googleapis.com/papyrus_images/e8bdfbd8b40ee42eea5bdbf86499c584808b0ededcf292515d4fc909994c3347.png)

可以看到当前的`account`是有`gas`消耗的.

### 0x3

对应的方法和变量可以在下面查看

![](https://storage.googleapis.com/papyrus_images/de4965a7cdb40622cb626801903b8d4aef14ce547d832aea0f2b7e3f6c247e33.png)

---

*Originally published on [weijunzi](https://paragraph.com/@weijunzi/soliditycaseballotanalysis)*
