Skip to content

Commit

Permalink
Redesign VotingHybrid to have single lifecycle
Browse files Browse the repository at this point in the history
  • Loading branch information
kronosapiens committed Apr 9, 2021
1 parent bef679f commit af7f93b
Show file tree
Hide file tree
Showing 7 changed files with 1,476 additions and 429 deletions.
209 changes: 159 additions & 50 deletions contracts/extensions/VotingBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ pragma solidity 0.7.3;
pragma experimental ABIEncoderV2;

import "./../colonyNetwork/IColonyNetwork.sol";
import "./../patriciaTree/PatriciaTreeProofs.sol";
import "./../tokenLocking/ITokenLocking.sol";
import "./ColonyExtension.sol";


abstract contract VotingBase is ColonyExtension {
abstract contract VotingBase is ColonyExtension, PatriciaTreeProofs {

// Events
event MotionCreated(uint256 indexed motionId, address creator, uint256 indexed domainId);
Expand Down Expand Up @@ -177,12 +178,12 @@ abstract contract VotingBase is ColonyExtension {
bytes32 rootHash;
uint256 domainId;
uint256 skillId;
uint256 maxVotes;
uint256 totalVotes;
uint256 paidVoterComp;
uint256[2] pastVoterComp; // [nay, yay]
uint256[2] stakes; // [nay, yay]
uint256[2] votes; // [nay, yay]
uint256[2][] votes; // [nay, yay]
uint256[] totalVotes;
uint256[] maxVotes;
bool escalated;
bool finalized;
address altTarget;
Expand All @@ -200,12 +201,9 @@ abstract contract VotingBase is ColonyExtension {

// Virtual functions

function getInfluence(uint256 _motionId, address _user) public view virtual returns (uint256);

function getTotalInfluence(uint256 _motionId) public view virtual returns (uint256);

function getInfluence(uint256 _motionId, address _user) public view virtual returns (uint256[] memory);
function getTotalInfluence(uint256 _motionId) public view virtual returns (uint256[] memory);
function postReveal(uint256 _motionId, address _user) internal virtual;

function postClaim(uint256 _motionId, address _user) internal virtual;

// Public functions (interface)
Expand All @@ -216,23 +214,33 @@ abstract contract VotingBase is ColonyExtension {
/// @param _vote The side being supported (0 = NAY, 1 = YAY)
function revealVote(uint256 _motionId, bytes32 _salt, uint256 _vote) public {
Motion storage motion = motions[_motionId];

require(getMotionState(_motionId) == MotionState.Reveal, "voting-base-motion-not-reveal");
require(_vote <= 1, "voting-base-bad-vote");

uint256 influence = getInfluence(_motionId, msg.sender);
motion.votes[_vote] = add(motion.votes[_vote], influence);
uint256[] memory influence = getInfluence(_motionId, msg.sender);
for (uint256 i; i < influence.length; i++) {
motion.votes[i][_vote] = add(motion.votes[i][_vote], influence[i]);
}

bytes32 voteSecret = voteSecrets[_motionId][msg.sender];
require(voteSecret == getVoteSecret(_salt, _vote), "voting-base-secret-no-match");
delete voteSecrets[_motionId][msg.sender];

uint256 voterReward = getVoterReward(_motionId, influence);
uint256 voterReward = getVoterReward(_motionId, msg.sender);
motion.paidVoterComp = add(motion.paidVoterComp, voterReward);

emit MotionVoteRevealed(_motionId, msg.sender, _vote);

// See if reputation revealed matches reputation submitted
if (add(motion.votes[NAY], motion.votes[YAY]) == motion.totalVotes) {
bool fullyRevealed = true;

for (uint256 j; j < influence.length; j++) {
fullyRevealed = fullyRevealed &&
add(motion.votes[j][NAY], motion.votes[j][YAY]) == motion.totalVotes[j];
}

if (fullyRevealed) {
motion.events[REVEAL_END] = uint64(block.timestamp);

emit MotionEventSet(_motionId, REVEAL_END);
Expand All @@ -245,20 +253,32 @@ abstract contract VotingBase is ColonyExtension {

function finalizeMotion(uint256 _motionId) public {
Motion storage motion = motions[_motionId];
require(getMotionState(_motionId) == MotionState.Finalizable, "voting-base-motion-not-finalizable");

assert(
motion.stakes[YAY] == getRequiredStake(_motionId) ||
add(motion.votes[NAY], motion.votes[YAY]) > 0
);
require(getMotionState(_motionId) == MotionState.Finalizable, "voting-base-motion-not-finalizable");

motion.finalized = true;

bool canExecute = (
motion.stakes[NAY] < motion.stakes[YAY] ||
motion.votes[NAY] < motion.votes[YAY]
);
uint256 sumVotes;
uint256 yayVotes;
for (uint256 i; i < motion.votes.length; i++) {
sumVotes = add(sumVotes, add(motion.votes[i][NAY], motion.votes[i][YAY]));
yayVotes = add(yayVotes, motion.votes[i][YAY]);
}

// Either we're fully staked YAY or we've gone to a vote
assert(motion.stakes[YAY] == getRequiredStake(_motionId) || sumVotes > 0);

bool canExecute = true;

// Check if every sub-vote passed
for (uint256 j; j < motion.votes.length; j++) {
canExecute = canExecute && motion.votes[j][NAY] < motion.votes[j][YAY];
}

// If not (i.e. no vote occurred), then we execute if only the YAY side fully staked
canExecute = canExecute || motion.stakes[NAY] < motion.stakes[YAY];

// Handle expenditure-related bookkeeping (claim delays, repeated vote checks)
if (getSig(motion.action) == CHANGE_FUNCTION && motion.altTarget == address(0x0)) {
bytes32 structHash = hashExpenditureActionStruct(motion.action);
expenditureMotionCounts[structHash] = sub(expenditureMotionCounts[structHash], 1);
Expand All @@ -271,8 +291,7 @@ abstract contract VotingBase is ColonyExtension {
}

bytes32 actionHash = hashExpenditureAction(motion.action);
uint256 votePower = (add(motion.votes[NAY], motion.votes[YAY]) > 0) ?
motion.votes[YAY] : motion.stakes[YAY];
uint256 votePower = (sumVotes > 0) ? yayVotes : motion.stakes[YAY];

if (expenditurePastVotes[actionHash] < votePower) {
expenditurePastVotes[actionHash] = votePower;
Expand Down Expand Up @@ -307,6 +326,7 @@ abstract contract VotingBase is ColonyExtension {
public
{
Motion storage motion = motions[_motionId];

require(
getMotionState(_motionId) == MotionState.Finalized ||
getMotionState(_motionId) == MotionState.Failed,
Expand Down Expand Up @@ -425,6 +445,7 @@ abstract contract VotingBase is ColonyExtension {
/// @return The current motion state
function getMotionState(uint256 _motionId) public view returns (MotionState) {
Motion storage motion = motions[_motionId];

uint256 requiredStake = getRequiredStake(_motionId);

// Check for valid motion Id
Expand All @@ -438,19 +459,19 @@ abstract contract VotingBase is ColonyExtension {
return MotionState.Finalized;

// Not fully staked
} else if (
motion.stakes[YAY] < requiredStake ||
motion.stakes[NAY] < requiredStake
) {
} else if (motion.stakes[YAY] < requiredStake || motion.stakes[NAY] < requiredStake) {

// Are we still staking?
if (block.timestamp < motion.events[STAKE_END]) {
return MotionState.Staking;
// If not, did the YAY side stake?
} else if (motion.stakes[YAY] == requiredStake) {
return MotionState.Finalizable;
// If not, was there a prior vote we can fall back on?
} else if (add(motion.votes[NAY], motion.votes[YAY]) > 0) {
// If not, was there a prior (reputation) vote we can fall back on?
} else if (
identifier() == keccak256("VotingReputation") &&
add(motion.votes[0][NAY], motion.votes[0][YAY]) > 0
) {
return MotionState.Finalizable;
// Otherwise, the motion failed
} else {
Expand All @@ -465,8 +486,8 @@ abstract contract VotingBase is ColonyExtension {
} else if (block.timestamp < motion.events[REVEAL_END]) {
return MotionState.Reveal;
} else if (
block.timestamp < motion.events[REVEAL_END] + escalationPeriod &&
motion.domainId > 1
motion.domainId > 1 &&
block.timestamp < motion.events[REVEAL_END] + escalationPeriod
) {
return MotionState.Closed;
} else {
Expand All @@ -478,12 +499,25 @@ abstract contract VotingBase is ColonyExtension {

/// @notice Get the voter reward
/// @param _motionId The id of the motion
/// @param _voterInfluence The influence the voter has in the domain
/// @param _user The address of the the voter
/// @return The voter reward
function getVoterReward(uint256 _motionId, uint256 _voterInfluence) public view returns (uint256) {
function getVoterReward(uint256 _motionId, address _user) public view returns (uint256) {
Motion storage motion = motions[_motionId];
uint256 totalInfluence = getTotalInfluence(_motionId);
uint256 fractionUserInfluence = wdiv(_voterInfluence, totalInfluence);

uint256[] memory influence = getInfluence(_motionId, _user);
uint256[] memory totalInfluence = getTotalInfluence(_motionId);
assert(influence.length == totalInfluence.length);

// Get the average per-influence fraction
uint256 fractionUserInfluence;

for (uint256 i; i < influence.length; i++) {
// TODO: Divide-by-zero ?
fractionUserInfluence = add(fractionUserInfluence, wdiv(influence[i], totalInfluence[i]));
}

fractionUserInfluence = fractionUserInfluence / influence.length;

uint256 totalStake = add(motion.stakes[YAY], motion.stakes[NAY]);
return wmul(wmul(fractionUserInfluence, totalStake), voterRewardFraction);
}
Expand All @@ -507,13 +541,25 @@ abstract contract VotingBase is ColonyExtension {
uint256 stakerReward;
uint256 repPenalty;

uint256 sumVotes;

for (uint256 i; i < motion.votes.length; i++) {
sumVotes = add(sumVotes, motion.totalVotes[i]);
}

// Went to a vote, use vote to determine reward or penalty
if (add(motion.votes[NAY], motion.votes[YAY]) > 0) {
if (sumVotes > 0) {

uint256 loserStake = sub(requiredStake, motion.paidVoterComp);
uint256 totalVotes = add(motion.votes[NAY], motion.votes[YAY]);
uint256 winFraction = wdiv(motion.votes[_vote], totalVotes);
uint256 winFraction;

for (uint256 j; j < motion.votes.length; j++) {
// TODO: divide-by-zero ??
winFraction = add(winFraction, wdiv(motion.votes[j][_vote], motion.totalVotes[j]));
}

winFraction = winFraction / motion.votes.length;
uint256 winShare = wmul(winFraction, 2 * WAD); // On a scale of 0-2 WAD
uint256 loserStake = sub(requiredStake, motion.paidVoterComp);

if (winShare > WAD || (winShare == WAD && _vote == NAY)) {
stakerReward = wmul(stakeFraction, add(requiredStake, wmul(loserStake, winShare - WAD)));
Expand Down Expand Up @@ -563,7 +609,8 @@ abstract contract VotingBase is ColonyExtension {
function createMotion(
address _altTarget,
bytes memory _action,
uint256 _domainId
uint256 _domainId,
uint256 _numInfluences
)
internal
notDeprecated
Expand All @@ -585,6 +632,10 @@ abstract contract VotingBase is ColonyExtension {
motion.altTarget = _altTarget;
motion.action = _action;

motion.votes = new uint256[2][](_numInfluences);
motion.totalVotes = new uint256[](_numInfluences);
motion.maxVotes = new uint256[](_numInfluences);

emit MotionCreated(motionCount, msg.sender, _domainId);
}

Expand All @@ -604,6 +655,7 @@ abstract contract VotingBase is ColonyExtension {
public
{
Motion storage motion = motions[_motionId];

require(_vote <= 1, "voting-base-bad-vote");
require(getMotionState(_motionId) == MotionState.Staking, "voting-base-motion-not-staking");

Expand All @@ -612,9 +664,15 @@ abstract contract VotingBase is ColonyExtension {
require(amount > 0, "voting-base-bad-amount");

uint256 stakerTotalAmount = add(stakes[_motionId][msg.sender][_vote], amount);
uint256[] memory influence = getInfluence(_motionId, msg.sender);

uint256 sumInfluence;
for (uint256 i; i < influence.length; i++) {
sumInfluence = add(sumInfluence, influence[i]);
}

require(
stakerTotalAmount <= getInfluence(_motionId, msg.sender),
stakerTotalAmount <= sumInfluence,
"voting-base-insufficient-influence"
);
require(
Expand Down Expand Up @@ -666,8 +724,10 @@ abstract contract VotingBase is ColonyExtension {
motion.events[REVEAL_END] = motion.events[SUBMIT_END] + uint64(revealPeriod);

// New stake supersedes prior votes
delete motion.votes;
delete motion.totalVotes;
for (uint256 j; j < motion.votes.length; j++) {
delete motion.votes[j];
delete motion.totalVotes[j];
}

emit MotionEventSet(_motionId, STAKE_END);
}
Expand All @@ -679,21 +739,30 @@ abstract contract VotingBase is ColonyExtension {
/// @param _voteSecret The hashed vote secret
function internalSubmitVote(uint256 _motionId, bytes32 _voteSecret) public {
Motion storage motion = motions[_motionId];

require(getMotionState(_motionId) == MotionState.Submit, "voting-base-motion-not-open");
require(_voteSecret != bytes32(0), "voting-base-invalid-secret");

uint256 influence = getInfluence(_motionId, msg.sender);

// Count reputation if first submission
// Add influence to totals if first submission
if (voteSecrets[_motionId][msg.sender] == bytes32(0)) {
motion.totalVotes = add(motion.totalVotes, influence);
uint256[] memory influence = getInfluence(_motionId, msg.sender);
for (uint256 i; i < influence.length; i++) {
motion.totalVotes[i] = add(motion.totalVotes[i], influence[i]);
}
}

voteSecrets[_motionId][msg.sender] = _voteSecret;

emit MotionVoteSubmitted(_motionId, msg.sender);

if (motion.totalVotes >= wmul(motion.maxVotes, maxVoteFraction)) {
bool submissionsComplete = true;

for (uint256 i; i < motion.votes.length; i++) {
submissionsComplete = submissionsComplete &&
motion.totalVotes[i] >= wmul(motion.maxVotes[i], maxVoteFraction);
}

if (submissionsComplete) {
motion.events[SUBMIT_END] = uint64(block.timestamp);
motion.events[REVEAL_END] = uint64(block.timestamp + revealPeriod);

Expand All @@ -706,7 +775,14 @@ abstract contract VotingBase is ColonyExtension {
}

function getRequiredStake(uint256 _motionId) public view returns (uint256) {
return wmul(motions[_motionId].maxVotes, totalStakeFraction);
Motion storage motion = motions[_motionId];

uint256 sumMaxVotes;
for (uint256 i; i < motion.maxVotes.length; i++) {
sumMaxVotes = add(sumMaxVotes, motion.maxVotes[i]);
}

return wmul(sumMaxVotes, totalStakeFraction);
}

function flip(uint256 _vote) internal pure returns (uint256) {
Expand Down Expand Up @@ -858,4 +934,37 @@ abstract contract VotingBase is ColonyExtension {

}
}

function getReputationFromProof(
uint256 _motionId,
address _who,
bytes memory _key,
bytes memory _value,
uint256 _branchMask,
bytes32[] memory _siblings
)
internal view returns (uint256)
{
bytes32 impliedRoot = getImpliedRootHashKey(_key, _value, _branchMask, _siblings);
require(motions[_motionId].rootHash == impliedRoot, "voting-base-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), "voting-base-invalid-colony-address");
require(keySkill == motions[_motionId].skillId, "voting-base-invalid-skill-id");
require(keyUserAddress == _who, "voting-base-invalid-user-address");

return reputationValue;
}

}
Loading

0 comments on commit af7f93b

Please sign in to comment.