diff --git a/packages/contracts/CHANGELOG.md b/packages/contracts/CHANGELOG.md index 285e98217..1159b01e0 100644 --- a/packages/contracts/CHANGELOG.md +++ b/packages/contracts/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Fixed logic bug in the `TokenVoting` and `AddresslistVoting` implementations that caused the `createProposal` function to emit the unvalidated `_startDate` and `_endDate` input arguments (that both can be zero) in the `ProposalCreated` event instead of the validated ones. - Changed the `createProposal` functions in `Multisig` to allow creating proposals when the `_msgSender()` is listed in the current block. - Changed the `createProposal` functions in `AddresslistVoting` to allow creating proposals when the `_msgSender()` is listed in the current block. - Changed the `createProposal` functions in `TokenVoting` to allow creating proposals when the `_msgSender()` owns more tokens at least `minProposerVotingPower()` tokens in the current block. diff --git a/packages/contracts/src/plugins/governance/majority-voting/addresslist/AddresslistVoting.sol b/packages/contracts/src/plugins/governance/majority-voting/addresslist/AddresslistVoting.sol index 3337349ff..c9451623b 100644 --- a/packages/contracts/src/plugins/governance/majority-voting/addresslist/AddresslistVoting.sol +++ b/packages/contracts/src/plugins/governance/majority-voting/addresslist/AddresslistVoting.sol @@ -98,6 +98,8 @@ contract AddresslistVoting is IMembership, Addresslist, MajorityVotingBase { snapshotBlock = block.number.toUint64() - 1; // The snapshot block must be mined already to protect the transaction against backrunning transactions causing census changes. } + (_startDate, _endDate) = _validateProposalDates(_startDate, _endDate); + proposalId = _createProposal({ _creator: _msgSender(), _metadata: _metadata, @@ -110,10 +112,8 @@ contract AddresslistVoting is IMembership, Addresslist, MajorityVotingBase { // Store proposal related information Proposal storage proposal_ = proposals[proposalId]; - (proposal_.parameters.startDate, proposal_.parameters.endDate) = _validateProposalDates({ - _start: _startDate, - _end: _endDate - }); + proposal_.parameters.startDate = _startDate; + proposal_.parameters.endDate = _endDate; proposal_.parameters.snapshotBlock = snapshotBlock; proposal_.parameters.votingMode = votingMode(); proposal_.parameters.supportThreshold = supportThreshold(); diff --git a/packages/contracts/src/plugins/governance/majority-voting/token/TokenVoting.sol b/packages/contracts/src/plugins/governance/majority-voting/token/TokenVoting.sol index f306a473b..3d10ac635 100644 --- a/packages/contracts/src/plugins/governance/majority-voting/token/TokenVoting.sol +++ b/packages/contracts/src/plugins/governance/majority-voting/token/TokenVoting.sol @@ -105,6 +105,8 @@ contract TokenVoting is IMembership, MajorityVotingBase { revert NoVotingPower(); } + (_startDate, _endDate) = _validateProposalDates(_startDate, _endDate); + proposalId = _createProposal({ _creator: _msgSender(), _metadata: _metadata, @@ -117,10 +119,8 @@ contract TokenVoting is IMembership, MajorityVotingBase { // Store proposal related information Proposal storage proposal_ = proposals[proposalId]; - (proposal_.parameters.startDate, proposal_.parameters.endDate) = _validateProposalDates( - _startDate, - _endDate - ); + proposal_.parameters.startDate = _startDate; + proposal_.parameters.endDate = _endDate; proposal_.parameters.snapshotBlock = snapshotBlock.toUint64(); proposal_.parameters.votingMode = votingMode(); proposal_.parameters.supportThreshold = supportThreshold(); @@ -148,7 +148,7 @@ contract TokenVoting is IMembership, MajorityVotingBase { /// @inheritdoc IMembership function isMember(address _account) external view returns (bool) { - // A member must own or least one token or have at least one token delegated to her/him. + // A member must own at least one token or have at least one token delegated to her/him. return votingToken.getVotes(_account) > 0 || IERC20Upgradeable(address(votingToken)).balanceOf(_account) > 0; diff --git a/packages/contracts/test/plugins/governance/majority-voting/addresslist/addresslist-voting.ts b/packages/contracts/test/plugins/governance/majority-voting/addresslist/addresslist-voting.ts index 20a9eac55..01442f4d9 100644 --- a/packages/contracts/test/plugins/governance/majority-voting/addresslist/addresslist-voting.ts +++ b/packages/contracts/test/plugins/governance/majority-voting/addresslist/addresslist-voting.ts @@ -478,6 +478,52 @@ describe('AddresslistVoting', function () { .withArgs(earliestEndDate, tooEarlyEndDate); }); + it('sets the startDate to now and endDate to startDate + minDuration, if 0 is provided as an input', async () => { + await voting.initialize(dao.address, votingSettings, [ + signers[0].address, + ]); + + // Create a proposal with zero as an input for `_startDate` and `_endDate` + const startDate = 0; // now + const endDate = 0; // startDate + minDuration + + const creationTx = await voting.createProposal( + dummyMetadata, + [], + 0, + startDate, + endDate, + VoteOption.None, + false + ); + + const currentTime = ( + await ethers.provider.getBlock((await creationTx.wait()).blockNumber) + ).timestamp; + + const expectedStartDate = currentTime; + const expectedEndDate = expectedStartDate + votingSettings.minDuration; + + // Check the state + const proposal = await voting.getProposal(id); + expect(proposal.parameters.startDate).to.eq(expectedStartDate); + expect(proposal.parameters.endDate).to.eq(expectedEndDate); + + // Check the event + const event = await findEvent( + creationTx, + 'ProposalCreated' + ); + + expect(event.args.proposalId).to.equal(id); + expect(event.args.creator).to.equal(signers[0].address); + expect(event.args.startDate).to.equal(expectedStartDate); + expect(event.args.endDate).to.equal(expectedEndDate); + expect(event.args.metadata).to.equal(dummyMetadata); + expect(event.args.actions).to.deep.equal([]); + expect(event.args.allowFailureMap).to.equal(0); + }); + it('ceils the `minVotingPower` value if it has a remainder', async () => { votingSettings.minParticipation = pctToRatio(30).add(1); // 30.0001 % diff --git a/packages/contracts/test/plugins/governance/majority-voting/token/token-voting.ts b/packages/contracts/test/plugins/governance/majority-voting/token/token-voting.ts index 24ce29f19..0031887e4 100644 --- a/packages/contracts/test/plugins/governance/majority-voting/token/token-voting.ts +++ b/packages/contracts/test/plugins/governance/majority-voting/token/token-voting.ts @@ -783,6 +783,54 @@ describe('TokenVoting', function () { .withArgs(earliestEndDate, tooEarlyEndDate); }); + it('sets the startDate to now and endDate to startDate + minDuration, if 0 is provided as an input', async () => { + await voting.initialize( + dao.address, + votingSettings, + governanceErc20Mock.address + ); + + // Create a proposal with zero as an input for `_startDate` and `_endDate` + const startDate = 0; // now + const endDate = 0; // startDate + minDuration + + const creationTx = await voting.createProposal( + dummyMetadata, + [], + 0, + startDate, + endDate, + VoteOption.None, + false + ); + + const currentTime = ( + await ethers.provider.getBlock((await creationTx.wait()).blockNumber) + ).timestamp; + + const expectedStartDate = currentTime; + const expectedEndDate = expectedStartDate + votingSettings.minDuration; + + // Check the state + const proposal = await voting.getProposal(id); + expect(proposal.parameters.startDate).to.eq(expectedStartDate); + expect(proposal.parameters.endDate).to.eq(expectedEndDate); + + // Check the event + const event = await findEvent( + creationTx, + 'ProposalCreated' + ); + + expect(event.args.proposalId).to.equal(id); + expect(event.args.creator).to.equal(signers[0].address); + expect(event.args.startDate).to.equal(expectedStartDate); + expect(event.args.endDate).to.equal(expectedEndDate); + expect(event.args.metadata).to.equal(dummyMetadata); + expect(event.args.actions).to.deep.equal([]); + expect(event.args.allowFailureMap).to.equal(0); + }); + it('ceils the `minVotingPower` value if it has a remainder', async () => { votingSettings.minParticipation = pctToRatio(30).add(1); // 30.0001 %