diff --git a/contracts/extensions/IColonyExtension.sol b/contracts/extensions/IColonyExtension.sol new file mode 100644 index 0000000000..95dd0a289c --- /dev/null +++ b/contracts/extensions/IColonyExtension.sol @@ -0,0 +1,36 @@ +/* + 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.7.3; +pragma experimental ABIEncoderV2; + +interface IColonyExtension { + + function identifier() external pure returns (bytes32); + function version() external pure virtual returns (uint256); + function install(address _colony) external virtual; + function finishUpgrade() external virtual; + function deprecate(bool _deprecated) external virtual; + function uninstall() external virtual; + + function getCapabilityRoles(bytes4 _sig) external view virtual returns (bytes32); + + function getDeprecated() external view returns (bool); + + function getColony() external view returns(address); + +} diff --git a/contracts/extensions/VotingReputation/IVotingReputation.sol b/contracts/extensions/VotingReputation/IVotingReputation.sol new file mode 100644 index 0000000000..8b02497eb3 --- /dev/null +++ b/contracts/extensions/VotingReputation/IVotingReputation.sol @@ -0,0 +1,321 @@ +/* + 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 external 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 external License for more details. + + You should have received a copy of the GNU General external License + along with The Colony Network. If not, see . +*/ + +pragma solidity 0.7.3; +pragma experimental ABIEncoderV2; + +// import "./../colonyNetwork/IColonyNetwork.sol"; +// import "./../colony/ColonyRoles.sol"; +import "./../../common/IBasicMetaTransaction.sol"; +import "./../IColonyExtension.sol"; +import "./VotingReputationDataTypes.sol"; +// import "./../patriciaTree/PatriciaTreeProofs.sol"; +// import "./../tokenLocking/ITokenLocking.sol"; +// import "./ColonyExtension.sol"; + + +interface IVotingReputation is IBasicMetaTransaction, IColonyExtension, VotingReputationDataTypes { + + // function getMetatransactionNonce(address userAddress) override external view returns (uint256 nonce); + + + /// @notice Initialise the extension + /// @param _totalStakeFraction The fraction of the domain's reputation we need to stake + /// @param _userMinStakeFraction The minimum per-user stake as fraction of total stake + /// @param _maxVoteFraction The fraction of the domain's reputation which must submit for quick-end + /// @param _voterRewardFraction The fraction of the total stake paid out to voters as rewards + /// @param _stakePeriod The length of the staking period in seconds + /// @param _submitPeriod The length of the submit period in seconds + /// @param _revealPeriod The length of the reveal period in seconds + /// @param _escalationPeriod The length of the escalation period in seconds + function initialise( + uint256 _totalStakeFraction, + uint256 _voterRewardFraction, + uint256 _userMinStakeFraction, + uint256 _maxVoteFraction, + uint256 _stakePeriod, + uint256 _submitPeriod, + uint256 _revealPeriod, + uint256 _escalationPeriod + ) + external; + + // // Data structures + + // external functions (interface) + + /// @notice Create a motion + /// @param _domainId The domain where we vote on the motion + /// @param _childSkillIndex The childSkillIndex pointing to the domain of the action + /// @param _altTarget The contract to which we send the action (0x0 for the colony) + /// @param _action A bytes array encoding a function call + /// @param _key Reputation tree key for the root domain + /// @param _value Reputation tree value for the root domain + /// @param _branchMask The branchmask of the proof + /// @param _siblings The siblings of the proof + function createMotion( + uint256 _domainId, + uint256 _childSkillIndex, + address _altTarget, + bytes memory _action, + bytes memory _key, + bytes memory _value, + uint256 _branchMask, + bytes32[] memory _siblings + ) + external; + + /// @notice Create a motion in the root domain (DEPRECATED) + /// @param _altTarget The contract to which we send the action (0x0 for the colony) + /// @param _action A bytes array encoding a function call + /// @param _key Reputation tree key for the root domain + /// @param _value Reputation tree value for the root domain + /// @param _branchMask The branchmask of the proof + /// @param _siblings The siblings of the proof + function createRootMotion( + address _altTarget, + bytes memory _action, + bytes memory _key, + bytes memory _value, + uint256 _branchMask, + bytes32[] memory _siblings + ) + external; + + /// @notice Create a motion in any domain (DEPRECATED) + /// @param _domainId The domain where we vote on the motion + /// @param _childSkillIndex The childSkillIndex pointing to the domain of the action + /// @param _action A bytes array encoding a function call + /// @param _key Reputation tree key for the domain + /// @param _value Reputation tree value for the domain + /// @param _branchMask The branchmask of the proof + /// @param _siblings The siblings of the proof + function createDomainMotion( + uint256 _domainId, + uint256 _childSkillIndex, + bytes memory _action, + bytes memory _key, + bytes memory _value, + uint256 _branchMask, + bytes32[] memory _siblings + ) + external; + + /// @notice Stake on a motion + /// @param _motionId The id of the motion + /// @param _permissionDomainId The domain where the extension has the arbitration permission + /// @param _childSkillIndex For the domain in which the motion is occurring + /// @param _vote The side being supported (0 = NAY, 1 = YAY) + /// @param _amount The amount of tokens being staked + /// @param _key Reputation tree key for the staker/domain + /// @param _value Reputation tree value for the staker/domain + /// @param _branchMask The branchmask of the proof + /// @param _siblings The siblings of the proof + function stakeMotion( + uint256 _motionId, + uint256 _permissionDomainId, + uint256 _childSkillIndex, + uint256 _vote, + uint256 _amount, + bytes memory _key, + bytes memory _value, + uint256 _branchMask, + bytes32[] memory _siblings + ) + external; + + /// @notice Submit a vote secret for a motion + /// @param _motionId The id of the motion + /// @param _voteSecret The hashed vote secret + /// @param _key Reputation tree key for the staker/domain + /// @param _value Reputation tree value for the staker/domain + /// @param _branchMask The branchmask of the proof + /// @param _siblings The siblings of the proof + function submitVote( + uint256 _motionId, + bytes32 _voteSecret, + bytes memory _key, + bytes memory _value, + uint256 _branchMask, + bytes32[] memory _siblings + ) + external; + + /// @notice Reveal a vote secret for a motion + /// @param _motionId The id of the motion + /// @param _salt The salt used to hash the vote + /// @param _vote The side being supported (0 = NAY, 1 = YAY) + /// @param _key Reputation tree key for the staker/domain + /// @param _value Reputation tree value for the staker/domain + /// @param _branchMask The branchmask of the proof + /// @param _siblings The siblings of the proof + function revealVote( + uint256 _motionId, + bytes32 _salt, + uint256 _vote, + bytes memory _key, + bytes memory _value, + uint256 _branchMask, + bytes32[] memory _siblings + ) + external; + + /// @notice Escalate a motion to a higher domain + /// @param _motionId The id of the motion + /// @param _newDomainId The desired domain of escalation + /// @param _childSkillIndex For the current domain, relative to the escalated domain + /// @param _key Reputation tree key for the new domain + /// @param _value Reputation tree value for the new domain + /// @param _branchMask The branchmask of the proof + /// @param _siblings The siblings of the proof + function escalateMotion( + uint256 _motionId, + uint256 _newDomainId, + uint256 _childSkillIndex, + bytes memory _key, + bytes memory _value, + uint256 _branchMask, + bytes32[] memory _siblings + ) + external; + + function finalizeMotion(uint256 _motionId) external; + + + /// @notice Return whether a motion, assuming it's in the finalizable state, + // is allowed to finalize without the call executing successfully. + /// @param _motionId The id of the motion + /// @dev We are only expecting this to be called from finalize motion in the contracts. + /// It is marked as external only so that the frontend can use it. + function failingExecutionAllowed(uint256 _motionId) external view returns (bool); + + /// @notice Claim the staker's reward + /// @param _motionId The id of the motion + /// @param _permissionDomainId The domain where the extension has the arbitration permission + /// @param _childSkillIndex For the domain in which the motion is occurring + /// @param _staker The staker whose reward is being claimed + /// @param _vote The side being supported (0 = NAY, 1 = YAY) + function claimReward( + uint256 _motionId, + uint256 _permissionDomainId, + uint256 _childSkillIndex, + address _staker, + uint256 _vote + ) + external; + + // external view functions + + /// @notice Get the total stake fraction + /// @return The total stake fraction + function getTotalStakeFraction() external view returns (uint256); + + /// @notice Get the voter reward fraction + /// @return The voter reward fraction + function getVoterRewardFraction() external view returns (uint256) ; + + /// @notice Get the user min stake fraction + /// @return The user min stake fraction + function getUserMinStakeFraction() external view returns (uint256) ; + + /// @notice Get the max vote fraction + /// @return The max vote fraction + function getMaxVoteFraction() external view returns (uint256); + + /// @notice Get the stake period + /// @return The stake period + function getStakePeriod() external view returns (uint256); + + /// @notice Get the submit period + /// @return The submit period + function getSubmitPeriod() external view returns (uint256); + + /// @notice Get the reveal period + /// @return The reveal period + function getRevealPeriod() external view returns (uint256); + + /// @notice Get the escalation period + /// @return The escalation period + function getEscalationPeriod() external view returns (uint256); + + + /// @notice Get the total motion count + /// @return The total motion count + function getMotionCount() external view returns (uint256) ; + + /// @notice Get the data for a single motion + /// @param _motionId The id of the motion + /// @return motion The motion struct + function getMotion(uint256 _motionId) external view returns (Motion memory motion); + + /// @notice Get a user's stake on a motion + /// @param _motionId The id of the motion + /// @param _staker The staker address + /// @param _vote The side being supported (0 = NAY, 1 = YAY) + /// @return The user's stake + function getStake(uint256 _motionId, address _staker, uint256 _vote) external view returns (uint256); + + /// @notice Get the number of ongoing motions for a single expenditure / expenditure slot + /// @param _structHash The hash of the expenditureId or expenditureId*expenditureSlot + /// @return The number of ongoing motions + function getExpenditureMotionCount(bytes32 _structHash) external view returns (uint256); + + /// @notice Get the largest past vote on a single expenditure variable + /// @param _actionHash The hash of the particular expenditure action + /// @return The largest past vote on this variable + function getExpenditurePastVote(bytes32 _actionHash) external view returns (uint256); + + /// @notice Get the current state of the motion + /// @return The current motion state + function getMotionState(uint256 _motionId) external view returns (MotionState) ; + + /// @notice Get the voter reward + /// NB This function will only return a meaningful value if in the reveal state. + /// Prior to the reveal state, getVoterRewardRange should be used. + /// @param _motionId The id of the motion + /// @param _voterRep The reputation the voter has in the domain + /// @return The voter reward + function getVoterReward(uint256 _motionId, uint256 _voterRep) external view returns (uint256) ; + + /// @notice Get the range of potential rewards for a voter on a specific motion, intended to be + /// used when the motion is in the reveal state. + /// Once a motion is in the reveal state the reward is known, and getVoterRewardRange should be used. + /// @param _motionId The id of the motion + /// @param _voterRep The reputation the voter has in the domain + /// @param _voterAddress The address the user will be voting as + /// @return The voter reward + function getVoterRewardRange(uint256 _motionId, uint256 _voterRep, address _voterAddress) external view returns (uint256, uint256) ; + /// @notice Get the staker reward + /// @param _motionId The id of the motion + /// @param _staker The staker's address + /// @param _vote The vote (0 = NAY, 1 = YAY) + /// @return The staker reward and the reputation penalty (if any) + function getStakerReward(uint256 _motionId, address _staker, uint256 _vote) external view returns (uint256, uint256); + + function createClaimDelayAction(bytes memory action, uint256 value) + external + returns (bytes memory); + + function claimMisalignedReward( + uint256 _motionId, + uint256 _permissionDomainId, + uint256 _childSkillIndex, + address _staker, + uint256 _vote + ) + external; +} diff --git a/contracts/extensions/VotingReputation.sol b/contracts/extensions/VotingReputation/VotingReputation.sol similarity index 96% rename from contracts/extensions/VotingReputation.sol rename to contracts/extensions/VotingReputation/VotingReputation.sol index 30d5ea1aa5..d509369eb5 100644 --- a/contracts/extensions/VotingReputation.sol +++ b/contracts/extensions/VotingReputation/VotingReputation.sol @@ -18,28 +18,17 @@ pragma solidity 0.7.3; pragma experimental ABIEncoderV2; -import "./../colonyNetwork/IColonyNetwork.sol"; -import "./../colony/ColonyRoles.sol"; -import "./../common/BasicMetaTransaction.sol"; -import "./../common/ERC20Extended.sol"; -import "./../patriciaTree/PatriciaTreeProofs.sol"; -import "./../tokenLocking/ITokenLocking.sol"; -import "./ColonyExtension.sol"; - - -contract VotingReputation is ColonyExtension, PatriciaTreeProofs, BasicMetaTransaction { - - // Events - event MotionCreated(uint256 indexed motionId, address creator, uint256 indexed domainId); - event MotionStaked(uint256 indexed motionId, address indexed staker, uint256 indexed vote, uint256 amount); - event MotionVoteSubmitted(uint256 indexed motionId, address indexed voter); - event MotionVoteRevealed(uint256 indexed motionId, address indexed voter, uint256 indexed vote); - event MotionFinalized(uint256 indexed motionId, bytes action, bool executed); - event MotionEscalated(uint256 indexed motionId, address escalator, uint256 indexed domainId, uint256 indexed newDomainId); - event MotionRewardClaimed(uint256 indexed motionId, address indexed staker, uint256 indexed vote, uint256 amount); - event MotionEventSet(uint256 indexed motionId, uint256 eventIndex); - - // Constants +import "./../../colonyNetwork/IColonyNetwork.sol"; +import "./../../colony/ColonyRoles.sol"; +import "./../../common/BasicMetaTransaction.sol"; +import "./../../common/ERC20Extended.sol"; +import "./../../patriciaTree/PatriciaTreeProofs.sol"; +import "./../../tokenLocking/ITokenLocking.sol"; +import "./../ColonyExtension.sol"; +import "./VotingReputationDataTypes.sol"; + + +contract VotingReputation is ColonyExtension, PatriciaTreeProofs, BasicMetaTransaction, VotingReputationDataTypes { uint256 constant UINT128_MAX = 2**128 - 1; uint256 constant NAY = 0; @@ -62,8 +51,6 @@ contract VotingReputation is ColonyExtension, PatriciaTreeProofs, BasicMetaTrans "moveFundsBetweenPots(uint256,uint256,uint256,uint256,uint256,uint256,address)" )); - enum ExtensionState { Deployed, Active, Deprecated } - // Initialization data ExtensionState state; @@ -129,7 +116,7 @@ contract VotingReputation is ColonyExtension, PatriciaTreeProofs, BasicMetaTrans /// @notice Return the version number /// @return The version number function version() public pure override returns (uint256) { - return 5; + return 6; } /// @notice Install the extension @@ -221,26 +208,6 @@ contract VotingReputation is ColonyExtension, PatriciaTreeProofs, BasicMetaTrans selfdestruct(address(uint160(address(colony)))); } - // Data structures - enum MotionState { Null, Staking, Submit, Reveal, Closed, Finalizable, Finalized, Failed } - - struct Motion { - uint64[3] events; // For recording motion lifecycle timestamps (STAKE, SUBMIT, REVEAL) - bytes32 rootHash; - uint256 domainId; - uint256 skillId; - uint256 skillRep; - uint256 repSubmitted; - uint256 paidVoterComp; - uint256[2] pastVoterComp; // [nay, yay] - uint256[2] stakes; // [nay, yay] - uint256[2] votes; // [nay, yay] - bool escalated; - bool finalized; - address altTarget; - bytes action; - } - // Public functions (interface) /// @notice Create a motion diff --git a/contracts/extensions/VotingReputation/VotingReputationDataTypes.sol b/contracts/extensions/VotingReputation/VotingReputationDataTypes.sol new file mode 100644 index 0000000000..9b7ebc0906 --- /dev/null +++ b/contracts/extensions/VotingReputation/VotingReputationDataTypes.sol @@ -0,0 +1,54 @@ +/* + 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.7.3; +import "./../../colony/ColonyDataTypes.sol"; + + +interface VotingReputationDataTypes { + // Constants + enum ExtensionState { Deployed, Active, Deprecated } + + enum MotionState { Null, Staking, Submit, Reveal, Closed, Finalizable, Finalized, Failed } + + struct Motion { + uint64[3] events; // For recording motion lifecycle timestamps (STAKE, SUBMIT, REVEAL) + bytes32 rootHash; + uint256 domainId; + uint256 skillId; + uint256 skillRep; + uint256 repSubmitted; + uint256 paidVoterComp; + uint256[2] pastVoterComp; // [nay, yay] + uint256[2] stakes; // [nay, yay] + uint256[2] votes; // [nay, yay] + bool escalated; + bool finalized; + address altTarget; + bytes action; + } + + // Events + event MotionCreated(uint256 indexed motionId, address creator, uint256 indexed domainId); + event MotionStaked(uint256 indexed motionId, address indexed staker, uint256 indexed vote, uint256 amount); + event MotionVoteSubmitted(uint256 indexed motionId, address indexed voter); + event MotionVoteRevealed(uint256 indexed motionId, address indexed voter, uint256 indexed vote); + event MotionFinalized(uint256 indexed motionId, bytes action, bool executed); + event MotionEscalated(uint256 indexed motionId, address escalator, uint256 indexed domainId, uint256 indexed newDomainId); + event MotionRewardClaimed(uint256 indexed motionId, address indexed staker, uint256 indexed vote, uint256 amount); + event MotionEventSet(uint256 indexed motionId, uint256 eventIndex); +} diff --git a/contracts/extensions/VotingReputation/VotingReputationMisalignedRecovery.sol b/contracts/extensions/VotingReputation/VotingReputationMisalignedRecovery.sol new file mode 100644 index 0000000000..b21b75424f --- /dev/null +++ b/contracts/extensions/VotingReputation/VotingReputationMisalignedRecovery.sol @@ -0,0 +1,225 @@ +/* + 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.7.3; +pragma experimental ABIEncoderV2; + +import "./../../colonyNetwork/IColonyNetwork.sol"; +import "./../../colony/ColonyRoles.sol"; +import "./../../common/BasicMetaTransaction.sol"; +import "./../../common/ERC20Extended.sol"; +import "./../../patriciaTree/PatriciaTreeProofs.sol"; +import "./../../tokenLocking/ITokenLocking.sol"; +import "./../ColonyExtension.sol"; +import "./../../../lib/dappsys/math.sol"; +import "./VotingReputationDataTypes.sol"; + +contract VotingReputationMisalignedRecovery is PatriciaTreeProofs, DSMath, DSAuth, VotingReputationDataTypes { + + // THIS FILE IS DELIBERATELY WRONG. IF YOU'RE EDITING THIS FILE, AND YOU'VE NOT BEEN EXPLICITLY + // TOLD TO DO SO, LEAVE NOW. THERE BE DRAGONS HERE. + + // Constants + uint256 constant UINT128_MAX = 2**128 - 1; + + uint256 constant NAY = 0; + uint256 constant YAY = 1; + + address resolver; // Align storage with EtherRouter + + IColony colony; + bool deprecated; + + ExtensionState state; + + IColonyNetwork colonyNetwork; + ITokenLocking tokenLocking; + address token; + + // All `Fraction` variables are stored as WADs i.e. fixed-point numbers with 18 digits after the radix. So + // 1 WAD = 10**18, which is interpreted as 1. + + uint256 totalStakeFraction; // Fraction of the domain's reputation needed to stake on each side in order to go to a motion. + // This can be set to a maximum of 0.5. + uint256 voterRewardFraction; // Fraction of staked tokens paid out to voters as rewards. This will be paid from the staked + // tokens of the losing side. This can be set to a maximum of 0.5. + + uint256 userMinStakeFraction; // Minimum stake as fraction of required stake. 1 means a single user will be required to + // provide the whole stake on each side, which may not be possible depending on totalStakeFraction and the distribution of + // reputation in a domain. + uint256 maxVoteFraction; // Fraction of total domain reputation that needs to commit votes before closing to further votes. + // Setting this to anything other than 1 will mean it is likely not all those eligible to vote will be able to do so. + + // All `Period` variables are second-denominated + + uint256 stakePeriod; // Length of time for staking + uint256 submitPeriod; // Length of time for submitting votes + uint256 revealPeriod; // Length of time for revealing votes + uint256 escalationPeriod; // Length of time for escalating after a vote + + // Here we deliberately recreate the misalignment in the storage slots, so solidity can correctly + // find the incorrect data in the mappings. + mapping(address => uint256) metatransactionNonces; + + uint256 motionCount; + mapping (uint256 => Motion) motions; + mapping (uint256 => mapping (address => mapping (uint256 => uint256))) stakes; + mapping (uint256 => mapping (address => bytes32)) voteSecrets; + + mapping (bytes32 => uint256) expenditurePastVotes; // expenditure slot signature => voting power + mapping (bytes32 => uint256) expenditureMotionCounts; // expenditure struct signature => count + + // Public functions (interface) + + /// @notice Claim the staker's reward + /// @param _motionId The id of the motion + /// @param _permissionDomainId The domain where the extension has the arbitration permission + /// @param _childSkillIndex For the domain in which the motion is occurring + /// @param _staker The staker whose reward is being claimed + /// @param _vote The side being supported (0 = NAY, 1 = YAY) + function claimMisalignedReward( + uint256 _motionId, + uint256 _permissionDomainId, + uint256 _childSkillIndex, + address _staker, + uint256 _vote + ) + public + { + Motion storage motion = motions[_motionId]; + // Motions might have been in any point in their lifecycle, so we lose our restirction + // on only being able to call this function on finalized/failed motions. These motions + // created while misaligned no longer exist, and cannot proceed through their lifecycle. + // require( + // getMotionState(_motionId) == MotionState.Finalized || + // getMotionState(_motionId) == MotionState.Failed, + // "voting-rep-motion-not-claimable" + // ); + + (uint256 stakerReward, uint256 repPenalty) = getStakerReward(_motionId, _staker, _vote); + + require(stakes[_motionId][_staker][_vote] > 0, "voting-rep-nothing-to-claim"); + delete stakes[_motionId][_staker][_vote]; + + tokenLocking.transfer(token, stakerReward, _staker, true); + + if (repPenalty > 0) { + colony.emitDomainReputationPenalty( + _permissionDomainId, + _childSkillIndex, + motion.domainId, + _staker, + -int256(repPenalty) + ); + } + + emit MotionRewardClaimed(_motionId, _staker, _vote, stakerReward); + } + + /// @notice Get the staker reward + /// @param _motionId The id of the motion + /// @param _staker The staker's address + /// @param _vote The vote (0 = NAY, 1 = YAY) + /// @return The staker reward and the reputation penalty (if any) + function getStakerReward(uint256 _motionId, address _staker, uint256 _vote) internal view returns (uint256, uint256) { + Motion storage motion = motions[_motionId]; + + uint256 totalSideStake = add(motion.stakes[_vote], motion.pastVoterComp[_vote]); + if (totalSideStake == 0) { return (0, 0); } + + uint256 stakeFraction = wdiv(stakes[_motionId][_staker][_vote], totalSideStake); + + uint256 realStake = wmul(stakeFraction, motion.stakes[_vote]); + + uint256 stakerReward; + uint256 repPenalty; + + // If finalized and went to a vote, use vote to determine reward or penalty + if (motion.finalized && add(motion.votes[NAY], motion.votes[YAY]) > 0) { + + uint256 loserStake; + uint256 winnerStake; + if (motion.votes[YAY] > motion.votes[NAY]){ + loserStake = motion.stakes[NAY]; + winnerStake = motion.stakes[YAY]; + } else { + loserStake = motion.stakes[YAY]; + winnerStake = motion.stakes[NAY]; + } + + loserStake = sub(loserStake, motion.paidVoterComp); + uint256 totalVotes = add(motion.votes[NAY], motion.votes[YAY]); + uint256 winFraction = wdiv(motion.votes[_vote], totalVotes); + uint256 winShare = wmul(winFraction, 2 * WAD); // On a scale of 0-2 WAD + + if (winShare > WAD || (winShare == WAD && _vote == NAY)) { + // 50% gets 0% of loser's stake, 100% gets 100% of loser's stake, linear in between + stakerReward = wmul(stakeFraction, add(winnerStake, wmul(loserStake, winShare - WAD))); + } else { + stakerReward = wmul(stakeFraction, wmul(loserStake, winShare)); + repPenalty = sub(realStake, stakerReward); + } + + // Else if finalized, rewards based on stakes alone + } else if (motion.finalized) { + assert(motion.paidVoterComp == 0); + uint256 requiredStake = getRequiredStake(_motionId); + + // Your side fully staked, receive 10% (proportional) of loser's stake + if ( + motion.stakes[_vote] == requiredStake && + motion.stakes[flip(_vote)] < requiredStake + ) { + + uint256 loserStake = motion.stakes[flip(_vote)]; + uint256 totalPenalty = wmul(loserStake, WAD / 10); + stakerReward = wmul(stakeFraction, add(requiredStake, totalPenalty)); + + // Opponent's side fully staked, pay 10% penalty + } else if ( + motion.stakes[_vote] < requiredStake && + motion.stakes[flip(_vote)] == requiredStake + ) { + + uint256 loserStake = motion.stakes[_vote]; + uint256 totalPenalty = wmul(loserStake, WAD / 10); + stakerReward = wmul(stakeFraction, sub(loserStake, totalPenalty)); + repPenalty = sub(realStake, stakerReward); + + // Neither side fully staked (or no votes were revealed), no reward or penalty + } else { + + stakerReward = realStake; + + } + } else { + // Motion was never finalized. We just return stakes, exactly as if neither + // side fully staked or no votes were revealed. + stakerReward = realStake; + } + + return (stakerReward, repPenalty); + } + + function getRequiredStake(uint256 _motionId) internal view returns (uint256) { + return wmul(motions[_motionId].skillRep, totalStakeFraction); + } + + function flip(uint256 _vote) internal pure returns (uint256) { + return sub(1, _vote); + } +} diff --git a/migrations/9_setup_extensions.js b/migrations/9_setup_extensions.js index 038e73ad22..264d7c757e 100644 --- a/migrations/9_setup_extensions.js +++ b/migrations/9_setup_extensions.js @@ -9,6 +9,7 @@ const EvaluatedExpenditure = artifacts.require("./EvaluatedExpenditure"); const FundingQueue = artifacts.require("./FundingQueue"); const OneTxPayment = artifacts.require("./OneTxPayment"); const VotingReputation = artifacts.require("./VotingReputation"); +const VotingReputationMisalignedRecovery = artifacts.require("./VotingReputationMisalignedRecovery"); const TokenSupplier = artifacts.require("./TokenSupplier"); const Whitelist = artifacts.require("./Whitelist"); @@ -17,17 +18,21 @@ const EtherRouter = artifacts.require("./EtherRouter"); const IColonyNetwork = artifacts.require("./IColonyNetwork"); const IMetaColony = artifacts.require("./IMetaColony"); -async function addExtension(colonyNetwork, name, implementation) { +async function addExtension(colonyNetwork, interfaceName, extensionName, implementations) { const metaColonyAddress = await colonyNetwork.getMetaColony(); const metaColony = await IMetaColony.at(metaColonyAddress); - const NAME_HASH = soliditySha3(name); - const deployment = await implementation.new(); + const NAME_HASH = soliditySha3(extensionName); + const deployments = await Promise.all(implementations.map((x) => x.new())); const resolver = await Resolver.new(); - // Computed property names! Fancy! - await setupEtherRouter(name, { [name]: deployment.address }, resolver); + + const deployedImplementations = {}; + for (const idx in implementations) { + deployedImplementations[implementations[idx].contractName] = deployments[idx].address; + } + await setupEtherRouter(interfaceName, deployedImplementations, resolver); await metaColony.addExtensionToNetwork(NAME_HASH, resolver.address); - console.log(`### ${name} extension installed`); + console.log(`### ${extensionName} extension installed`); } // eslint-disable-next-line no-unused-vars @@ -35,11 +40,11 @@ module.exports = async function (deployer, network, accounts) { const etherRouterDeployed = await EtherRouter.deployed(); const colonyNetwork = await IColonyNetwork.at(etherRouterDeployed.address); - await addExtension(colonyNetwork, "CoinMachine", CoinMachine); - await addExtension(colonyNetwork, "EvaluatedExpenditure", EvaluatedExpenditure); - await addExtension(colonyNetwork, "FundingQueue", FundingQueue); - await addExtension(colonyNetwork, "OneTxPayment", OneTxPayment); - await addExtension(colonyNetwork, "VotingReputation", VotingReputation); - await addExtension(colonyNetwork, "TokenSupplier", TokenSupplier); - await addExtension(colonyNetwork, "Whitelist", Whitelist); + await addExtension(colonyNetwork, "CoinMachine", "CoinMachine", [CoinMachine]); + await addExtension(colonyNetwork, "EvaluatedExpenditure", "EvaluatedExpenditure", [EvaluatedExpenditure]); + await addExtension(colonyNetwork, "FundingQueue", "FundingQueue", [FundingQueue]); + await addExtension(colonyNetwork, "OneTxPayment", "OneTxPayment", [OneTxPayment]); + await addExtension(colonyNetwork, "IVotingReputation", "VotingReputation", [VotingReputation, VotingReputationMisalignedRecovery]); + await addExtension(colonyNetwork, "TokenSupplier", "TokenSupplier", [TokenSupplier]); + await addExtension(colonyNetwork, "Whitelist", "Whitelist", [Whitelist]); }; diff --git a/test-smoke/colony-storage-consistent.js b/test-smoke/colony-storage-consistent.js index 288a2a6f02..82cd2af18b 100644 --- a/test-smoke/colony-storage-consistent.js +++ b/test-smoke/colony-storage-consistent.js @@ -154,8 +154,8 @@ contract("Contract Storage", (accounts) => { console.log("miningCycleStateHash:", miningCycleAccount.stateRoot.toString("hex")); console.log("tokenLockingStateHash:", tokenLockingAccount.stateRoot.toString("hex")); - expect(colonyNetworkAccount.stateRoot.toString("hex")).to.equal("59a945f801398e8c6953538db14bde68dfc61fb4b90314744d6f3869fa3f6fa1"); - expect(colonyAccount.stateRoot.toString("hex")).to.equal("db3561898fd52285f65cf27eecfdfb556838f7626f5f425d9b293dcf703fae84"); + expect(colonyNetworkAccount.stateRoot.toString("hex")).to.equal("9e974e7d769aae97eff965d3d57bab30a5298b8f9551833d1aabea9f9014c025"); + expect(colonyAccount.stateRoot.toString("hex")).to.equal("d3578d76c4570c355716ce38f0913e0dd5d33357be5a1ff43abea2db3f579172"); expect(metaColonyAccount.stateRoot.toString("hex")).to.equal("58f1833f0b94c47c028c91ededb70d6697624ecf98bc2cc7930bf55f40d2d931"); expect(miningCycleAccount.stateRoot.toString("hex")).to.equal("1f3909ac9098d953ec1d197e6d7924384e96209770f445466ea2f0c0c39f4834"); expect(tokenLockingAccount.stateRoot.toString("hex")).to.equal("7ec700a44aef86af735adcb205136940a73bd0507d07d88e93e629dee06f05c3");