@waku/vote-sdk-contracts v0.4.0
wakuconnect-voting-sdk/contracts
WakuConnect Proposal Contracts.
Voting Contract
The Voting Contract is a smart contract that enables the storage of proposals, from creation to vote result.
Lifecycle of proposal:
- Initialize proposal,
- Proposal is open, votes are accepted,
Voting time ends, votes are no longer accepted, result is final.
Types
The main types used in the contract are:
ProposalHolds information about the proposal
Fields
// block at which the proposal was created uint256 startBlock; // timestamp in seconds after which new votes won't be accepted // when casting votes endAt is compared to block.timestamp uint256 endAt; // proposal title string title; // proposal description string description; // total number of votes in favour of the proposal, this may be weighted uint256 totalVotesFor; // total number of votes against the proposal, this may be weighted uint256 totalVotesAgainst; // list of addresses that already voted address[] voters;VoteHolds the information for one vote
Fields
// address of the voter address voter; // encoded proposalId and type // first bit this field is a vote type: // 1 is a vote for // 0 is a vote against // rest of this field is a roomId shifted one bit to // the left uint256 proposalIdAndType; // amount of token used to vote uint256 tokenAmount; //signature of vote bytes32 r; bytes32 vs;
Constants and variables
tokencontract address of the token used for vote verification. It is assigned at contract creation. Only holders of this token can vote.voteDurationLength of duration during which proposals can be vote on, in seconds. It is assigned at contract creation.EIP712DOMAIN_TYPEHASHConstant holding type hash of EIP712 domain as per EIP712 specification.VOTE_TYPEHASHConstant holding type hash of Vote as per EIP712 specification.DOMAIN_SEPARATORVariable holding hash of domain separator according to EIP712 spec. It is assigned at smart contract creation.votedHolds whether a given address has voted to a given proposal. It is a mapping of proposal id to mapping of addresses to booleans which indicates whether the given address has voted.proposalsArray that holds all proposals. The id for a proposal is the index in this array.
Signing with EIP712
EIP712 MUST be used to sign votes. The structure of typed data for a vote message is as follows:
{
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
Vote: [
{ name: 'proposalIdAndType', type: 'uint256' },
{ name: 'tokenAmount', type: 'uint256' },
{ name: 'voter', type: 'address' },
],
},
primaryType: 'Vote',
domain: {
name: 'Voting Contract',
version: '1',
chainId: chainId,
verifyingContract: contract.address,
},
message: {
voter: voterAddress,
tokenAmount: tokenAmount,
proposalIdAndType: proposalIdAndType
}
}For more information about EIP-712 go to docs
Functions
constructor(IERC20 _address)Assigns_addresstotokenand generatesDOMAIN_SEPARATORgetProposals()Returns proposalsgetProposalsLength()Returns the length of theproposalsarraygetLastNProposals(uint256 amount)Returns the N last proposalsgetProposalFromId(uint256 id)Gets proposal from given idgetOpenProposals()Returns proposals for whichproposal.endAt > block.timestampwhich means the proposals are still accepting votes. SinceproposalDurationis set at contract creation and never changed,proposal.endAtis never decreasing with increasing index of votingRoom. Therefore, it is enough to check fromproposal.lengthup to first element whichendAt < block.timestamplistVotersForProposal(uint256 proposalId)Returns a list of voters for a given proposal. Reverts if there are no proposal for the givenproposalId.initializeProposal(string calldata title, string calldata description, uint256 creatorVoteForAmount)Creates a new proposal with the vote of the proposal creator. First checks if the creator voter has enough tokens to set vote for. Then creates a new proposal.startBlockis set as current block number.endAtis set a current block timestamp plusvotingDuration.titleis set as argumenttitle.descriptionis set as argumentdescription.creatorVoteForAmountis set as argumentvoteAmount. Mappingvotedof new proposal id ofmsg.senderis set to true to reflect that message sender has voted on this voting room withcreatorVoteForAmount.votingRoomsare appended with newVotingRoom andvotersin this new appended element are appended with message sender. After proposal creation,ProposalStartedis emitted.verify(Vote calldata vote, bytes32 r, bytes32 vs)Function used to verify thatvotewas signed byvote.voteras per EIP712 specification. See docs for more info.castVote(Vote calldata vote, uint256 proposalId)Cast a single vote. UpdatestotalVotesamount of proposal with index corresponding toproposalId.If voting first bit of
vote.proposalIdAndTypeis 1 that means that vote is for andvote.tokenAmountis added tovotingRooms[roomId].totalVotesFor, otherwise ifvote.proposalIdAndTypeis 0vote.tokenAmountis added tovotingRooms[roomId].totalVotesAgainst.After that add new address to room
votersand updates mappingvotedaccordingly.castVotes(Vote[] calldata votes)Function used to cast votes on a single proposas. Function accepts an array of votes of typeVote.All votes are looped through and verified that votes are:
- properly signed
- voter has enough tokens to vote
- proposal exists
proposal hasn't been closed
Vote verification is as follows:
First
proposalIdis decoded fromvote.proposalIdAndTypewhich means shifting it to the right once.Then it is verified that the proposal with given
proposalIdexists and isn't closed, otherwise, function reverts.Then it is checked that
vote.voterdidn't vote in this vote room before if he did function goes to another voter (IDEA: emit alreadyVoted ?).After that it is verified that
votehas been signed byvote.voter.Last check is whether
vote.voterhas enough tokens to vote.If all checks passed, the vote is cast with
castVoteandVoteCastis emitted.