diff --git a/src/Ballot.sol b/src/Ballot.sol index a451841f6f40dddcf13d4b056f46a22d45170103..07690f21a9a2241201e20efc71f479c97efc8c70 100644 --- a/src/Ballot.sol +++ b/src/Ballot.sol @@ -1,70 +1,66 @@ // Basado en https://soliditycookbook.com/voting // vim:syntax:filetype=javascript:ai:sm // vim:expandtab:backspace=indent,eol,start:softtabstop=4 + pragma solidity ^0.4.25; -/// @title Voting with delegation. -contract Ballot { - // This declares a new complex type which will - // be used for variables later. - // It will represent a single voter. - struct Voter { +/// @title A single ballot contract which allows certain +/// @title accounts to vote (one single vote). +/// @author soliditycookbook.com & Robert Martin-Legene +/// @notice A Chairman will deploy the contract, setting up initial +/// @notice conditions. Before the voting period starts the Chairman +/// @notice must also tell the contract who is allowed to vote. +contract Ballot +{ + /// @dev This is a struct for a single voter. + /// @dev Voter.vote is the index of the voted proposal or -1 if + /// @dev the account has not voted yet. + struct Voter + { address voter; - int vote; // index of the voted proposal or -1 if not voted - } - // This is a type for a single proposal. - struct Proposal { - bytes32 name; // short name (up to 32 bytes) - uint voteCount; // number of accumulated votes - } - struct Votingrules { - string title; - address chairman; - uint voteStarts; - uint voteBefore; - // A NOTE ON PERCENTAGES - // At present floats do not exist. Since we merely use - // our floats to later present to the outside world, - // and then let them handle the math, this scenario will - // be used: - // - // A percentage will be multiplied with one million. Thus: - // 100% is represented as 100 million. I.e. "100000000". - // 2/3 is represented as "67777778" (see note later on - // two-thirds - - // Which percentage of the registered voters must - // vote in order for the vote to be considered valid. - // e.g. 0 - uint percentOfRegisteredVotersReqToBeValid; - // Which percentage of the cast votes are required - // for the motion/title to pass? - // MAJORITY VOTE: - // specify 50000001 - // TWO-THIRDS: - // specify 67777777 - uint percentOfVotesCastToWin; - // Counting registered voters who do not vote as blank - // votes, has the effect that it is more difficult - // to acquire the desired votes. - bool countNonvotesAsBlanks; + uint[] votedProposals; + uint votesLeft; } + string public ballotTitle; + address public ballotChairman; + uint public ballotVoteStarts; + uint public ballotVoteBefore; + uint public ballotPercentOfRegisteredVotersReqToBeValid; + uint public ballotPercentOfVotesCastToWin; + /// @dev Counting registered voters who do not vote as blank + /// @dev votes, has the effect that it is more difficult + /// @dev to acquire the desired votes. + bool public ballotCountNonvotesAsBlanks; + uint public ballotMaxVotesPerVoter; + uint public ballotMaxVotesPerProposal; + /// @dev The votermap points to the index(number) that the struct of the + /// @dev account can be found. + mapping( address => uint ) + public voterMap; + Voter[] public voterList; + /// @dev Short name (up to 32 bytes) + bytes32[] public proposalList; + uint[] private emptyuintlist; - Votingrules public rules; - mapping( address => uint ) public voterMap; - Voter[] public voterList; - uint public numvoters; - Proposal[] public proposalList; - uint public numproposals; - - /// Create a new ballot to choose one of `proposalNames`. + /// @notice When the SC is being deployed, you must define initial conditions. When specifying percentages they are to have been multiplied with 100 million (5% is written as 5 million, since 5% is actially 0.05). + /// @param title Name of the things being voted for. + /// @param voteStarts Time in seconds (since Unix Epoch) when the voting starts (note that voters must be defined before voting starts). + /// @param voteBefore Time in seconds (since Unix Epoch) at/after which it is too late to vote. After this point, the Ballot is effectively readonly. + /// @param percentOfRegisteredVotersReqToBeValid How many of the registered voters must vote before the result of the Ballot can be considered valid. E.g. if 50% is set and only 40% votes, then the result of the Ballot can not be used. + /// @param percentOfVotesCastToWin How many of the votes that are cast are required to win. This is often 50.000001% ("more than half"), 67.777778% ("two thirds") or 0% (proposal with most votes - e.g. voting for most popular fruit). + /// @param countNonvotesAsBlanks Whether or not to count voters whom did not vote, as having voted blank. + /// @param maxVotesPerVoter How many votes can a voter cast. Often this is 1 (one). + /// @param maxVotesPerProposal How many votes one voter can cast on each proposal. I.e. can (s)he vote use all his votes on one proposal? + /// @param proposalNames 32 char "strings" listing all the options available to vote for. constructor( - string ballotTitle, + string title, uint voteStarts, uint voteBefore, uint percentOfRegisteredVotersReqToBeValid, uint percentOfVotesCastToWin, bool countNonvotesAsBlanks, + uint maxVotesPerVoter, + uint maxVotesPerProposal, bytes32[] proposalNames ) public @@ -72,61 +68,63 @@ contract Ballot { require( voteBefore > now ); require( percentOfRegisteredVotersReqToBeValid <= 100000000 ); require( percentOfVotesCastToWin <= 100000000 ); - // chairman can not automatically vote. Chairman must - // giveRightToVote to himself if he wants to vote. - rules.chairman = msg.sender; - rules.title = ballotTitle; - rules.voteStarts = voteStarts; - rules.voteBefore = voteBefore; - rules.percentOfRegisteredVotersReqToBeValid = percentOfRegisteredVotersReqToBeValid; - rules.percentOfVotesCastToWin = percentOfVotesCastToWin; - rules.countNonvotesAsBlanks = countNonvotesAsBlanks; - - // For each of the provided proposal names, - // create a new proposal object and add it - // to the end of the array. - numproposals = proposalNames.length; + // chairman can not automatically vote. + // Chairman must giveRightToVote to himself if + // he wants to vote. + ballotChairman = msg.sender; + ballotTitle = title; + ballotVoteStarts = voteStarts; + ballotVoteBefore = voteBefore; + ballotPercentOfRegisteredVotersReqToBeValid = percentOfRegisteredVotersReqToBeValid; + ballotPercentOfVotesCastToWin = percentOfVotesCastToWin; + ballotCountNonvotesAsBlanks = countNonvotesAsBlanks; + ballotMaxVotesPerVoter = maxVotesPerVoter; + ballotMaxVotesPerProposal = maxVotesPerProposal; + /// @dev For each of the provided proposal names, + /// @dev add it to the end of the proposalList + uint incoming = proposalNames.length; uint i = 0; - while ( i < numproposals ) + while ( i < incoming ) { - proposalList.push( - Proposal( - { - name: proposalNames[i], - voteCount: 0 - } - ) - ); + proposalList.push( proposalNames[i] ); i++; } } - // Give `voter` the right to vote on this ballot. - function giveRightToVote( address voter ) + /// @notice Give 'voters' the right to vote on this ballot. + /// @param voters The list of voters to add to the list of allowed voters. + function giveRightToVote( address[] voters ) public { // May only be called by chairman. - require( msg.sender == rules.chairman ); - require( rules.voteBefore < now ); - uint idx = voterMap[voter]; - // Can't add voters more than once. - require( idx == 0 ); - // Not even the voter listed in [0]. - if ( voterList.length > 0 ) - require( voterList[0].voter != voter ); - // If the voter's address doesn't match, it is because - // he doesn't exist (and then we always have idx=0). - // So we push him onto the voterList. - idx = voterList.push( - Voter( - { - voter: voter, - vote: -1 - } - ) - ) - 1; - voterMap[voter] = idx; - numvoters++; + require( msg.sender == ballotChairman ); + require( ballotVoteBefore < now ); + uint len = voters.length; + uint i = 0; + while ( i < len ) + { + address voter = voters[i]; + uint idx = voterMap[voter]; + /// @dev Can't add any voters more than once. + require( idx == 0 ); + /// @dev Not even the voter listed in [0] can vote more than once. + if ( voterList.length > 0 ) + require( voterList[0].voter != voter ); + /// @dev If the voter's address doesn't match, it is because + /// @dev he doesn't exist (and then we always have idx=0). + /// @dev So we push him onto the voterList. + idx = voterList.push( + Voter( + { + voter: voter, + votedProposals: emptyuintlist, + votesLeft: ballotMaxVotesPerVoter + } + ) + ) - 1; + voterMap[voter] = idx; + i++; + } } function getVoterIdx( address voter ) @@ -142,24 +140,26 @@ contract Ballot { return -1; } - /// Give your vote to proposal `proposals[proposal].name`. function vote( uint proposal ) public { - require( proposal < numproposals ); - require( rules.voteStarts >= now ); - require( rules.voteBefore < now ); + require( proposal < proposalList.length ); + require( ballotVoteStarts >= now ); + require( ballotVoteBefore < now ); int idx = getVoterIdx( msg.sender ); require( idx > -1 ); uint uidx = uint( idx ); - int formervote = voterList[uidx].vote; - require( formervote != int(proposal) ); - if ( formervote > -1 ) + require( voterList[uidx].votesLeft > 0 ); + uint proposalCounter = 0; + uint i = voterList[uidx].votedProposals.length; + while ( i > 0 ) { - // He changed his vote - this is normal for politicians, too. - proposalList[ uint(formervote) ].voteCount--; + i--; + if ( voterList[uidx].votedProposals[i] == proposal ) + proposalCounter++; } - proposalList[ proposal ].voteCount++; - voterList[ uidx ].vote = int(proposal); + require( proposalCounter < ballotMaxVotesPerProposal ); + voterList[uidx].votesLeft--; + voterList[uidx].votedProposals.push( proposal ); } }