Skip to content

Commit

Permalink
feat: idea
Browse files Browse the repository at this point in the history
  • Loading branch information
clauBv23 committed Oct 25, 2024
1 parent 1229452 commit 876535a
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 30 deletions.
144 changes: 118 additions & 26 deletions packages/contracts/src/MajorityVotingBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down Expand Up @@ -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;

Check warning on line 233 in packages/contracts/src/MajorityVotingBase.sol

View workflow job for this annotation

GitHub Actions / checks

Main key parameter in mapping settings is not named

Check warning on line 233 in packages/contracts/src/MajorityVotingBase.sol

View workflow job for this annotation

GitHub Actions / checks

Value parameter in mapping settings is not named
uint16 private latestSettingIdx; // Index from `stages` storage mapping

/// @notice Thrown if a date is out of bounds.
/// @param limit The limit value.
Expand Down Expand Up @@ -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
Expand All @@ -362,56 +382,79 @@ 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;

// The code below implements the formula of the
// 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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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

Check failure on line 571 in packages/contracts/src/MajorityVotingBase.sol

View workflow job for this annotation

GitHub Actions / checks

Line length must be no more than 120 but current length is 143
// 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.
Expand All @@ -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)) {
Expand All @@ -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) {

Check warning on line 637 in packages/contracts/src/MajorityVotingBase.sol

View workflow job for this annotation

GitHub Actions / checks

Code contains empty blocks
// 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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
29 changes: 25 additions & 4 deletions packages/contracts/src/TokenVoting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}
Expand Down

0 comments on commit 876535a

Please sign in to comment.