3.3 Solidity by Example
3.3.1 Voting 投票
投票を実装したコントラクト。
電子投票の問題は、どのように投票権を正しい人に割り当て、どのように改ざんを防ぐか。
ここでは、どのように投票カウントが同時に自動的かつ完全に透明性を持って委任投票が行われるかを示す。
投票用紙ごとにコントラクトを作り、各オプションごとに短い名前を提供する。
議長として奉仕するコントラクトの作成者は、各アドレスに個別に投票権を与える。
投票権を与えられたアドレスを操作する人間は、彼ら自身に投票するか信頼できる人に投票するか。
投票が終わると、winningProposal()が一番投票の多い提案とともに返される。
voting.sol
pragma solidity >=0.4.22 <0.7.0;
voting with delegation 「代表者による投票」
ballot=「投票用紙」
contract Ballot {
ここでは、のちに変数として使われる新しい複雑な型を宣言する。
一人の投票者を表す
struct Voter {
uint weight; //weightは持ち投票権の数。普通は1。他の人に投票権を委任されると加算される。
bool voted; //trueならその人は既に関数voteを実行し終えている。
address delegate; //この構造体Voterと紐ずくアドレスが投票を委任する相手のアドレス
uint vote; //この構造体Voterと紐ずくアドレスが投票したroposalのインデックス(番号)
}
1つの提案のためのタイプ
struct Proposal {
bytes32 name; //提案の名前
uint voteCount; //投票された数
}
このコントラクトをデプロイした人のアドレスを格納するaddress型変数chairpersonを定義
address public chairperson;
各可能なアドレス(投票者のアドレス)にVoter構造体を保存する状態変数votersを宣言する。
mapping(address => Voter) public voters;
Proposalという動的配列の構造体
Proposal[] public proposals;
proposalNames(提案名)の1つを選ぶ新しい投票用紙を作成
コンストラクタに合計提案数をproposalNamesという変数で渡す
constructor(bytes32[] memory proposalNames) public {
//関数実行者のアドレス(このコントラクトをデプロイした人)をaddress型変数chairpersonに代入する
chairperson = msg.sender;
//変数weightを1にすることで1票をもつ状態にする
voters[chairperson].weight = 1;
//提供された各提案名に対して、新しい提案オブジェクトを作成し、
//それを構造体Proposalの動的配列の末尾に追加します。
for (uint i = 0; i < proposalNames.length; i++) {
//`Proposal({...}) `は一時的なものを作成します。
//`Proposal({...}) `は前で定義したProposal構造体
//プロポーザルオブジェクトと `proposals.push(...)`は、それを `proposals`の最後に追加します。
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
投票者にこの投票用紙に投票する権利を与える。
'chairperson'アドレスにのみコールされる。
この関数はchairpersonが投票権を渡す人にアドレスをaddress型変数voterに入れて、引数として渡せる
function giveRightToVote(address voter) public {
//`require`の最初の引数が` false`に評価されれば、
//実行は終了し、状態とEtherバランスへのすべての変更は元に戻されます。
//関数実行者がchairpersonかどうか
//これは以前のEVMバージョンではすべてのガスを消費していましたが、
//現在は変更されている。
//多くの場合、関数が正しく呼ばれているかどうかを調べるために `require`を使うのが得策です。
//2番目の引数として、何が悪かったのかについてのメッセージも提供できます。
require(
msg.sender == chairperson,
"Only chairperson can give rigth to vote."
);
/*
//ここでは、のちに変数として使われる新しい複雑な型を宣言する。
//一人の投票者を表す
struct Voter {
uint weight; //weightは代表者によって加算される
bool voted; //trueならその人は既に投票を終えている。
address delegate; //委任された人のアドレス
uint vote; //投票された提案のインデックス(番号)
}
*/
/*
各可能なアドレス(投票者のアドレス)にVoter構造体を保存する状態変数を宣言する。
mapping(address => Voter) public voters;
*/
require(
!voters[voter].voted,
//"voted"はbool型の変数、構造体Voterで定義されている変数の1つ
//"voter"は投票者のアドレス、このgiveRightToVote関数で引数として渡されている変数
//votersはvoterアドレスと紐ずいているVoter構造体を示す。
//votedがfalseだと、まだ投票していないから次に進み、trueだと以下のメッセージを返す
"The voter already voted."
);
//voterに入っているアドレスと紐ずいている構造体Voterを示すvotersの変数weightが0かどうか
//持ち投票数の確認
require(voters[voter].weight == 0);
//voterに入っているアドレスと紐ずいている構造体Voterを示すvotersの変数weightに1を代入する
//voterというアドレスに投票権が1与えられた。
voters[voter].weight = 1;
}
あなたの1票を他の投票者に委任する
委任する相手のアドレスをaddress型変数toに代入してこのdelegate関数に引数として渡す
function delegate (address to) public {
//ここでは、のちに変数として使われる新しい複雑な型を宣言する。
//一人の投票者を表す
struct Voter {
uint weight; //weightは代表者によって加算される
bool voted; //trueならその人は既に投票を終えている。
address delegate; //委任する相手のアドレス
uint vote; //投票された提案のインデックス(番号)
}
*/
/*
各可能なアドレス(投票者のアドレス)にVoter構造体を保存する状態変数を宣言する。
mapping(address => Voter) public voters;
*/
//このdelegate関数を実行したアドレス(msg.sender)mappingされた構造体Voterを示しているvotersを
//新しい構造体Voterを示す変数senderに代入する
//mapping(msg.sender => Voter) public sender;
//参照を割り当てる
//変数senderをstorageに格納する
Voter storage sender = voters[msg.sender];
//構造体Voterを示すsenderの変数votedがfalseなると次に進み、trueなら次のメッセージを返す。
require(!sender.voted, "You already voted.");
//委託する相手のアドレスがこの関数を実行するアドレスと一致した場合エラーを返す
require(to != msg.sender, "Self-delegation is disallowed.");
//toも投票権を委任している限り、
//この関数実行者の投票権の委任先はその同じアドレスに転送される。
//一般的に、このようなループは非常に危険です。
//実行時間が長すぎると、
//ブロックで使用可能な量よりも多くのガスが必要になる可能性があるためです。
//この場合、委任は実行されませんが、他の状況では、
//このようなループによって契約が完全に「動けなくなる」ことがあります。
//この関数を実行するアドレスが保持する投票権を委任する相手のアドレスも
//自分の投票権を委任する場合を想定して、
//そのアドレスの委任相手のアドレスがデフォルト値0出ない場合、
//以下のwhile分内の条件が実行される。
while (voters[to].delegate != address(0)) {
//委任する相手のアドレスの委任先アドレスをaddress型変数toに代入する。
to = voters[to].delegate ;
//無限ループの回避
require(to != msg.sender, "Found loop in delegation.");
}
//senderは参照のため、これは`有権者[msg.sender] .voted`を修正します
//これでこの関数実行者のアドレスは投票したことになる
sender.voted = true;
//senderのaddress型変数delegateに委託する相手のアドレスを代入する
sender.delegate = to;
//構造体Voterを示すmapping "delegate_"に
//address型変数"to"とmappingされた構造体Votersを示すvoters代入する
//mapping(to => Voter) public delegate_;
Voter storage delegate_ = voters[to];
//委託する相手が既に投票済みの場合は、
//委任する投票権を直接相手の投票先のproposalsの変数voteCountに加える
if (delegate_.voted) {
proposals[delegate_.vote].voteCount += sender.weight;
}else {
//委託する相手が投票していない場合は、その人の持ち投票権weightに委任する投票権を加える。
delegate_.weight += sender.weight;
}
}
あなたに委任された投票権を含むあなたの投票を提案proposals[proposal].name
に与える。
投票先のindexを変数proposalに入れて引数として渡す。
function vote(uint proposal) public {
//参照を割り当てる
//vote関数を実行する人のアドレスとmappingされた構造体Voterを示すvotersを
//senderに代入する
//mapping(msg.sender => Voter) public sender;
Voter storage sender = voters[msg.sender];
//関数を実行する人のもち投票権が1以上かを確かめる。
require(sender.weight != 0, "Has no right to vote");
//bool型変数votedがfalseなら次に進む、trueなら左のメッセージが返される
require(!sender.voted, "Alredy voted.");
//この関数実行者が投票を実行したことにする
sender.voted = true;
//この関数実行時に実行者がこの関数に渡した引数proposalを
//実行者のアドレスの投票先を表す変数voteに代入する
sender.vote = proposal;
/*
//1つの提案のタイプ
struct Proposal {
bytes32 name; //提案の名前
uint voteCount; //投票された数
}
//Proposalという動的配列の構造体
Proposal[] public proposals;
*/
//この関数実行時に実行者がこの関数に渡した引数proposal
//がchairpersonがBallot関数のデプロイ時に指定した配列proposalNamesの範囲外である場合、
//これは自動的にスローされ、すべての変更を元に戻します。
proposals[proposal].voteCount += sender.weight;
}
以前の投票をすべて考慮に入れて、当選する提案を計算します。
function winningProposal() public view returns (uint winningProposal_){
uint winningVoteCount = 0;
//各proposalsをindexごとに見ていく
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
//WinningProposal()関数を呼び出して、
//proposals配列に含まれる勝者のインデックスを取得してから、
//当選した名前を返します。
function winnerName() public view returns (bytes32 winnerName_){
winnerName_ = proposals[winningProposal()].name;
}
}