Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silver-falcons-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`Governor`: Scrict enforcement of the expected proposal state depending on `proposalNeedsQueuing` when calling `execute`.
3 changes: 2 additions & 1 deletion contracts/governance/Governor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,11 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72
bytes32 descriptionHash
) public payable virtual returns (uint256) {
uint256 proposalId = getProposalId(targets, values, calldatas, descriptionHash);
bool needsQueueing = proposalNeedsQueuing(proposalId);

_validateStateBitmap(
proposalId,
_encodeStateBitmap(ProposalState.Succeeded) | _encodeStateBitmap(ProposalState.Queued)
_encodeStateBitmap(needsQueueing ? ProposalState.Queued : ProposalState.Succeeded)
);

// mark as executed before calls to avoid reentrancy
Expand Down
5 changes: 0 additions & 5 deletions contracts/governance/IGovernor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,6 @@ interface IGovernor is IERC165, IERC6372 {
*/
error GovernorQueueNotImplemented();

/**
* @dev The proposal hasn't been queued yet.
*/
error GovernorNotQueuedProposal(uint256 proposalId);

/**
* @dev The proposal has already been queued.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,6 @@ abstract contract GovernorTimelockCompound is Governor {
bytes32 /*descriptionHash*/
) internal virtual override {
uint256 etaSeconds = proposalEta(proposalId);
if (etaSeconds == 0) {
revert GovernorNotQueuedProposal(proposalId);
}
Comment on lines -97 to -99
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer necessary because Governor.execute enforces status = Queued

Address.sendValue(payable(_timelock), msg.value);
for (uint256 i = 0; i < targets.length; ++i) {
_timelock.executeTransaction(targets[i], values[i], "", calldatas[i], etaSeconds);
Expand Down
12 changes: 6 additions & 6 deletions test/governance/Governor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ describe('Governor', function () {
.withArgs(
this.proposal.id,
ProposalState.Active,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded]),
);
});

Expand All @@ -417,7 +417,7 @@ describe('Governor', function () {
.withArgs(
this.proposal.id,
ProposalState.Active,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded]),
);
});

Expand All @@ -430,7 +430,7 @@ describe('Governor', function () {
.withArgs(
this.proposal.id,
ProposalState.Active,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded]),
);
});

Expand Down Expand Up @@ -481,7 +481,7 @@ describe('Governor', function () {
.withArgs(
this.proposal.id,
ProposalState.Executed,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded]),
);
});
});
Expand Down Expand Up @@ -569,7 +569,7 @@ describe('Governor', function () {
.withArgs(
this.proposal.id,
ProposalState.Canceled,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded]),
);
});

Expand All @@ -587,7 +587,7 @@ describe('Governor', function () {
.withArgs(
this.proposal.id,
ProposalState.Canceled,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded]),
);
});

Expand Down
34 changes: 30 additions & 4 deletions test/governance/extensions/GovernorTimelockAccess.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,32 @@
await this.helper.setProposal([this.restricted.operation], 'descr');
await this.helper.propose();
expect(await this.mock.proposalNeedsQueuing(this.helper.currentProposal.id)).to.be.false;
await this.helper.waitForSnapshot();
await this.helper.connect(this.voter1).vote({ support: VoteType.For });
await this.helper.waitForDeadline();
await expect(this.helper.execute()).to.not.be.reverted;
});

it('does need to queue proposals with base delay', async function () {
const roleId = 1n;
const executionDelay = 0n;
const baseDelay = 10n;

// Set execution delay
await this.manager.connect(this.admin).setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId);
await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay);

// Set base delay
await this.mock.$_setBaseDelaySeconds(baseDelay);

await this.helper.setProposal([this.restricted.operation], 'descr');
await this.helper.propose();
expect(await this.mock.proposalNeedsQueuing(this.helper.currentProposal.id)).to.be.true;
await this.helper.waitForSnapshot();
await this.helper.connect(this.voter1).vote({ support: VoteType.For });
await this.helper.waitForDeadline();
// Not queueud, so it should revert

Check failure on line 265 in test/governance/extensions/GovernorTimelockAccess.test.js

View workflow job for this annotation

GitHub Actions / codespell

queueud ==> queued
await expect(this.helper.execute()).to.be.reverted;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this change, this would not have reverted

});

it('needs to queue proposals with any delay', async function () {
Expand Down Expand Up @@ -556,7 +582,7 @@
.withArgs(
this.proposal.id,
ProposalState.Canceled,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Queued]), // proposal needs queueing
);
});

Expand Down Expand Up @@ -602,7 +628,7 @@
.withArgs(
original.currentProposal.id,
ProposalState.Canceled,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Queued]), // proposal needs queueing
);
});

Expand All @@ -628,7 +654,7 @@
.withArgs(
this.proposal.id,
ProposalState.Canceled,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded]), // not queueing necessary
);
});

Expand All @@ -649,7 +675,7 @@
.withArgs(
this.proposal.id,
ProposalState.Canceled,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded]), // not queueing necessary
);
});

Expand Down
24 changes: 15 additions & 9 deletions test/governance/extensions/GovernorTimelockCompound.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,12 @@ describe('GovernorTimelockCompound', function () {
.to.be.revertedWithCustomError(this.mock, 'GovernorAlreadyQueuedProposal')
.withArgs(id);
await expect(this.helper.execute())
.to.be.revertedWithCustomError(this.mock, 'GovernorNotQueuedProposal')
.withArgs(id);
.to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
.withArgs(
this.proposal.id,
ProposalState.Succeeded,
GovernorHelper.proposalStatesToBitMap([ProposalState.Queued]),
);
});
});

Expand All @@ -168,11 +172,13 @@ describe('GovernorTimelockCompound', function () {
await this.helper.connect(this.voter1).vote({ support: VoteType.For });
await this.helper.waitForDeadline(1n);

expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Succeeded);

await expect(this.helper.execute())
.to.be.revertedWithCustomError(this.mock, 'GovernorNotQueuedProposal')
.withArgs(this.proposal.id);
.to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
.withArgs(
this.proposal.id,
ProposalState.Succeeded,
GovernorHelper.proposalStatesToBitMap([ProposalState.Queued]),
);
});

it('if too early', async function () {
Expand Down Expand Up @@ -204,7 +210,7 @@ describe('GovernorTimelockCompound', function () {
.withArgs(
this.proposal.id,
ProposalState.Expired,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Queued]),
);
});

Expand All @@ -222,7 +228,7 @@ describe('GovernorTimelockCompound', function () {
.withArgs(
this.proposal.id,
ProposalState.Executed,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Queued]),
);
});
});
Expand Down Expand Up @@ -317,7 +323,7 @@ describe('GovernorTimelockCompound', function () {
.withArgs(
this.proposal.id,
ProposalState.Canceled,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Queued]),
);
});
});
Expand Down
16 changes: 9 additions & 7 deletions test/governance/extensions/GovernorTimelockControl.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,13 @@ describe('GovernorTimelockControl', function () {
await this.helper.connect(this.voter1).vote({ support: VoteType.For });
await this.helper.waitForDeadline(1n);

expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Succeeded);

await expect(this.helper.execute())
.to.be.revertedWithCustomError(this.timelock, 'TimelockUnexpectedOperationState')
.withArgs(this.proposal.timelockid, GovernorHelper.proposalStatesToBitMap(OperationState.Ready));
.to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
.withArgs(
this.proposal.id,
ProposalState.Succeeded,
GovernorHelper.proposalStatesToBitMap([ProposalState.Queued]),
);
});

it('if too early', async function () {
Expand Down Expand Up @@ -201,7 +203,7 @@ describe('GovernorTimelockControl', function () {
.withArgs(
this.proposal.id,
ProposalState.Executed,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Queued]),
);
});

Expand All @@ -224,7 +226,7 @@ describe('GovernorTimelockControl', function () {
.withArgs(
this.proposal.id,
ProposalState.Executed,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Queued]),
);
});
});
Expand Down Expand Up @@ -270,7 +272,7 @@ describe('GovernorTimelockControl', function () {
.withArgs(
this.proposal.id,
ProposalState.Canceled,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Queued]),
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe('GovernorVotesQuorumFraction', function () {
.withArgs(
this.proposal.id,
ProposalState.Defeated,
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded]),
);
});

Expand Down
Loading