Skip to content

Lurve Voting

BokkyPooBah edited this page Dec 6, 2017 · 20 revisions

A simple Yes/No voting smart contract. Can be used to display the number of likes and dislikes on your web page.

The main bug-fixed Lurve contract is deployed to Ropsten at 0xdcb39e9f4b8347ec08b56a32c0e44b0751e6d421 in block 1673487.

Question - Do you love the Lurve Voting contract?

View the results at https://ethvoter.thedao.fund/lurve/ or http://ethvoter.thedao.fund/lurve/.

You can view the smart contract results at the main Lurve contract at 0xdcb39e9f4b8347ec08b56a32c0e44b0751e6d421.

The Application Binary Interface for the main Lurve contract deployed to Ropsten at 0xdcb39e9f4b8347ec08b56a32c0e44b0751e6d421:

[{"constant":true,"inputs":[],"name":"lurveYes","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"yesCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"seen","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"},{"name":"yesNo","type":"uint256"}],"name":"registerLurve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"noCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"lurveNo","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"addr","type":"address"},{"indexed":false,"name":"yesNo","type":"uint256"}],"name":"LurveIt","type":"event"}]


How It Works

The Lurve smart contract will create two addresses.

Users send a transaction to either of the two addresses.

Sending the transaction to the first address will register a vote of No and sending the transaction to the second address will register a vote of Yes.

Each account can only vote once, either a No or Yes.

Any ethers sent with the transaction will be returned back to the user's account. Transactions sending 0 ETH are accepted, and preferred.


Notes

  • The yesNo flag is a uint so you can extend it to multiple states instead of just a Yes/No. Otherwise it can be replaced by bool.

Smart Contract

From Lurve.sol:

pragma solidity ^0.4.16;

// -----------------------------------------------------------------------------
// See https://github.com/bokkypoobah/EthVoter
//
// Version 0.91
// 
// The MIT Licence 2017
// -----------------------------------------------------------------------------
contract LurveOption {
    Lurve public lurve;
    uint public yesNo;
    
    // Constructor - save parameters
    function LurveOption(Lurve _lurve, uint _yesNo) {
        lurve = _lurve;
        yesNo = _yesNo;
    }
    
    // User sends a 0 or non-0 ETH transaction to the address
    function () payable {
        // Call the factory to register the votes
        lurve.registerLurve(msg.sender, yesNo);
        // Send back any non-0 ETH
        if (msg.value > 0) {
            msg.sender.transfer(msg.value);
        }
    }
}

contract Lurve {
    // Contract addresses
    LurveOption public lurveNo;
    LurveOption public lurveYes;
    
    // Count of No votes
    uint public noCount;
    // Count of Yes votes
    uint public yesCount;
    
    // Save addresses that have voted
    mapping(address => bool) public seen;
    
    event LurveIt(address indexed addr, uint yesNo);
    
    // Constructor - deploy the yes and no contracts
    function Lurve() {
        // Deploy a contract to an address that will register a '0' vote
        lurveNo = new LurveOption(this, 0);
        // Deploy a contract to an address that will register a '1' vote
        lurveYes = new LurveOption(this, 1);
    }
    
    function registerLurve(address addr, uint yesNo) {
        // Only allow lurveNo and lurveYes to call this function
        require(msg.sender == address(lurveNo) || msg.sender == address(lurveYes));
        // Only allow each address to vote once
        require(!seen[addr]);
        // Register the address that is voting
        seen[addr] = true;
        // Register the vote
        if (yesNo == 0) {
            noCount++;
        } else {
            yesCount++;
        }
        // Log the event
        LurveIt(addr, yesNo);
    }
}

Script To Extract Data

I created /home/bokky/Lurve/getLurve.sh with the following contents:

#!/bin/sh

geth attach rpc:http://localhost:8545 << EOF | grep "RESULT: " | sed "s/RESULT: //"

var lurveAddress="0xdcb39e9f4b8347ec08b56a32c0e44b0751e6d421";
var lurveAbi=[{"constant":true,"inputs":[],"name":"lurveYes","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"yesCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"seen","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"},{"name":"yesNo","type":"uint256"}],"name":"registerLurve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"noCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"lurveNo","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"addr","type":"address"},{"indexed":false,"name":"yesNo","type":"uint256"}],"name":"LurveIt","type":"event"}];
var lurveBlockNumber=1673487;

var lurve=web3.eth.contract(lurveAbi).at(lurveAddress);

console.log("RESULT: noCount=" + lurve.noCount());
console.log("RESULT: yesCount=" + lurve.yesCount());

var lurveEvents = lurve.LurveIt({}, { fromBlock: lurveBlockNumber, toBlock: "latest" });
var i = 0;
lurveEvents.watch(function (error, result) {
  console.log("RESULT: LurveIt " + i++ + " #" + result.blockNumber + ": addr=" + result.args.addr + " yesNo=" + result.args.yesNo);
});
lurveEvents.stopWatching();

EOF

I set the executable permission for this script using the command:

chmod 700 /home/bokky/Lurve/getLurve.sh

Sample output:

$ ./getLurve.sh 
noCount=1
yesCount=2
LurveIt 0 #1673566: addr=0x0052d07bb447273181412197b1057d9a805edca4 yesNo=0
LurveIt 1 #1674428: addr=0xd9d72d466637e8408bb3b17d3ff6db02e8bebf27 yesNo=1
LurveIt 2 #1675069: addr=0x4aac1b2a1c2fee71a896e4b32b8fb3ab5a37562f yesNo=1

Lurve Web Page Generator

Script To Generate Web Page

I created /home/bokky/Lurve/genLurveHTML.sh with the following contents:

#!/bin/sh

HTMLFILE=/var/www/html/lurve/index.html

geth attach rpc:http://localhost:8545 << EOF | grep "RESULT: " | sed "s/RESULT: //" > $HTMLFILE

console.log("RESULT: <html>");
console.log("RESULT:   <head>");
console.log("RESULT:     <title>The Lurve Page</title>");
console.log("RESULT:   </head>");
console.log("RESULT:   <body>");
console.log("RESULT:     <h2>The Lurve Page</h2>");
console.log("RESULT:     <p>Send a 0 (or non-0) Ropsten Testnet ETH transaction to <a href='https://ropsten.etherscan.io/address/0xb3134f8709b8f5763c0c755517fd0403fc2e1367'>0xb3134f8709b8f5763c0c755517fd0403fc2e1367</a
> if you lurve this page</p>");
console.log("RESULT:     <p>Send a 0 (or non-0) Ropsten Testnet ETH transaction to <a href='https://ropsten.etherscan.io/address/0x26742b34ea5885d1f42757b16b10dd3531df2d41'>0x26742b34ea5885d1f42757b16b10dd3531df2d41</a
> if you don't lurve this page</p>");
console.log("RESULT:     <br />");
console.log("RESULT:     <h2>Statistics</h2>");

var lurveAddress="0xdcb39e9f4b8347ec08b56a32c0e44b0751e6d421";
var lurveAbi=[{"constant":true,"inputs":[],"name":"lurveYes","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"yesCount","outputs"
:[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"seen","outputs":[{"name":"","type":"bool"}],"payable":false,"
stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"},{"name":"yesNo","type":"uint256"}],"name":"registerLurve","outputs":[],"payable":false,"stateMutability":"nonpayab
le","type":"function"},{"constant":true,"inputs":[],"name":"noCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"lurveNo","o
utputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexe
d":true,"name":"addr","type":"address"},{"indexed":false,"name":"yesNo","type":"uint256"}],"name":"LurveIt","type":"event"}];
var lurveBlockNumber=1673487;

var lurve=web3.eth.contract(lurveAbi).at(lurveAddress);
var total=lurve.noCount().add(lurve.yesCount());

console.log("RESULT:       <p>Total votes=" + total + "</p>");
console.log("RESULT:       <p>Yes votes=" + lurve.yesCount() + " (" + lurve.yesCount()/total*100 + "%)</p>");
console.log("RESULT:       <p>No votes=" + lurve.noCount() + " (" + lurve.noCount()/total*100 + "%)</p>");
console.log("RESULT:       <br />");

console.log("RESULT:     <h2>Votes</h2>");
console.log("RESULT:     <p>Statistics collected at <a href='https://ropsten.etherscan.io/address/0xdcb39e9f4b8347ec08b56a32c0e44b0751e6d421#readContract'>0xdcb39e9f4b8347ec08b56a32c0e44b0751e6d421</a></p>");
var lurveEvents = lurve.LurveIt({}, { fromBlock: lurveBlockNumber, toBlock: "latest" });
var i = 0;
lurveEvents.watch(function (error, result) {
  console.log("RESULT:     <p>LurveIt " + i++ + " #" + result.blockNumber + ": addr=" + result.args.addr + " yesNo=" + result.args.yesNo + 
    " hash=<a href='https://ropsten.etherscan.io/tx/" + result.transactionHash + "'>" + result.transactionHash + "</a></p>");
});
lurveEvents.stopWatching();

console.log("RESULT:       <br />");
console.log("RESULT:       <p>Further information at <a href='https://github.com/bokkypoobah/EthVoter/wiki/Lurve-Voting'>https://github.com/bokkypoobah/EthVoter/wiki/Lurve-Voting</a></p>");
console.log("RESULT:       <br />");
console.log("RESULT:       <p>Page generated " + new Date().toUTCString() + "</p>");
console.log("RESULT:       <br />");
console.log("RESULT:       Enjoy. BokkyPooBah 2017");
console.log("RESULT:   </body>");
console.log("RESULT: </html>");

EOF

I set the executable permission for this script using the command:

chmod 700 /home/bokky/Lurve/genLurveHTML.sh

I created the HTML directory using the command:

sudo mkdir /var/www/html/lurve

I set the ownership of the HTML directory using the command:

sudo chown bokky:bokky /var/www/html/lurve

The script is scheduled to run every minute by adding the following entry to crontab -e:

# m h  dom mon dow   command
* * * * * /home/bokky/Lurve/genLurveHTML.sh


Other Voting Systems

Some existing voting systems to consider: