# Walkthrough of lil-voting **Published by:** [Kunal Arora](https://paragraph.com/@lanuk/) **Published on:** 2022-04-18 **URL:** https://paragraph.com/@lanuk/walkthrough-of-lil-voting ## Content Test and try out this simplified version of the quadratic voting mechanism used by projects like Gitcoin. This was inspired by Solidity Doc’s version which I extended to include quadratic voting, a more efficient way of adding voters, and events to tell you when the voting has finished. I further tested all working parts of the code with the Foundry testing toolkit. Here’s a brief run-through of the features and functionality of the code before I dive deeper and explain the code and my thought process behind it. The contract below shows how delegate voting works, automatically and transparently. We create one contract per ballot where we put in proposals we want to vote on. Then, the chairperson (deployer of the contract) distributes away the right to vote to each address. The voters can either delegate their vote to someone they trust or vote on their chosen proposal themselves. In the end, when everyone casts their vote, the winning proposal is declared courtesy of the higher number of votes. A bit about quadratic voting: Quadratic Voting or QV is a more radical voting method giving a more accurate representation of people’s intensity and direction of preference in collective decisions. The quadratic formula is used to take a square root of individual weightage. This way to have 3x the impact, you’ll need 9x the votes. So, while you are increasing the chances of victory for your issue with each additional vote, the quadratic nature of the voting ensures that only those who care deeply about issues will cast additional votes for them. More about this here.lil-voting.solFirst, our calculations for the quadratic formula require square roots and hence floating numbers which are not supported by the EVM. The solution that Uniswap found which I will be using is to use 224-bit values, with 112 bits for the integer part, and 112 bits for the fraction. So 1.0 is represented as 2^112, 1.5 is represented as 2^112 + 2^111, etc. More details about this library are available here. using UQ112x112 for uint; We define the struct holding Voter info including the dynamic weight each person’s vote carries and Proposal info that has a bytes32 name and keeps track of the votes tally. struct Voter { uint weight; // weight is accumulated by delegation bool voted; // if true, that person already voted address delegate; // person delegated to uint vote; // index of the voted proposal } // This is a type for a single proposal. struct Proposal { bytes32 name; // short name (up to 32 bytes) uint voteCount; // number of accumulated votes } After defining the state variables as public mappings for the voters and dynamic arrays for the proposals, we declare an event for listening to instances of all votes being cast either through delegation or directly. Events are used to inform external agents that something just happened to the state of the blockchain. They are like Event Listeners in Javascript. More about them here. mapping(address => Voter) public voters; Proposal[] public proposals; event EveryoneHasVoted(uint _totalVotes, bytes32 _winningProposal); We use the contract constructor to initial all the proposals passed in as a list of bytes32 elements. All the voters get their weight set to 1e18 which is 10^18 as per EVM convention and the proposals are pushed into the dynamic proposals array one by one. constructor(bytes32[] memory proposalNames) { chairperson = msg.sender; voters[chairperson].weight = 1e18; numVoters = 1; for (uint i = 0; i < proposalNames.length; i++) { proposals.push(Proposal({ name: proposalNames[i], voteCount: 0 })); numVoters += 1; } } Giving the right to vote is a right reserved for the chairperson who is set to be the deployer of the contract, this is restricted by the first require. Each voter passed in as a parameter is initialized to have 1e18 weight by default. function giveRightToVote(address[] memory _voters) external { require( msg.sender == chairperson, "Only chairperson can give right to vote." ); for (uint i = 0; i < _voters.length; i++) { require( !voters[_voters[i]].voted, "The voter already voted." ); require(voters[_voters[i]].weight == 0); voters[_voters[i]].weight = 1e18; } } After receiving your voting share, you can either vote yourself or delegate your vote to someone else, who you think is better suited and trust. This is a common practice in a lot of protocols and DAOs and also non-web3 organizations. Say voter1 delegates to voter2 and voter2 delegates to voter3, and voter3 delegates to voter1. We have formed a cycle in our line of voting power. This is undesirable for our ballot. So, we use a while loop checking for loops of delegation. In our example, voter3 will be stopped before they can delegate their vote to voter1. In the case where the delegate has already voted, we can simply add the delegator’s vote to the proposal tally that the delegatee voted on. Otherwise, we increase the delegatee’s weight. function delegate(address to) external { require(voters[msg.sender].weight != 0, "voter not found"); Voter storage sender = voters[msg.sender]; require(!sender.voted, "You already voted."); require(to != msg.sender, "Self-delegation is disallowed."); while (voters[to].delegate != address(0)) { to = voters[to].delegate; // We found a loop in the delegation, not allowed. require(to != msg.sender, "Found loop in delegation."); } Voter storage delegate_ = voters[to]; // Voters cannot delegate to wallets that cannot vote. require(delegate_.weight > 0); sender.voted = true; sender.delegate = to; if (delegate_.voted) { weightCasted += sender.weight; proposals[delegate_.vote].voteCount += Math.sqrt(sender.weight); concludeVoting(); } else { delegate_.weight += sender.weight; } } Actual voting is relatively straightforward. The nuance of the quadratic mechanism is taking a square root of the actual weight. This is where using UQ112x112 is essential and since square root functionality is not supported by EVM, we need to define the rudimental Babylonian method for finding square roots in ../util/Math.sol. function vote(uint proposal) external { Voter storage sender = voters[msg.sender]; require(sender.weight != 0, "Has no right to vote"); require(!sender.voted, "Already voted."); sender.voted = true; sender.vote = proposal; weightCasted += sender.weight; proposals[proposal].voteCount += Math.sqrt(sender.weight); concludeVoting(); } We can create an instance of the event we defined wherever we want in the contract. Additionally, we can pass in appropriate data such as the winning proposal’s name for an easier lookup for the outside world. if (checkIfEveryoneVoted()) { emit EveryoneHasVoted(numVoters, proposals[winningProposal()].name); } Finally, once we have concluded a vote. We can reveal the winner. In this implementation, I’ve decided not to resolve ties but feel free to experiment and change to other tie-breakers. function winningProposal() public view returns (uint _winningProposal) { require(checkIfEveryoneVoted(), "Not everyone has voted yet."); uint _winningVoteCount = 0; bool tie = false; for (uint p = 0; p < proposals.length; p++) { if (proposals[p].voteCount > _winningVoteCount) { _winningVoteCount = proposals[p].voteCount; _winningProposal = p; tie = false; } else if (proposals[p].voteCount == _winningVoteCount) { tie = true; } } require(!tie, "Tie between proposals."); } Voting.t.solFoundry’s testing toolkit makes it easy to test our contracts in Solidity. If the test function reverts, the test fails, otherwise, it passes. The testing contract inherits from ds-test and uses the optional setUp() function. We can examine two of the tests: We are testing to see if the function delegate() reverts if we try to form a loop in the form address(1) → address(2) → address(3) → address(1). We are using testFail prefix - if the test function does not revert, the test case fails. function testFailDelegateLoop() public { vm.startPrank(address(1)); testBallot.delegate(address(2)); vm.stopPrank(); vm.startPrank(address(2)); testBallot.delegate(address(3)); vm.stopPrank(); vm.startPrank(address(3)); testBallot.delegate(address(1)); vm.stopPrank(); } The second test case we are examining is slightly more complicated. We have defined an interface for detecting events and comparing them to the expected occurrence of the exact same event.interface CheatCodes { function expectEmit( bool, bool, bool, bool ) external; } We add address(4) along with the chairperson, address(1), address(2), and address(3) as voters. We delegate 1→3 and 2→3 votes and they are competing against 4 and the chair. This case especially highlights the brilliance of quadratic voting. By delegating their votes to 3, 1 and 2 have lost a part of the votes’ weight. As sqrt(3) is 1.732 vs 4 and the chair can collectively put in 2 votes who end up with the winning proposal even though they were outnumbered in a majority setting. Once the last person casts their vote (deployer here), we caught an EveryoneHasVoted event which is to be expected and to be tested against. function testExpectEmit() public { testBallot.addVoter(address(4)); // // 1->3 and 2->3 vs chair and 4 vm.startPrank(address(1)); testBallot.delegate(address(3)); vm.stopPrank(); vm.startPrank(address(2)); testBallot.delegate(address(3)); vm.stopPrank(); vm.startPrank(address(3)); testBallot.vote(0); vm.stopPrank(); vm.startPrank(deployer); testBallot.vote(1); vm.stopPrank(); cheats.expectEmit(true, true, false, false); emit EveryoneHasVoted(5, bytes32("sue the SEC")); vm.startPrank(address(4)); testBallot.vote(1); vm.stopPrank(); } Hopefully, you learned a lot perusing through this and you’re ready to get your hands dirty. This is the actual contract. Some possible modifications or improvements you can make can be:adding new voters with differentiated weightsresolving tiesevents keeping track of useful status updates like when one proposal overtook the other as the leading one in the ongoing voteholding multiple ballot votes with just one contractrewarding long time voters with incremental gains in individual weightsThis is the end of the walkthrough. Thanks for reading. I’m VP of Education at Blockchain at San Diego, UCSD’s only blockchain-only org, being a bridge between the abundantly talented student body on campus and the ever-growing crypto industry. Give either BaSD or me a follow on Twitter for more of such content! ## Publication Information - [Kunal Arora](https://paragraph.com/@lanuk/): Publication homepage - [All Posts](https://paragraph.com/@lanuk/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@lanuk): Subscribe to updates - [Twitter](https://twitter.com/auroraByKunal): Follow on Twitter