diff --git a/packages/contracts/src/MajorityVotingBase.sol b/packages/contracts/src/MajorityVotingBase.sol index d59b25cb..f97fd4fd 100644 --- a/packages/contracts/src/MajorityVotingBase.sol +++ b/packages/contracts/src/MajorityVotingBase.sol @@ -14,6 +14,7 @@ import {PluginUUPSUpgradeable} from "@aragon/osx-commons-contracts/src/plugin/Pl import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; import {IMajorityVoting} from "./IMajorityVoting.sol"; +import {_applyRatioCeiled} from "@aragon/osx-commons-contracts/src/utils/math/Ratio.sol"; /* solhint-enable max-line-length */ @@ -222,7 +223,15 @@ abstract contract MajorityVotingBase is mapping(uint256 => Proposal) internal proposals; /// @notice The struct storing the voting settings. - VotingSettings private votingSettings; + VotingSettings private votingSettings; // !deprecated + + struct SettingsSnapshot { + uint64 snapshot; + VotingSettings votingSettings; + } + + mapping(uint256 => SettingsSnapshot) private settings; + uint16 private latestSettingIdx; // Index from `stages` storage mapping /// @notice Thrown if a date is out of bounds. /// @param limit The limit value. @@ -349,11 +358,22 @@ abstract contract MajorityVotingBase is function isSupportThresholdReached(uint256 _proposalId) public view virtual returns (bool) { Proposal storage proposal_ = proposals[_proposalId]; + uint64 snapshotBlock; + uint64 supportThreshold; + if (proposal_.parameters.snapshotBlock == 0) { + // should be signaling proposal + (, , , snapshotBlock) = getUint64Parts(_proposalId); + supportThreshold = getSetting(snapshotBlock).supportThreshold; + } else { + snapshotBlock = proposal_.parameters.snapshotBlock; + supportThreshold = proposal_.parameters.supportThreshold; + } + // The code below implements the formula of the support criterion explained in the top of this file. // `(1 - supportThreshold) * N_yes > supportThreshold * N_no` return - (RATIO_BASE - proposal_.parameters.supportThreshold) * proposal_.tally.yes > - proposal_.parameters.supportThreshold * proposal_.tally.no; + (RATIO_BASE - supportThreshold) * proposal_.tally.yes > + supportThreshold * proposal_.tally.no; } /// @inheritdoc IMajorityVoting @@ -362,7 +382,19 @@ abstract contract MajorityVotingBase is ) public view virtual returns (bool) { Proposal storage proposal_ = proposals[_proposalId]; - uint256 noVotesWorstCase = totalVotingPower(proposal_.parameters.snapshotBlock) - + uint64 snapshotBlock; + uint64 supportThreshold; + + if (proposal_.parameters.snapshotBlock == 0) { + // should it signaling proposal + (, , snapshotBlock, ) = getUint64Parts(_proposalId); + supportThreshold = getSetting(snapshotBlock).supportThreshold; + } else { + snapshotBlock = proposal_.parameters.snapshotBlock; + supportThreshold = proposal_.parameters.supportThreshold; + } + + uint256 noVotesWorstCase = totalVotingPower(snapshotBlock) - proposal_.tally.yes - proposal_.tally.abstain; @@ -370,48 +402,59 @@ abstract contract MajorityVotingBase is // early execution support criterion explained in the top of this file. // `(1 - supportThreshold) * N_yes > supportThreshold * N_no,worst-case` return - (RATIO_BASE - proposal_.parameters.supportThreshold) * proposal_.tally.yes > - proposal_.parameters.supportThreshold * noVotesWorstCase; + (RATIO_BASE - supportThreshold) * proposal_.tally.yes > + supportThreshold * noVotesWorstCase; } /// @inheritdoc IMajorityVoting function isMinParticipationReached(uint256 _proposalId) public view virtual returns (bool) { Proposal storage proposal_ = proposals[_proposalId]; + uint256 minVotingPower; + + if (proposal_.parameters.snapshotBlock == 0) { + // should it signaling proposal + (, , uint64 snapshotBlock, ) = getUint64Parts(_proposalId); + minVotingPower = _applyRatioCeiled( + totalVotingPower(snapshotBlock), + getSetting(snapshotBlock).minParticipation + ); + } else { + minVotingPower = proposal_.parameters.minVotingPower; + } + // The code below implements the formula of the // participation criterion explained in the top of this file. // `N_yes + N_no + N_abstain >= minVotingPower = minParticipation * N_total` - return - proposal_.tally.yes + proposal_.tally.no + proposal_.tally.abstain >= - proposal_.parameters.minVotingPower; + return proposal_.tally.yes + proposal_.tally.no + proposal_.tally.abstain >= minVotingPower; } /// @inheritdoc IMajorityVoting function supportThreshold() public view virtual returns (uint32) { - return votingSettings.supportThreshold; + return settings[latestSettingIdx].votingSettings.supportThreshold; } /// @inheritdoc IMajorityVoting function minParticipation() public view virtual returns (uint32) { - return votingSettings.minParticipation; + return settings[latestSettingIdx].votingSettings.minParticipation; } /// @notice Returns the minimum duration parameter stored in the voting settings. /// @return The minimum duration parameter. function minDuration() public view virtual returns (uint64) { - return votingSettings.minDuration; + return settings[latestSettingIdx].votingSettings.minDuration; } /// @notice Returns the minimum voting power required to create a proposal stored in the voting settings. /// @return The minimum voting power required to create a proposal. function minProposerVotingPower() public view virtual returns (uint256) { - return votingSettings.minProposerVotingPower; + return settings[latestSettingIdx].votingSettings.minProposerVotingPower; } /// @notice Returns the vote mode stored in the voting settings. /// @return The vote mode parameter. function votingMode() public view virtual returns (VotingMode) { - return votingSettings.votingMode; + return settings[latestSettingIdx].votingSettings.votingMode; } /// @notice Returns the total voting power checkpointed for a specific block number. @@ -444,7 +487,11 @@ abstract contract MajorityVotingBase is { Proposal storage proposal_ = proposals[_proposalId]; - open = _isProposalOpen(proposal_); + open = _isProposalOpen( + proposal_.parameters.startDate, + proposal_.parameters.endDate, + proposal_.executed + ); executed = proposal_.executed; parameters = proposal_.parameters; tally = proposal_.tally; @@ -521,6 +568,8 @@ abstract contract MajorityVotingBase is VoteOption _voteOption ) internal view virtual returns (bool); + // todo as Sarkawt suggested we could have a function that only checks the results of the vote and don't care about the early execution etc + // todo but not 100% sure if will give the needed value, early execution and vote replacement is also useful /// @notice Internal function to check if a proposal can be executed. It assumes the queried proposal exists. /// @param _proposalId The ID of the proposal. /// @return True if the proposal can be executed, false otherwise. @@ -533,9 +582,29 @@ abstract contract MajorityVotingBase is return false; } - if (_isProposalOpen(proposal_)) { + // The proposal vote hasn't started or has already ended. + uint64 startDate; + uint64 endDate; + bool executed; + VotingMode votingMode; + if (proposal_.parameters.snapshotBlock == 0) { + // it doesn't exist + + // try to get the info from the proposalId + uint64 snapshotBlock; + (startDate, endDate, snapshotBlock, ) = getUint64Parts(_proposalId); + + votingMode = getSetting(snapshotBlock).votingMode; + } else { + startDate = proposal_.parameters.startDate; + endDate = proposal_.parameters.endDate; + executed = proposal_.executed; + votingMode = proposal_.parameters.votingMode; + } + + if (_isProposalOpen(startDate, endDate, false)) { // Early execution - if (proposal_.parameters.votingMode != VotingMode.EarlyExecution) { + if (votingMode != VotingMode.EarlyExecution) { return false; } if (!isSupportThresholdReachedEarly(_proposalId)) { @@ -554,16 +623,33 @@ abstract contract MajorityVotingBase is return true; } + // ! double check this function + function getUint64Parts( + uint256 input + ) public pure returns (uint64 part1, uint64 part2, uint64 part3, uint64 part4) { + part1 = uint64(input & 0xFFFFFFFFFFFFFFFF); // Mask the least significant 64 bits + part2 = uint64((input >> 64) & 0xFFFFFFFFFFFFFFFF); // Mask the second 64 bits + part3 = uint64((input >> 128) & 0xFFFFFFFFFFFFFFFF); // Mask the third 64 bits + part4 = uint64((input >> 192) & 0xFFFFFFFFFFFFFFFF); // Mask the fourth 64 bits + } + + // todo + function getSetting(uint64 snapshotBlock) public view returns (VotingSettings memory settings) { + // binary search for the settings to get the right one + // maybe no need binary search, just go from the end of the list + } + /// @notice Internal function to check if a proposal vote is still open. - /// @param proposal_ The proposal struct. + // / @param _startDate The proposal struct. /// @return True if the proposal vote is open, false otherwise. - function _isProposalOpen(Proposal storage proposal_) internal view virtual returns (bool) { + function _isProposalOpen( + uint64 _startDate, + uint64 _endDate, + bool _executed + ) internal view virtual returns (bool) { uint64 currentTime = block.timestamp.toUint64(); - return - proposal_.parameters.startDate <= currentTime && - currentTime < proposal_.parameters.endDate && - !proposal_.executed; + return _startDate <= currentTime && currentTime < _endDate && !_executed; } /// @notice Internal function to update the plugin-wide proposal vote settings. @@ -592,7 +678,13 @@ abstract contract MajorityVotingBase is revert MinDurationOutOfBounds({limit: 365 days, actual: _votingSettings.minDuration}); } - votingSettings = _votingSettings; + // ! deprecated + // votingSettings = _votingSettings; + + // we start on idx 1 so latestSettingIdx means no settings configured + latestSettingIdx++; + settings[latestSettingIdx].snapshot = block.timestamp.toUint64(); + settings[latestSettingIdx].votingSettings = _votingSettings; emit VotingSettingsUpdated({ votingMode: _votingSettings.votingMode, @@ -627,7 +719,7 @@ abstract contract MajorityVotingBase is // Since `minDuration` is limited to 1 year, // `startDate + minDuration` can only overflow if the `startDate` is after `type(uint64).max - minDuration`. // In this case, the proposal creation will revert and another date can be picked. - uint64 earliestEndDate = startDate + votingSettings.minDuration; + uint64 earliestEndDate = startDate + settings[latestSettingIdx].votingSettings.minDuration; if (_end == 0) { endDate = earliestEndDate; @@ -644,5 +736,5 @@ abstract contract MajorityVotingBase is /// new variables without shifting down storage in the inheritance chain /// (see [OpenZeppelin's guide about storage gaps] /// (https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps)). - uint256[47] private __gap; + uint256[45] private __gap; } diff --git a/packages/contracts/src/TokenVoting.sol b/packages/contracts/src/TokenVoting.sol index 95a061a0..a0a89e53 100644 --- a/packages/contracts/src/TokenVoting.sol +++ b/packages/contracts/src/TokenVoting.sol @@ -216,7 +216,28 @@ contract TokenVoting is IMembership, MajorityVotingBase { Proposal storage proposal_ = proposals[_proposalId]; // The proposal vote hasn't started or has already ended. - if (!_isProposalOpen(proposal_)) { + uint64 startDate; + uint64 endDate; + bool executed; + uint64 snapshotBlock; + VotingMode votingMode; + if (proposal_.parameters.snapshotBlock == 0) { + // should it signaling proposal + + // try to get the info from the proposalId + (startDate, endDate, snapshotBlock, ) = getUint64Parts(_proposalId); + votingMode = getSetting(snapshotBlock).votingMode; + + // if this information is formatted in a different way, the values will make no sense + } else { + startDate = proposal_.parameters.startDate; + endDate = proposal_.parameters.endDate; + executed = proposal_.executed; + snapshotBlock = proposal_.parameters.snapshotBlock; + votingMode = proposal_.parameters.votingMode; + } + + if (!_isProposalOpen(startDate, endDate, executed)) { return false; } @@ -226,14 +247,14 @@ contract TokenVoting is IMembership, MajorityVotingBase { } // The voter has no voting power. - if (votingToken.getPastVotes(_account, proposal_.parameters.snapshotBlock) == 0) { + if (votingToken.getPastVotes(_account, snapshotBlock) == 0) { return false; } - // The voter has already voted but vote replacment is not allowed. + // The voter has already voted but vote replacement is not allowed. if ( proposal_.voters[_account] != VoteOption.None && - proposal_.parameters.votingMode != VotingMode.VoteReplacement + votingMode != VotingMode.VoteReplacement ) { return false; }