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