Skip to content

Commit 03e06bf

Browse files
arr00Amxxernestognw
authored
Add GovernorSequentialProposalId extension for sequential numbers on proposals (#5290)
Co-authored-by: Hadrien Croubois <[email protected]> Co-authored-by: Ernesto García <[email protected]>
1 parent 3b240d7 commit 03e06bf

File tree

10 files changed

+405
-48
lines changed

10 files changed

+405
-48
lines changed

.changeset/brave-islands-sparkle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`GovernorSequentialProposalId`: Adds a `Governor` extension that sequentially numbers proposal ids instead of using the hash.

.changeset/ten-fishes-fold.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`IGovernor`: Add the `getProposalId` function to the governor interface.

contracts/governance/Governor.sol

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72
9292
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
9393
return
9494
interfaceId == type(IGovernor).interfaceId ||
95+
interfaceId == type(IGovernor).interfaceId ^ IGovernor.getProposalId.selector ||
9596
interfaceId == type(IERC1155Receiver).interfaceId ||
9697
super.supportsInterface(interfaceId);
9798
}
@@ -132,6 +133,18 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72
132133
return uint256(keccak256(abi.encode(targets, values, calldatas, descriptionHash)));
133134
}
134135

136+
/**
137+
* @dev See {IGovernor-getProposalId}.
138+
*/
139+
function getProposalId(
140+
address[] memory targets,
141+
uint256[] memory values,
142+
bytes[] memory calldatas,
143+
bytes32 descriptionHash
144+
) public view virtual returns (uint256) {
145+
return hashProposal(targets, values, calldatas, descriptionHash);
146+
}
147+
135148
/**
136149
* @dev See {IGovernor-state}.
137150
*/
@@ -317,7 +330,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72
317330
string memory description,
318331
address proposer
319332
) internal virtual returns (uint256 proposalId) {
320-
proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description)));
333+
proposalId = getProposalId(targets, values, calldatas, keccak256(bytes(description)));
321334

322335
if (targets.length != values.length || targets.length != calldatas.length || targets.length == 0) {
323336
revert GovernorInvalidProposalLength(targets.length, calldatas.length, values.length);
@@ -358,7 +371,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72
358371
bytes[] memory calldatas,
359372
bytes32 descriptionHash
360373
) public virtual returns (uint256) {
361-
uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);
374+
uint256 proposalId = getProposalId(targets, values, calldatas, descriptionHash);
362375

363376
_validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Succeeded));
364377

@@ -406,7 +419,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72
406419
bytes[] memory calldatas,
407420
bytes32 descriptionHash
408421
) public payable virtual returns (uint256) {
409-
uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);
422+
uint256 proposalId = getProposalId(targets, values, calldatas, descriptionHash);
410423

411424
_validateStateBitmap(
412425
proposalId,
@@ -468,8 +481,8 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72
468481
) public virtual returns (uint256) {
469482
// The proposalId will be recomputed in the `_cancel` call further down. However we need the value before we
470483
// do the internal call, because we need to check the proposal state BEFORE the internal `_cancel` call
471-
// changes it. The `hashProposal` duplication has a cost that is limited, and that we accept.
472-
uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);
484+
// changes it. The `getProposalId` duplication has a cost that is limited, and that we accept.
485+
uint256 proposalId = getProposalId(targets, values, calldatas, descriptionHash);
473486

474487
// public cancel restrictions (on top of existing _cancel restrictions).
475488
_validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Pending));
@@ -492,7 +505,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72
492505
bytes[] memory calldatas,
493506
bytes32 descriptionHash
494507
) internal virtual returns (uint256) {
495-
uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);
508+
uint256 proposalId = getProposalId(targets, values, calldatas, descriptionHash);
496509

497510
_validateStateBitmap(
498511
proposalId,

contracts/governance/IGovernor.sol

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,9 @@ interface IGovernor is IERC165, IERC6372 {
203203

204204
/**
205205
* @notice module:core
206-
* @dev Hashing function used to (re)build the proposal id from the proposal details..
206+
* @dev Hashing function used to (re)build the proposal id from the proposal details.
207+
*
208+
* NOTE: For all off-chain and external calls, use {getProposalId}.
207209
*/
208210
function hashProposal(
209211
address[] memory targets,
@@ -212,6 +214,17 @@ interface IGovernor is IERC165, IERC6372 {
212214
bytes32 descriptionHash
213215
) external pure returns (uint256);
214216

217+
/**
218+
* @notice module:core
219+
* @dev Function used to get the proposal id from the proposal details.
220+
*/
221+
function getProposalId(
222+
address[] memory targets,
223+
uint256[] memory values,
224+
bytes[] memory calldatas,
225+
bytes32 descriptionHash
226+
) external view returns (uint256);
227+
215228
/**
216229
* @notice module:core
217230
* @dev Current state of a proposal, following Compound's convention
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {Governor} from "../Governor.sol";
6+
7+
/**
8+
* @dev Extension of {Governor} that changes the numbering of proposal ids from the default hash-based approach to
9+
* sequential ids.
10+
*/
11+
abstract contract GovernorSequentialProposalId is Governor {
12+
uint256 private _latestProposalId;
13+
mapping(uint256 proposalHash => uint256 proposalId) private _proposalIds;
14+
15+
/**
16+
* @dev The {latestProposalId} may only be initialized if it hasn't been set yet
17+
* (through initialization or the creation of a proposal).
18+
*/
19+
error GovernorAlreadyInitializedLatestProposalId();
20+
21+
/**
22+
* @dev See {IGovernor-getProposalId}.
23+
*/
24+
function getProposalId(
25+
address[] memory targets,
26+
uint256[] memory values,
27+
bytes[] memory calldatas,
28+
bytes32 descriptionHash
29+
) public view virtual override returns (uint256) {
30+
uint256 proposalHash = hashProposal(targets, values, calldatas, descriptionHash);
31+
uint256 storedProposalId = _proposalIds[proposalHash];
32+
if (storedProposalId == 0) {
33+
revert GovernorNonexistentProposal(0);
34+
}
35+
return storedProposalId;
36+
}
37+
38+
/**
39+
* @dev Returns the latest proposal id. A return value of 0 means no proposals have been created yet.
40+
*/
41+
function latestProposalId() public view virtual returns (uint256) {
42+
return _latestProposalId;
43+
}
44+
45+
/**
46+
* @dev See {IGovernor-_propose}.
47+
* Hook into the proposing mechanism to increment proposal count.
48+
*/
49+
function _propose(
50+
address[] memory targets,
51+
uint256[] memory values,
52+
bytes[] memory calldatas,
53+
string memory description,
54+
address proposer
55+
) internal virtual override returns (uint256) {
56+
uint256 proposalHash = hashProposal(targets, values, calldatas, keccak256(bytes(description)));
57+
uint256 storedProposalId = _proposalIds[proposalHash];
58+
if (storedProposalId == 0) {
59+
_proposalIds[proposalHash] = ++_latestProposalId;
60+
}
61+
return super._propose(targets, values, calldatas, description, proposer);
62+
}
63+
64+
/**
65+
* @dev Internal function to set the {latestProposalId}. This function is helpful when transitioning
66+
* from another governance system. The next proposal id will be `newLatestProposalId` + 1.
67+
*
68+
* May only call this function if the current value of {latestProposalId} is 0.
69+
*/
70+
function _initializeLatestProposalId(uint256 newLatestProposalId) internal virtual {
71+
if (_latestProposalId != 0) {
72+
revert GovernorAlreadyInitializedLatestProposalId();
73+
}
74+
_latestProposalId = newLatestProposalId;
75+
}
76+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {Governor} from "../../governance/Governor.sol";
6+
import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol";
7+
import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol";
8+
import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol";
9+
import {GovernorSequentialProposalId} from "../../governance/extensions/GovernorSequentialProposalId.sol";
10+
11+
abstract contract GovernorSequentialProposalIdMock is
12+
GovernorSettings,
13+
GovernorVotesQuorumFraction,
14+
GovernorCountingSimple,
15+
GovernorSequentialProposalId
16+
{
17+
function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {
18+
return super.proposalThreshold();
19+
}
20+
21+
function getProposalId(
22+
address[] memory targets,
23+
uint256[] memory values,
24+
bytes[] memory calldatas,
25+
bytes32 descriptionHash
26+
) public view virtual override(Governor, GovernorSequentialProposalId) returns (uint256) {
27+
return super.getProposalId(targets, values, calldatas, descriptionHash);
28+
}
29+
30+
function _propose(
31+
address[] memory targets,
32+
uint256[] memory values,
33+
bytes[] memory calldatas,
34+
string memory description,
35+
address proposer
36+
) internal virtual override(Governor, GovernorSequentialProposalId) returns (uint256 proposalId) {
37+
return super._propose(targets, values, calldatas, description, proposer);
38+
}
39+
}

test/governance/Governor.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ describe('Governor', function () {
9696
);
9797
});
9898

99-
shouldSupportInterfaces(['ERC1155Receiver', 'Governor']);
99+
shouldSupportInterfaces(['ERC1155Receiver', 'Governor', 'Governor_5_3']);
100100
shouldBehaveLikeERC6372(mode);
101101

102102
it('deployment check', async function () {

0 commit comments

Comments
 (0)