This is the last of a 3-part article which presents the Solidity-based implementation of the Token weighted voting protocol.
In part 2 of this article we concluded by defining the isAddressLocked function in our VotingLibrary which is key to the implementation of the token locking logic we focus on here. Further on, we define the algorithm of revealing and counting secret votes as well as token unlocking.
Our token contract implementation is based on the ERC #20 standard where there are two functions that enable token transfers: transfer and transferFrom. To enable token locking, we extend these functions with the following logic:
Once transfer is called, we first establish whether the sender or recipient addresses are locked via the isAddressLocked function in our Voting contract, described in part 2 of this article.
In the case when the sender is locked, we fail the transfer. If however the recipient is locked, we hold the transfer amount in an onHoldBalance. After the user reveals their vote, thus unlocking their tokens, that onHoldBalance is transferred to their available balance. The held balance ‘read’, ‘write’ and ‘transfer to balance’ functions are defined as follows:
The gas cost changes resulting from the additional checks we introduced, show transfer function gas cost has increased only by ~4,500 gas (measured at 53559 before, 58146 after) or roughly 8.5%.
Reveal vote transaction
This algorithm requires users to always reveal their votes, even if the reveal period has expired. When the pollCloseTime is reached, every user who had voted in that particular poll has their tokens locked, essentially the isAddressLocked function called for their address returns true. This entails that all token transfer calls they make, as per above, will fail and any transfers tokens they receive, will be put on hold. This is mitigated by the fact that a user can unlock their tokens immediately after the poll closes by revealing their vote. This revealVote transaction is detailed below.
This is token-weighted voting, so firstly we retrieve the user token balance as this affects how we count the vote .
Secondly we validate the state by checking that the poll has closed and the vote secret is the same as the one submitted when the user voted. This is done by comparing the saved secret to the hash of the salt and pollOptionId the user has voted on. For details of generating the secret vote, see Secret voting in part 2 of this article.
Once the vote is revealed and validated, we determine whether to count it in the final poll results. This is determined again by the poll status property, which if resolved, means the admin has taken the final poll results and counting votes is over. Otherwise, the votes on the decision are incremented appropriately based on the current balance of the revealing user which we had already retrieved in the first part of this transaction. For example, if a user A with 1200 tokens had voted for option X, then the total count of poll option X is increased by 1200 once user A submits their revealVote transaction (and before the poll is resolved).
We then continue to unlock the user account for token transfers and any tokens that have been on hold are transferred to their standard available balance. Note that tokens are only released once no unrevealed votes remain. As it could be the case that there are other votes left to be revealed, in which case the current revealVote transaction doesn’t unlock the account.
This implementation was presented at Devcon2 (which is where the featured photos of the Shanghai skyline are from, in case you wonder about the connection between China and democratic voting protocols).
The recording from our presentation there gives a good high level overview of the theory and practice of this implementation.