LoginSignup
1
1

More than 5 years have passed since last update.

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

Posted at

ballot.solとは

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

Remix

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

Solidityのコンパイル

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

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つ渡す必要があります。

    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メソッドを実行します。

    /// 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になって投票できなくなります。
あとは投票権を与えるアドレスがオーナーのアドレスか否かもチェックしてますね。
これはつまり、投票権を与えることができるのはこのスマートコントラクトをデプロイしたオーナーのみ、ということです。
事前にオーナーから投票権を与えられたアドレスでないと、投票することができないわけです。

投票する

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

    /// 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メソッドで確認します。

    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

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;
            }
    }
}

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1