Skip to content

Commit

Permalink
Add support for vote actions
Browse files Browse the repository at this point in the history
  • Loading branch information
kronosapiens committed Jun 20, 2019
1 parent 7cf2e2c commit c254d5b
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 208 deletions.
77 changes: 72 additions & 5 deletions contracts/extensions/VotingBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,58 @@ contract VotingBase is DSMath {
}

// Data structures
enum PollState { Open, Reveal, Closed }
enum PollState { Pending, Open, Reveal, Closed, Executed }

struct Poll {
bool executed;
address creator;
uint256 pollCloses;
uint256[] voteCounts;
}

// Storage
uint256 pollCount;
mapping (uint256 => Poll) polls;
mapping (uint256 => mapping (uint256 => bytes)) pollActions;

// Modifiers
modifier onlyCreator(uint256 _pollId) {
require(polls[_pollId].creator == msg.sender, "voting-base-only-creator");
_;
}

modifier pollPending(uint256 _pollId) {
require(getPollState(_pollId) == PollState.Pending, "voting-base-poll-not-pending");
_;
}

// Functions
function createPoll() public {
pollCount += 1;
polls[pollCount].creator = msg.sender;
}

function addPollAction(uint256 _pollId, bytes memory _action) public onlyCreator(_pollId) pollPending(_pollId) {
pollActions[_pollId][polls[_pollId].voteCounts.length] = _action;
polls[_pollId].voteCounts.push(0);
}

function openPoll(uint256 _pollId, uint256 _duration) public onlyCreator(_pollId) pollPending(_pollId) {
require(polls[_pollId].voteCounts.length > 1, "voting-base-insufficient-poll-actions");
polls[_pollId].pollCloses = add(now, _duration);
}

function executePoll(uint256 _pollId) public returns (bool) {
require(getPollState(_pollId) != PollState.Executed, "voting-base-poll-already-executed");
require(getPollState(_pollId) == PollState.Closed, "voting-base-poll-not-closed");

polls[_pollId].executed = true;

uint256 winner = getPollWinner(_pollId);
bytes storage action = pollActions[_pollId][winner];
return executeCall(address(colony), action);
}

function getPollCount() public view returns (uint256) {
return pollCount;
}
Expand All @@ -58,13 +98,40 @@ contract VotingBase is DSMath {
poll = polls[_pollId];
}

function getPollState(uint256 _pollId) internal view returns (PollState) {
if (now < polls[_pollId].pollCloses) {
function getPollState(uint256 _pollId) public view returns (PollState) {
Poll storage poll = polls[_pollId];
if (poll.pollCloses == 0) {
return PollState.Pending;
} else if (now < poll.pollCloses) {
return PollState.Open;
} else if (now < add(polls[_pollId].pollCloses, REVEAL_PERIOD)) {
} else if (now < add(poll.pollCloses, REVEAL_PERIOD)) {
return PollState.Reveal;
} else {
} else if (!poll.executed) {
return PollState.Closed;
} else {
return PollState.Executed;
}
}

function getPollWinner(uint256 _pollId) public view returns (uint256 winner) {
uint256[] storage voteCounts = polls[_pollId].voteCounts;

// TODO: Handle ties!
for (uint256 i; i < voteCounts.length; i += 1) {
if (voteCounts[i] > voteCounts[winner]) {
winner = i;
}
}
}

function executeCall(address to, bytes memory data) internal returns (bool success) {
assembly {
// call contract at address a with input mem[in…(in+insize))
// providing g gas and v wei and output area mem[out…(out+outsize))
// returning 0 on error (eg. out of gas) and 1 on success

// call(g, a, v, in, insize, out, outsize)
success := call(gas, to, 0, add(data, 0x20), mload(data), 0, 0)
}
}

Expand Down
39 changes: 18 additions & 21 deletions contracts/extensions/VotingReputation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,32 @@ contract VotingReputation is VotingBase, PatriciaTreeProofs {

constructor(address _colony) public VotingBase(_colony) {}

struct RepDatum {
struct RepInfo {
bytes32 rootHash;
uint256 skillId;
}

mapping (uint256 => RepDatum) repData;
mapping (uint256 => RepInfo) repInfo;

// The UserVote type here is just the bytes32 voteSecret

mapping (address => mapping (uint256 => bytes32)) userVotes;

function createPoll(uint256 _numOutcomes, uint256 _duration, uint256 _skillId) public {
pollCount += 1;

polls[pollCount] = Poll({
pollCloses: add(now, _duration),
voteCounts: new uint256[](_numOutcomes)
});

repData[pollCount] = RepDatum({
function setPollRepInfo(uint256 _pollId, uint256 _skillId) public onlyCreator(_pollId) pollPending(_pollId) {
repInfo[pollCount] = RepInfo({
rootHash: colonyNetwork.getReputationRootHash(),
skillId: _skillId
});
}

function submitVote(uint256 _pollId, bytes32 _voteSecret) public {
require(getPollState(_pollId) == PollState.Open, "colony-rep-voting-poll-not-open");
// Override the base function
function openPoll(uint256 _pollId, uint256 _duration) public onlyCreator(_pollId) pollPending(_pollId) {
require(repInfo[_pollId].rootHash != 0x0, "voting-rep-poll-no-root-hash");
super.openPoll(_pollId, _duration);
}

function submitVote(uint256 _pollId, bytes32 _voteSecret) public {
require(getPollState(_pollId) == PollState.Open, "voting-rep-poll-not-open");
userVotes[msg.sender][_pollId] = _voteSecret;
}

Expand All @@ -68,12 +66,11 @@ contract VotingReputation is VotingBase, PatriciaTreeProofs {
)
public
{
uint256 pollCloses = polls[_pollId].pollCloses;
require(getPollState(_pollId) != PollState.Open, "colony-rep-voting-poll-still-open");
require(getPollState(_pollId) != PollState.Open, "voting-rep-poll-still-open");

bytes32 voteSecret = userVotes[msg.sender][_pollId];
require(voteSecret == getVoteSecret(_salt, _vote), "colony-rep-voting-secret-no-match");
require(_vote < polls[_pollId].voteCounts.length, "colony-rep-voting-invalid-vote");
require(voteSecret == getVoteSecret(_salt, _vote), "voting-rep-secret-no-match");
require(_vote < polls[_pollId].voteCounts.length, "voting-rep-invalid-vote");

// Validate proof and get reputation value
uint256 userReputation = checkReputation(_pollId, _key, _value, _branchMask, _siblings);
Expand All @@ -98,7 +95,7 @@ contract VotingReputation is VotingBase, PatriciaTreeProofs {
internal view returns (uint256)
{
bytes32 impliedRoot = getImpliedRootHashKey(_key, _value, _branchMask, _siblings);
require(repData[_pollId].rootHash == impliedRoot, "colony-rep-voting-invalid-root-hash");
require(repInfo[_pollId].rootHash == impliedRoot, "voting-rep-invalid-root-hash");

uint256 reputationValue;
address keyColonyAddress;
Expand All @@ -112,9 +109,9 @@ contract VotingReputation is VotingBase, PatriciaTreeProofs {
keyUserAddress := mload(add(_key, 72))
}

require(keyColonyAddress == address(colony), "colony-rep-voting-invalid-colony-address");
require(keySkill == repData[_pollId].skillId, "colony-rep-voting-invalid-skill-id");
require(keyUserAddress == msg.sender, "colony-rep-voting-invalid-user-address");
require(keyColonyAddress == address(colony), "voting-rep-invalid-colony-address");
require(keySkill == repInfo[_pollId].skillId, "voting-rep-invalid-skill-id");
require(keyUserAddress == msg.sender, "voting-rep-invalid-user-address");

return reputationValue;
}
Expand Down
9 changes: 0 additions & 9 deletions contracts/extensions/VotingToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,6 @@ contract VotingToken is VotingBase {

mapping (address => mapping (uint256 => UserVote)) userVotes;

function createPoll(uint256 _numOutcomes, uint256 _duration) public {
pollCount += 1;

polls[pollCount] = Poll({
pollCloses: add(now, _duration),
voteCounts: new uint256[](_numOutcomes)
});
}

// TODO: Implement inner linked list
function submitVote(uint256 _pollId, bytes32 _voteSecret, uint256 _prevPollCloses) public {
require(getPollState(_pollId) == PollState.Open, "colony-token-voting-poll-not-open");
Expand Down
20 changes: 3 additions & 17 deletions helpers/task-review-signing.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { soliditySha3, padLeft, isBN } from "web3-utils";
import { soliditySha3, padLeft } from "web3-utils";
import { hashPersonalMessage, ecsign } from "ethereumjs-util";
import fs from "fs";
import { ethers } from "ethers";
import { BigNumber } from "bignumber.js";
import { encodeTxData } from "./test-helper";

export async function executeSignedTaskChange({ colony, taskId, functionName, signers, privKeys, sigTypes, args }) {
const { sigV, sigR, sigS, txData } = await getSigsAndTransactionData({ colony, taskId, functionName, signers, privKeys, sigTypes, args });
Expand All @@ -18,21 +18,7 @@ export async function getSigsAndTransactionData({ colony, taskId, functionName,
// We have to pass in an ethers BN because of https://github.com/ethereum/web3.js/issues/1920
// and https://github.com/ethereum/web3.js/issues/2077
const ethersBNTaskId = ethers.utils.bigNumberify(taskId.toString());
const convertedArgs = [];
args.forEach(arg => {
if (Number.isInteger(arg)) {
const convertedArg = ethers.utils.bigNumberify(arg);
convertedArgs.push(convertedArg);
} else if (isBN(arg) || BigNumber.isBigNumber(arg)) {
// Can use isBigNumber from utils once https://github.com/ethereum/web3.js/issues/2835 sorted
const convertedArg = ethers.utils.bigNumberify(arg.toString());
convertedArgs.push(convertedArg);
} else {
convertedArgs.push(arg);
}
});

const txData = await colony.contract.methods[functionName](...convertedArgs).encodeABI();
const txData = await encodeTxData(colony, functionName, args);
const sigsPromises = sigTypes.map((type, i) => {
let privKey = [];
if (privKeys) {
Expand Down
23 changes: 22 additions & 1 deletion helpers/test-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
/* eslint-disable no-console */
import shortid from "shortid";
import chai from "chai";
import { asciiToHex } from "web3-utils";
import { asciiToHex, isBN } from "web3-utils";
import BN from "bn.js";
import { ethers } from "ethers";
import { BigNumber } from "bignumber.js";

import { UINT256_MAX, MIN_STAKE, MINING_CYCLE_DURATION, DEFAULT_STAKE } from "./constants";

Expand Down Expand Up @@ -710,3 +712,22 @@ export async function getColonyEditable(colony, colonyNetwork) {
const colonyUnderRecovery = await ContractEditing.at(colony.address);
return colonyUnderRecovery;
}

export async function encodeTxData(colony, functionName, args) {
const convertedArgs = [];
args.forEach(arg => {
if (Number.isInteger(arg)) {
const convertedArg = ethers.utils.bigNumberify(arg);
convertedArgs.push(convertedArg);
} else if (isBN(arg) || BigNumber.isBigNumber(arg)) {
// Can use isBigNumber from utils once https://github.com/ethereum/web3.js/issues/2835 sorted
const convertedArg = ethers.utils.bigNumberify(arg.toString());
convertedArgs.push(convertedArg);
} else {
convertedArgs.push(arg);
}
});

const txData = await colony.contract.methods[functionName](...convertedArgs).encodeABI();
return txData;
}
Loading

0 comments on commit c254d5b

Please sign in to comment.