Skip to content

Commit

Permalink
Token and reputation voting
Browse files Browse the repository at this point in the history
  • Loading branch information
kronosapiens committed Jun 20, 2019
1 parent a57f682 commit 7cf2e2c
Show file tree
Hide file tree
Showing 8 changed files with 707 additions and 3 deletions.
74 changes: 74 additions & 0 deletions contracts/extensions/VotingBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
This file is part of The Colony Network.
The Colony Network is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The Colony Network is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with The Colony Network. If not, see <http://www.gnu.org/licenses/>.
*/

pragma solidity 0.5.8;
pragma experimental ABIEncoderV2;

import "../../lib/dappsys/math.sol";
import "../IColony.sol";
import "../IColonyNetwork.sol";


contract VotingBase is DSMath {

// Constants
uint256 constant REVEAL_PERIOD = 2 days;

// Initialization data
IColony colony;
IColonyNetwork colonyNetwork;

constructor(address _colony) public {
colony = IColony(_colony);
colonyNetwork = IColonyNetwork(colony.getColonyNetwork());
}

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

struct Poll {
uint256 pollCloses;
uint256[] voteCounts;
}

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

// Functions
function getPollCount() public view returns (uint256) {
return pollCount;
}

function getPollInfo(uint256 _pollId) public view returns (Poll memory poll) {
poll = polls[_pollId];
}

function getPollState(uint256 _pollId) internal view returns (PollState) {
if (now < polls[_pollId].pollCloses) {
return PollState.Open;
} else if (now < add(polls[_pollId].pollCloses, REVEAL_PERIOD)) {
return PollState.Reveal;
} else {
return PollState.Closed;
}
}

function getVoteSecret(bytes32 _salt, uint256 _vote) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(_salt, _vote));
}
}
122 changes: 122 additions & 0 deletions contracts/extensions/VotingReputation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
This file is part of The Colony Network.
The Colony Network is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The Colony Network is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with The Colony Network. If not, see <http://www.gnu.org/licenses/>.
*/

pragma solidity 0.5.8;
pragma experimental ABIEncoderV2;

import "../PatriciaTree/PatriciaTreeProofs.sol";
import "./VotingBase.sol";


contract VotingReputation is VotingBase, PatriciaTreeProofs {

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

struct RepDatum {
bytes32 rootHash;
uint256 skillId;
}

mapping (uint256 => RepDatum) repData;

// 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({
rootHash: colonyNetwork.getReputationRootHash(),
skillId: _skillId
});
}

function submitVote(uint256 _pollId, bytes32 _voteSecret) public {
require(getPollState(_pollId) == PollState.Open, "colony-rep-voting-poll-not-open");

userVotes[msg.sender][_pollId] = _voteSecret;
}

function revealVote(
uint256 _pollId,
bytes32 _salt,
uint256 _vote,
bytes memory _key,
bytes memory _value,
uint256 _branchMask,
bytes32[] memory _siblings
)
public
{
uint256 pollCloses = polls[_pollId].pollCloses;
require(getPollState(_pollId) != PollState.Open, "colony-rep-voting-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");

// Validate proof and get reputation value
uint256 userReputation = checkReputation(_pollId, _key, _value, _branchMask, _siblings);

// Remove the secret
delete userVotes[msg.sender][_pollId];

// Increment the vote if poll in reveal, otherwise skip
// NOTE: since there's no locking, we could just `require` PollState.Reveal
if (getPollState(_pollId) == PollState.Reveal) {
polls[_pollId].voteCounts[_vote] += userReputation;
}
}

function checkReputation(
uint256 _pollId,
bytes memory _key,
bytes memory _value,
uint256 _branchMask,
bytes32[] memory _siblings
)
internal view returns (uint256)
{
bytes32 impliedRoot = getImpliedRootHashKey(_key, _value, _branchMask, _siblings);
require(repData[_pollId].rootHash == impliedRoot, "colony-rep-voting-invalid-root-hash");

uint256 reputationValue;
address keyColonyAddress;
uint256 keySkill;
address keyUserAddress;

assembly {
reputationValue := mload(add(_value, 32))
keyColonyAddress := mload(add(_key, 20))
keySkill := mload(add(_key, 52))
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");

return reputationValue;
}

}
110 changes: 110 additions & 0 deletions contracts/extensions/VotingToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
This file is part of The Colony Network.
The Colony Network is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The Colony Network is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with The Colony Network. If not, see <http://www.gnu.org/licenses/>.
*/

pragma solidity 0.5.8;
pragma experimental ABIEncoderV2;

import "../ITokenLocking.sol";
import "./VotingBase.sol";


contract VotingToken is VotingBase {

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

struct UserVote {
uint256 pollId;
bytes32 voteSecret;
uint256 prevPollCloses;
uint256 nextPollCloses;
}

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");

UserVote storage prev = userVotes[msg.sender][_prevPollCloses];
UserVote storage next = userVotes[msg.sender][prev.nextPollCloses];

// Check we are inserting at the correct location
uint256 pollCloses = polls[_pollId].pollCloses;
require(pollCloses > _prevPollCloses, "colony-token-voting-insert-too-soon");
require(pollCloses < prev.nextPollCloses || prev.nextPollCloses == 0, "colony-token-voting-insert-too-late");

userVotes[msg.sender][pollCloses] = UserVote({
pollId: _pollId,
voteSecret: _voteSecret,
prevPollCloses: _prevPollCloses,
nextPollCloses: prev.nextPollCloses
});

prev.nextPollCloses = pollCloses;
next.prevPollCloses = pollCloses;
}

function revealVote(uint256 _pollId, bytes32 _salt, uint256 _vote) public {
require(getPollState(_pollId) != PollState.Open, "colony-token-voting-poll-still-open");

uint256 pollCloses = polls[_pollId].pollCloses;
UserVote storage curr = userVotes[msg.sender][pollCloses];
UserVote storage prev = userVotes[msg.sender][curr.prevPollCloses];
UserVote storage next = userVotes[msg.sender][curr.nextPollCloses];

require(curr.voteSecret == getVoteSecret(_salt, _vote), "colony-token-voting-secret-no-match");
require(_vote < polls[_pollId].voteCounts.length, "colony-token-voting-invalid-vote");

// Remove the secret
prev.nextPollCloses = curr.nextPollCloses;
next.prevPollCloses = curr.prevPollCloses;
delete userVotes[msg.sender][pollCloses];

// Increment the vote if poll in reveal
if (getPollState(_pollId) == PollState.Reveal) {
address token = colony.getToken();
address tokenLocking = colonyNetwork.getTokenLocking();
uint256 userBalance = ITokenLocking(tokenLocking).getUserLock(token, msg.sender).balance;
polls[_pollId].voteCounts[_vote] += userBalance;
}
}

function isAddressLocked(address _address) public view returns (bool) {
uint256 nextPollCloses = userVotes[_address][0].nextPollCloses;
if (nextPollCloses == 0) {
// The list is empty, no unrevealed votes for this address
return false;
} else if (now < nextPollCloses) {
// The poll is still open for voting and tokens transfer
return false;
} else {
// The poll is closed for voting and is in the reveal period, during which all votes' tokens are locked until reveal
// Note: even after the poll is resolved, tokens remain locked until reveal
return true;
}
}

}
11 changes: 8 additions & 3 deletions helpers/test-data-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,13 +368,18 @@ export async function setupRandomColony(colonyNetwork) {
const token = await Token.new(...tokenArgs);
await token.unlock();

const { logs } = await colonyNetwork.createColony(token.address);
const { colonyAddress } = logs[0].args;
const colony = await IColony.at(colonyAddress);
const colony = await setupColony(colonyNetwork, token.address);
const tokenLockingAddress = await colonyNetwork.getTokenLocking();

const tokenAuthority = await TokenAuthority.new(token.address, colony.address, [tokenLockingAddress]);
await token.setAuthority(tokenAuthority.address);

return { colony, token };
}

export async function setupColony(colonyNetwork, tokenAddress) {
const { logs } = await colonyNetwork.createColony(tokenAddress);
const { colonyAddress } = logs[0].args;
const colony = await IColony.at(colonyAddress);
return colony;
}
3 changes: 3 additions & 0 deletions scripts/check-recovery.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ walkSync("./contracts/").forEach(contractName => {
"contracts/extensions/OldRolesFactory.sol",
"contracts/extensions/OneTxPayment.sol",
"contracts/extensions/OneTxPaymentFactory.sol",
"contracts/extensions/VotingBase.sol",
"contracts/extensions/VotingReputation.sol",
"contracts/extensions/VotingToken.sol",
"contracts/gnosis/MultiSigWallet.sol",
"contracts/PatriciaTree/Bits.sol",
"contracts/PatriciaTree/Data.sol",
Expand Down
3 changes: 3 additions & 0 deletions scripts/check-storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ walkSync("./contracts/").forEach(contractName => {
"contracts/extensions/OldRolesFactory.sol",
"contracts/extensions/OneTxPayment.sol",
"contracts/extensions/OneTxPaymentFactory.sol",
"contracts/extensions/VotingBase.sol",
"contracts/extensions/VotingReputation.sol",
"contracts/extensions/VotingToken.sol",
"contracts/CommonAuthority.sol",
"contracts/ColonyAuthority.sol",
"contracts/ColonyNetworkAuthority.sol",
Expand Down
Loading

0 comments on commit 7cf2e2c

Please sign in to comment.