diff --git a/packages/contracts/src/MajorityVotingBase.sol b/packages/contracts/src/MajorityVotingBase.sol index dacd0aa9..dcbeae22 100644 --- a/packages/contracts/src/MajorityVotingBase.sol +++ b/packages/contracts/src/MajorityVotingBase.sol @@ -430,7 +430,10 @@ abstract contract MajorityVotingBase is revert NonexistentProposal(_proposalId); } - return _hasSucceeded(_proposalId); + Proposal storage proposal_ = proposals[_proposalId]; + bool isProposalOpen = _isProposalOpen(proposal_); + + return _hasSucceeded(_proposalId, isProposalOpen); } /// @inheritdoc IMajorityVoting @@ -640,20 +643,26 @@ abstract contract MajorityVotingBase is /// @notice An internal function that checks if the proposal succeeded or not. /// @param _proposalId The ID of the proposal. + /// @param _isOpen Weather the proposal is open or not. /// @return Returns `true` if the proposal succeeded depending on the thresholds and voting modes. - function _hasSucceeded(uint256 _proposalId) internal view virtual returns (bool) { + function _hasSucceeded(uint256 _proposalId, bool _isOpen) internal view virtual returns (bool) { Proposal storage proposal_ = proposals[_proposalId]; - if (_isProposalOpen(proposal_)) { - // Early execution - if (proposal_.parameters.votingMode != VotingMode.EarlyExecution) { + if (_isOpen) { + // If the proposal is still open and the voting mode is VoteReplacement, + // success cannot be determined until the voting period ends. + if (proposal_.parameters.votingMode == VotingMode.VoteReplacement) { return false; } + + // For Standard and EarlyExecution modes, check if the support threshold + // has been reached early to determine success while proposal is still open. if (!isSupportThresholdReachedEarly(_proposalId)) { return false; } } else { - // Normal execution + // When the proposal is closed, check if the support threshold + // has been reached based on final voting results. if (!isSupportThresholdReached(_proposalId)) { return false; } @@ -680,7 +689,14 @@ abstract contract MajorityVotingBase is return false; } - return _hasSucceeded(_proposalId); + bool isProposalOpen = _isProposalOpen(proposal_); + + // For Standard and VoteReplacement modes, enforce waiting until end date + if (proposal_.parameters.votingMode != VotingMode.EarlyExecution && isProposalOpen) { + return false; + } + + return _hasSucceeded(_proposalId, isProposalOpen); } /// @notice Internal function to check if a proposal is still open. diff --git a/packages/contracts/test/10_unit-testing/11_plugin.ts b/packages/contracts/test/10_unit-testing/11_plugin.ts index 5dff3096..0a179b4c 100644 --- a/packages/contracts/test/10_unit-testing/11_plugin.ts +++ b/packages/contracts/test/10_unit-testing/11_plugin.ts @@ -2065,8 +2065,8 @@ describe('TokenVoting', function () { expect(await plugin.isSupportThresholdReachedEarly(id)).to.be.true; expect(await plugin.isMinParticipationReached(id)).to.be.true; expect(await plugin.canExecute(id)).to.equal(false); - // Still return false as voting mode is Standard and proposal is still open. - expect(await plugin.hasSucceeded(id)).to.be.false; + // It should return true as voting mode is Standard and proposal is still open, but thresholds are met. + expect(await plugin.hasSucceeded(id)).to.be.true; }); it('can execute normally if participation and support are met', async () => {