Merge branch '000-snt-voting-dapp' of https://github.com/status-im/contracts into 000-snt-voting-dapp

This commit is contained in:
Barry Gitarts 2018-06-26 16:35:12 -04:00
commit 00957320b1
8 changed files with 161 additions and 244 deletions

View File

@ -48,10 +48,6 @@
"TestToken": { "TestToken": {
"deploy": true "deploy": true
}, },
"MultiOptionPollManager": {
"deploy": false
},
"SNT": { "SNT": {
"instanceOf": "MiniMeToken", "instanceOf": "MiniMeToken",
"deploy": true, "deploy": true,
@ -83,10 +79,13 @@
], ],
"gasLimit": 5000000 "gasLimit": 5000000
}, },
"PollManager": { "SingleChoiceFactory": {
"deploy": true, "deploy": false
"args": ["$MiniMeTokenFactory", "$SNT"] },
}, "PollManager": {
"deploy": true,
"args": ["$MiniMeTokenFactory", "$SNT"]
},
"SingleChoice": { "SingleChoice": {
"deploy": false "deploy": false
} }

View File

@ -1,145 +0,0 @@
pragma solidity ^0.4.21;
import "../common/Controlled.sol";
import "../token/MiniMeTokenInterface.sol";
import "../token/MiniMeTokenFactory.sol";
/**
* @title PollManager
* @author Richard Ramos (Status Research & Development GmbH)
*/
contract MultiOptionPollManager is Controlled {
event PollCreated(uint256 pollId, uint8 numOptions);
event PollCanceled(uint256 pollId);
event Voted(address voter, uint8[] votes);
MiniMeTokenFactory public tokenFactory;
MiniMeTokenInterface public token;
Poll[] public polls;
struct Vote {
mapping(uint8 => uint8) distribution;
bool voted;
}
struct Poll {
uint start;
uint end;
uint8 numOptions;
address[] voters;
mapping(address => Vote) voteMap;
uint256[] results;
address token;
bool cancelled;
}
constructor(address _tokenFactory, address _token)
public
{
tokenFactory = MiniMeTokenFactory(_tokenFactory);
token = MiniMeTokenInterface(_token);
}
function createPoll(
uint _blocksUntilVotingStart,
uint _voteDuration,
uint8 _numOptions
)
public
onlyController
returns (uint pollId)
{
pollId = polls.length++;
Poll storage p = polls[pollId];
p.cancelled = false;
p.start = block.number + _blocksUntilVotingStart;
p.end = p.start + _voteDuration;
p.numOptions = _numOptions;
p.token = tokenFactory.createCloneToken(
token,
p.start - 1,
"VotingToken",
MiniMeToken(token).decimals(),
"VTN",
true);
emit PollCreated(pollId, _numOptions);
}
function vote(uint _pollId, uint8[] _vote)
public
{
Poll storage poll = polls[_pollId];
require(block.number >= poll.start);
require(block.number <= poll.end);
require(_vote.length == poll.numOptions);
require(!poll.voteMap[msg.sender].voted);
require(!poll.cancelled);
uint8 percentage = 0;
for(uint8 i = 0; i < poll.numOptions; i++){
poll.results[i] = (MiniMeTokenInterface(poll.token).balanceOf(msg.sender) * _vote[i]) / 100;
percentage += _vote[i];
poll.voteMap[msg.sender].distribution[i] = _vote[i];
}
require(percentage == 100);
poll.voteMap[msg.sender].voted = true;
poll.voters.push(msg.sender);
emit Voted(msg.sender, _vote);
}
function getPollCount()
public
view
returns (uint256)
{
return polls.length;
}
function exists(uint _pollId)
public
view
returns (bool) {
return polls.length != 0 && polls[_pollId].start != 0;
}
function getPollResults(uint _pollId)
external
view
returns (uint256[]){
return polls[_pollId].results;
}
function hasVotesRecorded(uint256 _pollId)
external
view
returns (bool)
{
return polls[_pollId].voters.length > 0;
}
function isVotingAvailable(uint _pollId) public view returns (bool){
Poll memory p = polls[_pollId];
return p.end > block.number;
}
function cancel(uint _pollId) onlyController {
require(polls[_pollId].start > 0);
require(polls[_pollId].end < block.number);
Poll storage p = polls[_pollId];
p.cancelled = true;
emit PollCanceled(_pollId);
}
}

View File

@ -0,0 +1,5 @@
pragma solidity ^0.4.23;
contract IPollFactory {
function create(bytes _description) public returns(address);
}

View File

@ -65,7 +65,7 @@ contract LowLevelStringManipulator {
} }
function getTokenNameSymbol(address tokenAddr) internal returns (string name, string symbol) { function getTokenNameSymbol(address tokenAddr) internal returns (string name, string symbol) {
return (getString(tokenAddr, bytes4(sha3("name()"))),getString(tokenAddr, bytes4(sha3("symbol()")))); return (getString(tokenAddr, bytes4(keccak256("name()"))),getString(tokenAddr, bytes4(keccak256("symbol()"))));
} }
function getString(address _dst, bytes4 sig) internal returns(string) { function getString(address _dst, bytes4 sig) internal returns(string) {

View File

@ -3,16 +3,17 @@ pragma solidity ^0.4.23;
import "../common/Controlled.sol"; import "../common/Controlled.sol";
import "./LowLevelStringManipulator.sol"; import "./LowLevelStringManipulator.sol";
import "../token/MiniMeToken.sol"; import "../token/MiniMeToken.sol";
import "./IPollFactory.sol";
import "./SingleChoiceFactory.sol";
contract IPollContract { contract IPollContract {
function deltaVote(int _amount, bytes32 _ballot) returns (bool _succes); function deltaVote(int _amount, bytes32 _ballot) public returns (bool _succes);
function pollType() constant returns (bytes32); function pollType() public constant returns (bytes32);
function question() constant returns (string); function question() public constant returns (string);
} }
contract IPollFactory {
function create(bytes _description) returns(address);
}
contract PollManager is LowLevelStringManipulator, Controlled { contract PollManager is LowLevelStringManipulator, Controlled {
@ -33,14 +34,16 @@ contract PollManager is LowLevelStringManipulator, Controlled {
} }
Poll[] _polls; Poll[] _polls;
IPollFactory pollFactory;
MiniMeTokenFactory public tokenFactory; MiniMeTokenFactory public tokenFactory;
MiniMeToken public token; MiniMeToken public token;
function PollManager(address _tokenFactory, address _token) constructor(address _tokenFactory, address _token)
public { public {
tokenFactory = MiniMeTokenFactory(_tokenFactory); tokenFactory = MiniMeTokenFactory(_tokenFactory);
token = MiniMeToken(_token); token = MiniMeToken(_token);
pollFactory = IPollFactory(new SingleChoiceFactory());
} }
modifier onlySNTHolder { modifier onlySNTHolder {
@ -52,21 +55,24 @@ contract PollManager is LowLevelStringManipulator, Controlled {
function addPoll( function addPoll(
uint _startBlock, uint _startBlock,
uint _endBlock, uint _endBlock,
address _pollFactory,
bytes _description) bytes _description)
public
onlySNTHolder onlySNTHolder
returns (uint _idPoll) returns (uint _idPoll)
{ {
if (_endBlock <= _startBlock) throw; require(_endBlock > _startBlock && _endBlock > block.number);
if (_endBlock <= getBlockNumber()) throw;
_idPoll = _polls.length; _idPoll = _polls.length;
_polls.length ++; _polls.length ++;
Poll p = _polls[ _idPoll ]; Poll storage p = _polls[ _idPoll ];
p.startBlock = _startBlock; p.startBlock = _startBlock;
p.endBlock = _endBlock; p.endBlock = _endBlock;
p.voters = 0; p.voters = 0;
var (name, symbol) = getTokenNameSymbol(address(token)); string memory name;
string memory symbol;
(name, symbol) = getTokenNameSymbol(address(token));
string memory proposalName = strConcat(name, "_", uint2str(_idPoll)); string memory proposalName = strConcat(name, "_", uint2str(_idPoll));
string memory proposalSymbol = strConcat(symbol, "_", uint2str(_idPoll)); string memory proposalSymbol = strConcat(symbol, "_", uint2str(_idPoll));
@ -78,23 +84,32 @@ contract PollManager is LowLevelStringManipulator, Controlled {
proposalSymbol, proposalSymbol,
true); true);
p.pollContract = pollFactory.create(_description);
p.pollContract = IPollFactory(_pollFactory).create(_description); require(p.pollContract != 0);
if (p.pollContract == 0) throw;
emit PollCreated(_idPoll); emit PollCreated(_idPoll);
} }
function cancelPoll(uint _idPoll) onlyController { function cancelPoll(uint _idPoll)
if (_idPoll >= _polls.length) throw; onlyController
Poll p = _polls[_idPoll]; public
if (getBlockNumber() >= p.endBlock) throw; {
require(_idPoll < _polls.length);
Poll storage p = _polls[_idPoll];
require(p.endBlock < block.number);
p.canceled = true; p.canceled = true;
PollCanceled(_idPoll); emit PollCanceled(_idPoll);
} }
function canVote(uint _idPoll) public view returns(bool) { function canVote(uint _idPoll)
public
view
returns(bool)
{
if(_idPoll >= _polls.length) return false; if(_idPoll >= _polls.length) return false;
Poll storage p = _polls[_idPoll]; Poll storage p = _polls[_idPoll];
@ -106,23 +121,19 @@ contract PollManager is LowLevelStringManipulator, Controlled {
balance != 0; balance != 0;
} }
function vote(uint _idPoll, bytes32 _ballot) { function vote(uint _idPoll, bytes32 _ballot) public {
if (_idPoll >= _polls.length) throw; require(_idPoll < _polls.length);
Poll p = _polls[_idPoll];
if (getBlockNumber() < p.startBlock) throw; Poll storage p = _polls[_idPoll];
if (getBlockNumber() >= p.endBlock) throw;
if (p.canceled) throw; require(block.number >= p.startBlock && block.number < p.endBlock && !p.canceled);
unvote(_idPoll); unvote(_idPoll);
uint amount = MiniMeToken(p.token).balanceOf(msg.sender); uint amount = MiniMeToken(p.token).balanceOf(msg.sender);
if (amount == 0) throw; require(amount != 0);
require(MiniMeToken(p.token).transferFrom(msg.sender, address(this), amount));
// enableTransfers = true;
if (!MiniMeToken(p.token).transferFrom(msg.sender, address(this), amount)) throw;
// enableTransfers = false;
p.votes[msg.sender].ballot = _ballot; p.votes[msg.sender].ballot = _ballot;
p.votes[msg.sender].amount = amount; p.votes[msg.sender].amount = amount;
@ -131,44 +142,48 @@ contract PollManager is LowLevelStringManipulator, Controlled {
p.votersPerBallot[_ballot]++; p.votersPerBallot[_ballot]++;
if (!IPollContract(p.pollContract).deltaVote(int(amount), _ballot)) throw; require(IPollContract(p.pollContract).deltaVote(int(amount), _ballot));
Vote(_idPoll, msg.sender, _ballot, amount); emit Vote(_idPoll, msg.sender, _ballot, amount);
} }
function unvote(uint _idPoll) { function unvote(uint _idPoll) public {
if (_idPoll >= _polls.length) throw; require(_idPoll < _polls.length);
Poll p = _polls[_idPoll]; Poll storage p = _polls[_idPoll];
if (getBlockNumber() < p.startBlock) throw;
if (getBlockNumber() >= p.endBlock) throw; require(block.number >= p.startBlock && block.number < p.endBlock && !p.canceled);
if (p.canceled) throw;
uint amount = p.votes[msg.sender].amount; uint amount = p.votes[msg.sender].amount;
bytes32 ballot = p.votes[msg.sender].ballot; bytes32 ballot = p.votes[msg.sender].ballot;
if (amount == 0) return; if (amount == 0) return;
if (!IPollContract(p.pollContract).deltaVote(-int(amount), ballot)) throw; require(IPollContract(p.pollContract).deltaVote(-int(amount), ballot));
p.votes[msg.sender].ballot = 0; p.votes[msg.sender].ballot = 0x00;
p.votes[msg.sender].amount = 0; p.votes[msg.sender].amount = 0;
p.votersPerBallot[ballot]--; p.votersPerBallot[ballot]--;
p.voters--; p.voters--;
// enableTransfers = true; require(MiniMeToken(p.token).transferFrom(address(this), msg.sender, amount));
if (!MiniMeToken(p.token).transferFrom(address(this), msg.sender, amount)) throw;
// enableTransfers = false;
Unvote(_idPoll, msg.sender, ballot, amount); emit Unvote(_idPoll, msg.sender, ballot, amount);
} }
// Constant Helper Function // Constant Helper Function
function nPolls() constant returns(uint) { function nPolls()
public
view
returns(uint)
{
return _polls.length; return _polls.length;
} }
function poll(uint _idPoll) constant returns( function poll(uint _idPoll)
public
view
returns(
uint _startBlock, uint _startBlock,
uint _endBlock, uint _endBlock,
address _token, address _token,
@ -179,32 +194,44 @@ contract PollManager is LowLevelStringManipulator, Controlled {
bool _finalized, bool _finalized,
uint _totalCensus, uint _totalCensus,
uint _voters uint _voters
) { )
if (_idPoll >= _polls.length) throw; {
Poll p = _polls[_idPoll]; require(_idPoll < _polls.length);
Poll storage p = _polls[_idPoll];
_startBlock = p.startBlock; _startBlock = p.startBlock;
_endBlock = p.endBlock; _endBlock = p.endBlock;
_token = p.token; _token = p.token;
_pollContract = p.pollContract; _pollContract = p.pollContract;
_canceled = p.canceled; _canceled = p.canceled;
_pollType = IPollContract(p.pollContract).pollType(); _pollType = IPollContract(p.pollContract).pollType();
_question = getString(p.pollContract, bytes4(sha3("question()"))); _question = getString(p.pollContract, bytes4(keccak256("question()")));
_finalized = (!p.canceled) && (getBlockNumber() >= _endBlock); _finalized = (!p.canceled) && (block.number >= _endBlock);
_totalCensus = MiniMeToken(p.token).totalSupply(); _totalCensus = MiniMeToken(p.token).totalSupply();
_voters = p.voters; _voters = p.voters;
} }
function getVote(uint _idPoll, address _voter) constant returns (bytes32 _ballot, uint _amount) { function getVote(uint _idPoll, address _voter)
if (_idPoll >= _polls.length) throw; public
Poll p = _polls[_idPoll]; view
returns (bytes32 _ballot, uint _amount)
{
require(_idPoll < _polls.length);
Poll storage p = _polls[_idPoll];
_ballot = p.votes[_voter].ballot; _ballot = p.votes[_voter].ballot;
_amount = p.votes[_voter].amount; _amount = p.votes[_voter].amount;
} }
function getVotesByBallot(uint _idPoll, bytes32 _ballot) function getVotesByBallot(uint _idPoll, bytes32 _ballot)
public view returns(uint voters, uint votes) { public
if (_idPoll >= _polls.length) throw; view
returns(uint voters, uint votes)
{
require(_idPoll < _polls.length);
Poll storage p = _polls[_idPoll]; Poll storage p = _polls[_idPoll];
voters = p.votersPerBallot[_ballot]; voters = p.votersPerBallot[_ballot];
@ -212,29 +239,30 @@ contract PollManager is LowLevelStringManipulator, Controlled {
} }
function proxyPayment(address ) payable returns(bool) { function proxyPayment(address )
payable
returns(bool) {
return false; return false;
} }
function onTransfer(address , address , uint ) returns(bool) { function onTransfer(address , address , uint )
public
pure
returns(bool)
{
return true; return true;
} }
function onApprove(address , address , uint ) returns(bool) { function onApprove(address , address , uint )
public
pure
returns(bool) {
return true; return true;
} }
function getBlockNumber() internal constant returns (uint) {
return block.number;
}
event Vote(uint indexed idPoll, address indexed _voter, bytes32 ballot, uint amount); event Vote(uint indexed idPoll, address indexed _voter, bytes32 ballot, uint amount);
event Unvote(uint indexed idPoll, address indexed _voter, bytes32 ballot, uint amount); event Unvote(uint indexed idPoll, address indexed _voter, bytes32 ballot, uint amount);
event PollCanceled(uint indexed idPoll); event PollCanceled(uint indexed idPoll);
event PollCreated(uint indexed idPoll); event PollCreated(uint indexed idPoll);
} }

View File

@ -27,12 +27,15 @@ contract SingleChoice is Controlled {
string public question; string public question;
string[] public choices; string[] public choices;
int[] public result; int[] public result;
int[] public qvResult;
bytes32 uid; bytes32 uid;
function SingleChoice(address _controller, bytes _rlpDefinition, uint salt) { function SingleChoice(address _controller, bytes _rlpDefinition, uint salt) {
uid = sha3(block.blockhash(block.number-1), salt); uid = keccak256(block.blockhash(block.number-1), salt);
controller = _controller; controller = _controller;
var itmPoll = _rlpDefinition.toRLPItem(true); var itmPoll = _rlpDefinition.toRLPItem(true);
@ -55,9 +58,10 @@ contract SingleChoice is Controlled {
} }
result.length = choices.length; result.length = choices.length;
qvResult.length = choices.length;
} }
function pollType() constant returns (bytes32) { function pollType() public constant returns (bytes32) {
return bytes32("SINGLE_CHOICE"); return bytes32("SINGLE_CHOICE");
} }
@ -71,7 +75,18 @@ contract SingleChoice is Controlled {
function deltaVote(int _amount, bytes32 _ballot) onlyController returns (bool _succes) { function deltaVote(int _amount, bytes32 _ballot) onlyController returns (bool _succes) {
if (!isValid(_ballot)) return false; if (!isValid(_ballot)) return false;
uint v = uint(_ballot) / (2**248); uint v = uint(_ballot) / (2**248);
result[v] += _amount; result[v] += _amount;
int qv;
if (_amount < 0) {
qv = -sqrt(-_amount);
} else {
qv = sqrt(_amount);
}
qvResult[v] += qv;
return true; return true;
} }
@ -80,7 +95,16 @@ contract SingleChoice is Controlled {
} }
function getBallot(uint _option) constant returns(bytes32) { function getBallot(uint _option) constant returns(bytes32) {
return bytes32((_option * (2**248)) + (uint(sha3(uid, _option)) & (2**248 -1))); return bytes32((_option * (2**248)) + (uint(keccak256(uid, _option)) & (2**248 -1)));
}
function sqrt(int256 x) public pure returns (int256 y) {
int256 z = (x + 1) / 2;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
} }
} }

View File

@ -1,14 +1,15 @@
pragma solidity ^0.4.6; pragma solidity ^0.4.6;
import "./SingleChoice.sol"; import "./SingleChoice.sol";
import "./PollManager.sol"; import "./IPollFactory.sol";
contract SingleChoiceFactory is IPollFactory { contract SingleChoiceFactory is IPollFactory {
uint salt; uint salt;
function create(bytes _description) returns(address) { function create(bytes _description) public returns(address) {
salt++; salt++;
SingleChoice sc = new SingleChoice(msg.sender, _description, salt); SingleChoice sc = new SingleChoice(msg.sender, _description, salt);
SingleChoiceCreated(address(sc)); emit SingleChoiceCreated(address(sc));
return address(sc); return address(sc);
} }

View File

@ -26,12 +26,12 @@ config({
], ],
"gasLimit": 4000000 "gasLimit": 4000000
}, },
"SingleChoiceFactory": {
"deploy": false
},
"PollManager": { "PollManager": {
"deploy": true, "deploy": true,
"args": ["$MiniMeTokenFactory", "$SNT"] "args": ["$MiniMeTokenFactory", "$SNT"]
},
"SingleChoiceFactory": {
"deploy": true
} }
} }
}); });
@ -59,13 +59,13 @@ describe("VotingDapp", function () {
web3.eth.getAccounts().then((acc) => { web3.eth.getAccounts().then((acc) => {
accounts = acc; accounts = acc;
return SNT.methods.generateTokens(accounts[0], 123456).send() return SNT.methods.generateTokens(accounts[0], 6).send()
}).then((receipt) => { }).then((receipt) => {
return SNT.methods.generateTokens(accounts[1], 789012).send() return SNT.methods.generateTokens(accounts[1], 12).send()
}).then((receipt) => { }).then((receipt) => {
return SNT.methods.generateTokens(accounts[2], 345678).send() return SNT.methods.generateTokens(accounts[2], 10).send()
}).then((receipt) => { }).then((receipt) => {
return SNT.methods.generateTokens(accounts[3], 901234).send() return SNT.methods.generateTokens(accounts[3], 7).send()
}).then((receipt) => { }).then((receipt) => {
done(); done();
}); });
@ -84,7 +84,6 @@ describe("VotingDapp", function () {
receipt = await PollManager.methods.addPoll( receipt = await PollManager.methods.addPoll(
blockNumber, blockNumber,
blockNumber + 10, blockNumber + 10,
SingleChoiceFactory.options.address,
question) question)
.send({from: accounts[8]}); .send({from: accounts[8]});
assert.fail('should have reverted before'); assert.fail('should have reverted before');
@ -99,7 +98,6 @@ describe("VotingDapp", function () {
receipt = await PollManager.methods.addPoll( receipt = await PollManager.methods.addPoll(
blockNumber, blockNumber,
blockNumber + 10, blockNumber + 10,
SingleChoiceFactory.options.address,
question) question)
.send({from: accounts[0]}); .send({from: accounts[0]});
@ -150,18 +148,25 @@ describe("VotingDapp", function () {
poll = await PollManager.methods.poll(pollId).call(); poll = await PollManager.methods.poll(pollId).call();
let votersByBallotYES = await PollManager.methods.getVotesByBallot(pollId, Yes).call(); let votersByBallotYES = await PollManager.methods.getVotesByBallot(pollId, Yes).call();
let tokenVotesByBallotYES = await pollContract.methods.result(0).call(); // 0 == Yes (because it is the initial option ) let tokenVotesByBallotYES = await pollContract.methods.result(0).call(); // 0 == Yes (because it is the initial option )
let quadraticVotesByBallotYES = await pollContract.methods.qvResult(0).call(); // 0 == Yes (because it is the initial option )
// console.dir(poll); // Will contain state of the poll
// console.log(tokenVotesByBallotYES); // Contains how many votes has a ballot
// console.log(votersByBallotYES); // Contains how many voters voted for that option
// Will contain state of the poll
// console.dir(poll);
// Contains how many votes has a ballot
// console.log(tokenVotesByBallotYES);
// Contains how many votes has a ballot using quadratic voting
//console.log(quadraticVotesByBallotYES);
// Contains how many voters voted for that option
// console.log(votersByBallotYES);
// =================================================== // ===================================================
// Unvote // Unvote
receipt = await PollManager.methods.unvote(pollId).send({from: accounts[0]}); receipt = await PollManager.methods.unvote(pollId).send({from: accounts[0]});
assert.equal(!!receipt.events.Unvote, true, "Unvote not triggered"); assert.equal(!!receipt.events.Unvote, true, "Unvote not triggered");
}); });
}); });