Remix IDEのBallot.solを読んでみる

# ballot.solとは

Solidityの統合開発環境であるRemixに、デフォルトで実装してあるスマートコントラクトです。
Remixを開くと「ballot.sol」ファイルがあり、そこにSolidityコードが書かれてます。

[Remix](https://remix.ethereum.org)

これはRemixが用意したサンプルコードなので特に利用することはないですが、学習のためにコードリーディングしてみました。
コントラクト名がBallot(投票用紙)なので、およそ投票に関するスマートコントラクトであることが予想できます。
なおこの記事では、Solidityの細かい構文などは理解していることを前提に話を進めます。

# Solidityのコンパイル

まず最初に、ballot.solファイルを開いた状態でSolidityをコンパイルします。
Remixの画面右上「Compile」タブをクリック。

```solidity
pragma solidity >=0.4.22 <0.6.0;
```

とあるので、コンパイラは0.4.22以上、0.6.0より小さいバージョンを選択する必要があります。
今回は0.4.24でコンパイルしたいので、「Select new compiler version」プルダウンから「0.4.24-commit-xxxxxx」を選択します。
その後「Start to compile (Ctrl-S)」をクリックしてコンパイルします。

画面下に緑枠で「Ballot」と表示されれば成功です。

# デプロイ

コンパイルしたら次はブロックチェーンにデプロイします。
スマートコントラクトのコンストラクタはデプロイすると1度だけ実行されます。
「コントラクト」「コンストラクタ」、うーーん、ややこしいですね。
コンストラクタを見ると、数値型の引数を1つ渡す必要があります。

```solidity
    constructor(uint8 _numProposals) public {
        chairperson = msg.sender;
        voters[chairperson].weight = 1;
        proposals.length = _numProposals;
    }
```

ここでchairpersonに自分のアドレス、つまりデプロイした人のアドレスをセットして、さらに自分自身にも投票権を与えてます。
weightが投票数で「1」をセットしてます。
これは1人1票投票できることを意味します。

そしてproposals.lengthに引数の値をセットしてます。これは投票対象の数になります。
つまり引数に「3」を与えると、投票対象が3つある状態になります。
ここでは投票対象はそれぞれ配列の添字で表現してます。
つまり以下の通り、投票対象が3つある状態になります。

* proposals[0]
* proposals[1]
* proposals[2]

投票する人は、この3つのうちどれか1つに投票するわけです。

# 投票権を付与

無事にデプロイが完了したら次にgiveRightToVoteメソッドを実行します。

```solidity
    /// Give $(toVoter) the right to vote on this ballot.
    /// May only be called by $(chairperson).
    function giveRightToVote(address toVoter) public {
        if (msg.sender != chairperson || voters[toVoter].voted) return;
        voters[toVoter].weight = 1;
    }
```

ここは引数で指定したアドレスに対して、1票の投票権を付与してます。
最初のバリデーションのvotedは既に投票済か否かのフラグで、1度でも投票するとvotedがtrueになって投票できなくなります。
あとは投票権を与えるアドレスがオーナーのアドレスか否かもチェックしてますね。
これはつまり、投票権を与えることができるのはこのスマートコントラクトをデプロイしたオーナーのみ、ということです。
事前にオーナーから投票権を与えられたアドレスでないと、投票することができないわけです。

# 投票する

そしていよいよ投票です。

```solidity
    /// Give a single vote to proposal $(toProposal).
    function vote(uint8 toProposal) public {
        Voter storage sender = voters[msg.sender];
        if (sender.voted || toProposal >= proposals.length) return;
        sender.voted = true;
        sender.vote = toProposal;
        proposals[toProposal].voteCount += sender.weight;
    }
```

メソッド名から推測できる通り、ここが投票機能です。
引数には数値型の値を指定します。この値が投票対象になります。
つまり引数に「1」を指定すると、proposals配列の1番目に投票したことになります。
変数にstorageを付けているため、このメソッドを実行したアドレスが何番に投票したのか、しっかりブロックチェーンに記録されるわけです。

処理自体はvotedフラグをtrue(投票済)にして、投票数をweightだけカウントアップしてます。
1アドレス1票が基本なので、投票される度に1つカウントアップされる仕組みです。
さて、これで一通りの投票作業が完了しました。

# 投票結果の確認

最後は投票結果の確認ですね。
結果はwinningProposalメソッドで確認します。

```solidity
    function winningProposal() public view returns (uint8 _winningProposal) {
        uint256 winningVoteCount = 0;
        for (uint8 prop = 0; prop < proposals.length; prop++)
            if (proposals[prop].proposals > winningVoteCount) {
                winningVoteCount = proposals[prop].voteCount;
                _winningProposal = prop;
            }
    }
```

ループでどのproposalsのvoteCountが1番大きいかチェックしてます。
そして1番投票された番号をreturnsで返却してます。

ここで1つ個人的に紛らわしいと思ったのが、proposalsは配列で添字が「0」からスタートします。
なので仮にproposalsの1番目の要素が1番投票された場合、returnで戻る値が「0」なんですよね。
ま、サンプルコードだし、細かいことは気にしないでおきます。

ブロックチェーンを利用した投票システムはずっと前から期待されているユースケースの1つですが、まだまだ検討する余地が多いです。
今後さらに実装が進化して、実世界で利用される日が来ると良いですね。

### サンプルコード

最後にサンプルコードを全て記載しておきます。
Remixからコピペしたものですが、上記の説明を参考にコードリーディングするとより理解できるかと思います。

ballot.sol

```solidity
pragma solidity >=0.4.22 <0.6.0;
contract Ballot {

    struct Voter {
        uint weight;
        bool voted;
        uint8 vote;
        address delegate;
    }

    struct Proposal {
        uint voteCount;
    }

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

    /// Create a new ballot with $(_numProposals) different proposals.
    constructor(uint8 _numProposals) public {
        chairperson = msg.sender;
        voters[chairperson].weight = 1;
        proposals.length = _numProposals;
    }

    /// Give $(toVoter) the right to vote on this ballot.
    /// May only be called by $(chairperson).
    function giveRightToVote(address toVoter) public {
        if (msg.sender != chairperson || voters[toVoter].voted) return;
        voters[toVoter].weight = 1;
    }

    /// Delegate your vote to the voter $(to).
    function delegate(address to) public {
        Voter storage sender = voters[msg.sender]; // assigns reference
        if (sender.voted) return;
        while (voters[to].delegate != address(0) && voters[to].delegate != msg.sender)
            to = voters[to].delegate;
        if (to == msg.sender) return;
        sender.voted = true;
        sender.delegate = to;
        Voter storage delegateTo = voters[to];
        if (delegateTo.voted)
            proposals[delegateTo.vote].voteCount += sender.weight;
        else
            delegateTo.weight += sender.weight;
    }

    /// Give a single vote to proposal $(toProposal).
    function vote(uint8 toProposal) public {
        Voter storage sender = voters[msg.sender];
        if (sender.voted || toProposal >= proposals.length) return;
        sender.voted = true;
        sender.vote = toProposal;
        proposals[toProposal].voteCount += sender.weight;
    }

    function winningProposal() public view returns (uint8 _winningProposal) {
        uint256 winningVoteCount = 0;
        for (uint8 prop = 0; prop < proposals.length; prop++)
            if (proposals[prop].voteCount > winningVoteCount) {
                winningVoteCount = proposals[prop].voteCount;
                _winningProposal = prop;
            }
    }
}

```