From 37595226339d33d39146473f952ff0cf10becd36 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Tue, 26 Jun 2018 13:34:24 -0400 Subject: [PATCH] Created test case to demonstrate how to use contracts --- config/namesystem.js | 7 + contracts/polls/PollManager.sol | 47 ++++++- contracts/polls/SingleChoice.sol | 12 +- contracts/polls/SingleChoiceFactory.sol | 3 +- package.json | 4 +- test/votingdapp.js | 168 ++++++++++++++++++++++++ 6 files changed, 227 insertions(+), 14 deletions(-) create mode 100644 config/namesystem.js create mode 100644 test/votingdapp.js diff --git a/config/namesystem.js b/config/namesystem.js new file mode 100644 index 0000000..8305df8 --- /dev/null +++ b/config/namesystem.js @@ -0,0 +1,7 @@ +module.exports = { + default: { + enabled: false, + available_providers: ["ens"], + provider: "ens" + } +}; diff --git a/contracts/polls/PollManager.sol b/contracts/polls/PollManager.sol index 4fa2bf0..3595f0d 100644 --- a/contracts/polls/PollManager.sol +++ b/contracts/polls/PollManager.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.11; +pragma solidity ^0.4.23; import "../common/Controlled.sol"; import "./LowLevelStringManipulator.sol"; @@ -27,6 +27,8 @@ contract PollManager is LowLevelStringManipulator, Controlled { address token; address pollContract; bool canceled; + uint voters; + mapping(bytes32 => uint) votersPerBallot; mapping(address => VoteLog) votes; } @@ -62,10 +64,10 @@ contract PollManager is LowLevelStringManipulator, Controlled { Poll p = _polls[ _idPoll ]; p.startBlock = _startBlock; p.endBlock = _endBlock; + p.voters = 0; - - var (name,symbol) = getTokenNameSymbol(address(token)); - string memory proposalName = strConcat(name , "_", uint2str(_idPoll)); + var (name, symbol) = getTokenNameSymbol(address(token)); + string memory proposalName = strConcat(name, "_", uint2str(_idPoll)); string memory proposalSymbol = strConcat(symbol, "_", uint2str(_idPoll)); p.token = tokenFactory.createCloneToken( @@ -80,6 +82,8 @@ contract PollManager is LowLevelStringManipulator, Controlled { p.pollContract = IPollFactory(_pollFactory).create(_description); if (p.pollContract == 0) throw; + + emit PollCreated(_idPoll); } function cancelPoll(uint _idPoll) onlyController { @@ -90,6 +94,18 @@ contract PollManager is LowLevelStringManipulator, Controlled { PollCanceled(_idPoll); } + function canVote(uint _idPoll) public view returns(bool) { + if(_idPoll >= _polls.length) return false; + + Poll storage p = _polls[_idPoll]; + uint balance = MiniMeToken(p.token).balanceOf(msg.sender); + + return block.number >= p.startBlock && + block.number <= p.endBlock && + !p.canceled && + balance != 0; + } + function vote(uint _idPoll, bytes32 _ballot) { if (_idPoll >= _polls.length) throw; Poll p = _polls[_idPoll]; @@ -110,6 +126,10 @@ contract PollManager is LowLevelStringManipulator, Controlled { p.votes[msg.sender].ballot = _ballot; p.votes[msg.sender].amount = amount; + + p.voters++; + + p.votersPerBallot[_ballot]++; if (!IPollContract(p.pollContract).deltaVote(int(amount), _ballot)) throw; @@ -129,9 +149,11 @@ contract PollManager is LowLevelStringManipulator, Controlled { if (!IPollContract(p.pollContract).deltaVote(-int(amount), ballot)) throw; - p.votes[msg.sender].ballot = 0; p.votes[msg.sender].amount = 0; + p.votersPerBallot[ballot]--; + + p.voters--; // enableTransfers = true; if (!MiniMeToken(p.token).transferFrom(address(this), msg.sender, amount)) throw; @@ -155,7 +177,8 @@ contract PollManager is LowLevelStringManipulator, Controlled { bytes32 _pollType, string _question, bool _finalized, - uint _totalCensus + uint _totalCensus, + uint _voters ) { if (_idPoll >= _polls.length) throw; Poll p = _polls[_idPoll]; @@ -168,6 +191,7 @@ contract PollManager is LowLevelStringManipulator, Controlled { _question = getString(p.pollContract, bytes4(sha3("question()"))); _finalized = (!p.canceled) && (getBlockNumber() >= _endBlock); _totalCensus = MiniMeToken(p.token).totalSupply(); + _voters = p.voters; } function getVote(uint _idPoll, address _voter) constant returns (bytes32 _ballot, uint _amount) { @@ -178,6 +202,16 @@ contract PollManager is LowLevelStringManipulator, Controlled { _amount = p.votes[_voter].amount; } + function getVotesByBallot(uint _idPoll, bytes32 _ballot) + public view returns(uint voters, uint votes) { + if (_idPoll >= _polls.length) throw; + Poll storage p = _polls[_idPoll]; + + voters = p.votersPerBallot[_ballot]; + votes = p.votersPerBallot[_ballot]; + + } + function proxyPayment(address ) payable returns(bool) { return false; } @@ -199,6 +233,7 @@ contract PollManager is LowLevelStringManipulator, Controlled { 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 PollCanceled(uint indexed idPoll); + event PollCreated(uint indexed idPoll); diff --git a/contracts/polls/SingleChoice.sol b/contracts/polls/SingleChoice.sol index aac346a..2cbcb57 100644 --- a/contracts/polls/SingleChoice.sol +++ b/contracts/polls/SingleChoice.sol @@ -26,7 +26,7 @@ contract SingleChoice is Controlled { using RLP for bytes; string public question; - string[] public options; + string[] public choices; int[] public result; bytes32 uid; @@ -50,11 +50,11 @@ contract SingleChoice is Controlled { var itrOptions = itmOptions.iterator(); while(itrOptions.hasNext()) { - options.length++; - options[options.length-1] = itrOptions.next().toAscii(); + choices.length++; + choices[choices.length-1] = itrOptions.next().toAscii(); } - result.length = options.length; + result.length = choices.length; } function pollType() constant returns (bytes32) { @@ -63,7 +63,7 @@ contract SingleChoice is Controlled { function isValid(bytes32 _ballot) constant returns(bool) { uint v = uint(_ballot) / (2**248); - if (v>=options.length) return false; + if (v>=choices.length) return false; if (getBallot(v) != _ballot) return false; return true; } @@ -76,7 +76,7 @@ contract SingleChoice is Controlled { } function nOptions() constant returns(uint) { - return options.length; + return choices.length; } function getBallot(uint _option) constant returns(bytes32) { diff --git a/contracts/polls/SingleChoiceFactory.sol b/contracts/polls/SingleChoiceFactory.sol index 31d0799..864e4d8 100644 --- a/contracts/polls/SingleChoiceFactory.sol +++ b/contracts/polls/SingleChoiceFactory.sol @@ -1,8 +1,9 @@ pragma solidity ^0.4.6; import "./SingleChoice.sol"; +import "./PollManager.sol"; -contract SingleChoiceFactory { +contract SingleChoiceFactory is IPollFactory { uint salt; function create(bytes _description) returns(address) { salt++; diff --git a/package.json b/package.json index fc5afc3..0461ff5 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,12 @@ "bignumber.js": "^5.0.0", "formik": "^0.11.11", "jquery": "^3.3.1", + "lodash": "^4.17.10", "react": "^16.3.2", "react-blockies": "^1.3.0", "react-bootstrap": "^0.32.1", "react-dom": "^16.3.2", - "react-toggle": "^4.0.2" + "react-toggle": "^4.0.2", + "rlp": "^2.0.0" } } diff --git a/test/votingdapp.js b/test/votingdapp.js new file mode 100644 index 0000000..da17144 --- /dev/null +++ b/test/votingdapp.js @@ -0,0 +1,168 @@ +const utils = require('../utils/testUtils') +const assert = require('assert'); +const SingleChoice = require('Embark/contracts/SingleChoice'); +const BN = web3.utils.BN; +var _ = require('lodash'); +var rlp = require('rlp'); + +config({ + contracts: { + "MiniMeTokenFactory": { + "gasLimit": 4000000 + }, + "MiniMeToken": { + "deploy": false, + }, + "SNT":{ + "instanceOf": "MiniMeToken", + "args": [ + "$MiniMeTokenFactory", + utils.zeroAddress, + 0, + "TestMiniMeToken", + 18, + "TST", + true + ], + "gasLimit": 4000000 + }, + "PollManager": { + "deploy": true, + "args": ["$MiniMeTokenFactory", "$SNT"] + }, + "SingleChoiceFactory": { + "deploy": true + } + } +}); + +singleChoiceDef = (question, options) => { + var d = [ + new Buffer(question), + _.map(options, function(o) { + return new Buffer(o); + }) + ]; + + var b= rlp.encode(d); + var rlpDefinition = '0x' + b.toString('hex'); + + return rlpDefinition; +} + +describe("VotingDapp", function () { + this.timeout(0); + + let accounts; + + before(function(done) { + + web3.eth.getAccounts().then((acc) => { + accounts = acc; + return SNT.methods.generateTokens(accounts[0], 123456).send() + }).then((receipt) => { + return SNT.methods.generateTokens(accounts[1], 789012).send() + }).then((receipt) => { + return SNT.methods.generateTokens(accounts[2], 345678).send() + }).then((receipt) => { + return SNT.methods.generateTokens(accounts[3], 901234).send() + }).then((receipt) => { + done(); + }); + }); + + it("Test", async () => { + + const blockNumber = await web3.eth.getBlockNumber(); + const question = singleChoiceDef("Move from Slack to Status Desktop", [ "Yes", "No" ]); + let receipt; + + + // =================================================== + // Creating a proposal without holding SNT SHOULD FAIL! + try { + receipt = await PollManager.methods.addPoll( + blockNumber, + blockNumber + 10, + SingleChoiceFactory.options.address, + question) + .send({from: accounts[8]}); + assert.fail('should have reverted before'); + } catch(error) { + utils.assertJump(error); + } + + + // =================================================== + // Creating a proposal as a SNT holder + + receipt = await PollManager.methods.addPoll( + blockNumber, + blockNumber + 10, + SingleChoiceFactory.options.address, + question) + .send({from: accounts[0]}); + + assert.equal(!!receipt.events.PollCreated, true, "PollCreated not triggered"); + + const pollId = receipt.events.PollCreated.returnValues.idPoll; + let poll = await PollManager.methods.poll(pollId).call(); + + SingleChoice.options.address = poll._pollContract; + const pollContract = SingleChoice; + + // Options are represented as a hex value + const Yes = await SingleChoice.methods.getBallot(0).call(); + const No = await SingleChoice.methods.getBallot(1).call(); + + + // =================================================== + // Determining if I can vote por a proposal + let canVote = await PollManager.methods.canVote(pollId).call({from: accounts[0]}); + assert.equal(canVote, true, "User should be able to vote"); + + + // =================================================== + // Voting + receipt = await PollManager.methods.vote(pollId, Yes).send({from: accounts[0]}); + assert.equal(!!receipt.events.Vote, true, "Vote not triggered"); + + + // =================================================== + // Getting what option the voter selected + let myVote = await PollManager.methods.getVote(pollId, accounts[0]).call(); + assert.equal(myVote._ballot, Yes, "Vote is different from selected"); + + + // =================================================== + // Voting when you're not a SNT holder SHOULD FAIL! + try { + receipt = await PollManager.methods.vote(pollId, Yes) + .send({from: accounts[8]}); + assert.fail('should have reverted before'); + } catch(error) { + utils.assertJump(error); + } + + + // =================================================== + // Getting proposal information + poll = await PollManager.methods.poll(pollId).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 ) + + // 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 + + + // =================================================== + // Unvote + receipt = await PollManager.methods.unvote(pollId).send({from: accounts[0]}); + assert.equal(!!receipt.events.Unvote, true, "Unvote not triggered"); + + + }); + +}); +