diff --git a/contracts/extensions/VotingBase.sol b/contracts/extensions/VotingBase.sol
new file mode 100644
index 0000000000..6400876580
--- /dev/null
+++ b/contracts/extensions/VotingBase.sol
@@ -0,0 +1,57 @@
+/*
+ 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 .
+*/
+
+pragma solidity 0.5.8;
+pragma experimental ABIEncoderV2;
+
+import "../../lib/dappsys/math.sol";
+import "../IColony.sol";
+import "../IColonyNetwork.sol";
+
+
+contract VotingBase is DSMath {
+
+ uint64 constant REVEAL_PERIOD = 2 days;
+
+ enum PollState { Open, Reveal, Closed }
+
+ IColony colony;
+ IColonyNetwork colonyNetwork;
+
+ constructor(address _colony) public {
+ colony = IColony(_colony);
+ colonyNetwork = IColonyNetwork(colony.getColonyNetwork());
+ }
+
+ function getVoteSecret(bytes32 _salt, uint64 _vote) public pure returns (bytes32) {
+ return keccak256(abi.encodePacked(_salt, _vote));
+ }
+
+ function getPollState(uint64 _pollCloseTime) public view returns (PollState) {
+ if (_pollCloseTime < uint64(now)) {
+ return PollState.Open;
+ } else if (add64(_pollCloseTime, REVEAL_PERIOD) < uint64(now)) {
+ return PollState.Reveal;
+ } else {
+ return PollState.Closed;
+ }
+ }
+
+ function add64(uint64 x, uint64 y) internal pure returns (uint64 z) {
+ require((z = x + y) >= x, "voting-base-add64-overflow");
+ }
+}
diff --git a/contracts/extensions/VotingReputation.sol b/contracts/extensions/VotingReputation.sol
new file mode 100644
index 0000000000..3be0c19c56
--- /dev/null
+++ b/contracts/extensions/VotingReputation.sol
@@ -0,0 +1,119 @@
+/*
+ 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 .
+*/
+
+pragma solidity 0.5.8;
+pragma experimental ABIEncoderV2;
+
+import "../PatriciaTree/PatriciaTreeProofs.sol";
+import "./VotingBase.sol";
+
+
+contract VotingReputation is VotingBase, PatriciaTreeProofs {
+
+ struct Poll {
+ uint64 numOutcomes;
+ uint64 pollCloses;
+ bytes32 rootHash;
+ uint256 skillId;
+ mapping (uint64 => uint256) voteCounts;
+ }
+
+ // The UserVote type here is just the bytes32 voteSecret
+
+ uint256 pollCount;
+ mapping (uint256 => Poll) polls;
+ mapping (address => mapping (uint256 => bytes32)) userVotes;
+
+ function createPoll(uint64 _numOutcomes, uint64 _duration, uint256 _skillId) public {
+ pollCount += 1;
+ polls[pollCount] = Poll({
+ numOutcomes: _numOutcomes,
+ pollCloses: add64(uint64(now), _duration),
+ rootHash: colonyNetwork.getReputationRootHash(),
+ skillId: _skillId
+ });
+ }
+
+ function submitVote(uint256 _pollId, bytes32 _voteSecret) public {
+ uint64 pollCloses = polls[_pollId].pollCloses;
+ require(getPollState(pollCloses) == PollState.Open, "colony-rep-voting-poll-not-open");
+
+ userVotes[msg.sender][_pollId] = _voteSecret;
+ }
+
+ function revealVote(
+ uint256 _pollId,
+ bytes32 _salt,
+ uint64 _vote,
+ bytes memory _key,
+ bytes memory _value,
+ uint256 _branchMask,
+ bytes32[] memory _siblings
+ )
+ public
+ {
+ uint64 pollCloses = polls[_pollId].pollCloses;
+ require(getPollState(pollCloses) != PollState.Open, "colony-rep-voting-poll-still-open");
+
+ bytes32 voteSecret = getVoteSecret(_salt, _vote);
+ require(userVotes[msg.sender][_pollId] == voteSecret, "colony-rep-voting-secret-no-match");
+
+ // 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(pollCloses) == 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(polls[_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 == polls[_pollId].skillId, "colony-rep-voting-invalid-skill-id");
+ require(keyUserAddress == msg.sender, "colony-rep-voting-invalid-user-address");
+
+ return reputationValue;
+ }
+
+}
diff --git a/contracts/extensions/VotingToken.sol b/contracts/extensions/VotingToken.sol
new file mode 100644
index 0000000000..c62e57cded
--- /dev/null
+++ b/contracts/extensions/VotingToken.sol
@@ -0,0 +1,115 @@
+/*
+ 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 .
+*/
+
+pragma solidity 0.5.8;
+pragma experimental ABIEncoderV2;
+
+import "../ITokenLocking.sol";
+import "./VotingBase.sol";
+
+
+contract VotingToken is VotingBase {
+
+ struct Poll {
+ uint64 numOutcomes;
+ uint64 pollCloses;
+ mapping (uint64 => uint256) voteCounts;
+ }
+
+ struct UserVote {
+ uint256 pollId;
+ bytes32 voteSecret;
+ uint64 prevPollCloses;
+ uint64 nextPollCloses;
+ }
+
+ uint256 pollCount;
+ mapping (uint256 => Poll) polls;
+ mapping (address => mapping (uint64 => UserVote)) userVotes;
+
+ function createPoll(uint64 _numOutcomes, uint64 _duration) public {
+ pollCount += 1;
+ polls[pollCount] = Poll({
+ numOutcomes: _numOutcomes,
+ pollCloses: add64(uint64(now), _duration)
+ });
+ }
+
+ // TODO: Implement inner linked list
+ function submitVote(uint256 _pollId, bytes32 _voteSecret, uint64 _prevPollCloses, uint256 _prevPollId) public {
+ uint64 pollCloses = polls[_pollId].pollCloses;
+ require(getPollState(pollCloses) == 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
+ 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, uint64 _vote) public {
+ uint64 pollCloses = polls[_pollId].pollCloses;
+ require(getPollState(pollCloses) != PollState.Open, "colony-token-voting-poll-still-open");
+
+ UserVote storage curr = userVotes[msg.sender][pollCloses];
+ UserVote storage prev = userVotes[msg.sender][curr.prevPollCloses];
+ UserVote storage next = userVotes[msg.sender][curr.nextPollCloses];
+
+ bytes32 voteSecret = getVoteSecret(_salt, _vote);
+ require(curr.voteSecret == voteSecret, "colony-token-voting-secret-no-match");
+
+ // 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(pollCloses) == 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) {
+ uint64 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;
+ }
+ }
+
+}