Skip to content

Commit da89c43

Browse files
Amxxernestognwfrangio
authored
Pack Governor's ProposalCore into a single slot (#4268)
Co-authored-by: Ernesto García <ernestognw@gmail.com> Co-authored-by: Francisco <fg@frang.io>
1 parent a7a94c7 commit da89c43

6 files changed

+43
-34
lines changed

.changeset/grumpy-bulldogs-call.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': major
3+
---
4+
5+
`Governor`: Optimized use of storage for proposal data

contracts/governance/Governor.sol

+12-18
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive
3434

3535
// solhint-disable var-name-mixedcase
3636
struct ProposalCore {
37-
// --- start retyped from Timers.BlockNumber at offset 0x00 ---
38-
uint64 voteStart;
3937
address proposer;
40-
bytes4 __gap_unused0;
41-
// --- start retyped from Timers.BlockNumber at offset 0x20 ---
42-
uint64 voteEnd;
43-
bytes24 __gap_unused1;
44-
// --- Remaining fields starting at offset 0x40 ---------------
38+
uint48 voteStart;
39+
uint32 voteDuration;
4540
bool executed;
4641
bool canceled;
4742
}
@@ -166,7 +161,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive
166161
* @dev See {IGovernor-state}.
167162
*/
168163
function state(uint256 proposalId) public view virtual override returns (ProposalState) {
169-
ProposalCore storage proposal = _proposals[proposalId];
164+
// ProposalCore is just one slot. We can load it from storage to memory with a single sload and use memory
165+
// object as a cache. This avoid duplicating expensive sloads.
166+
ProposalCore memory proposal = _proposals[proposalId];
170167

171168
if (proposal.executed) {
172169
return ProposalState.Executed;
@@ -219,7 +216,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive
219216
* @dev See {IGovernor-proposalDeadline}.
220217
*/
221218
function proposalDeadline(uint256 proposalId) public view virtual override returns (uint256) {
222-
return _proposals[proposalId].voteEnd;
219+
return _proposals[proposalId].voteStart + _proposals[proposalId].voteDuration;
223220
}
224221

225222
/**
@@ -300,16 +297,14 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive
300297
}
301298

302299
uint256 snapshot = currentTimepoint + votingDelay();
303-
uint256 deadline = snapshot + votingPeriod();
300+
uint256 duration = votingPeriod();
304301

305302
_proposals[proposalId] = ProposalCore({
306303
proposer: proposer,
307-
voteStart: SafeCast.toUint64(snapshot),
308-
voteEnd: SafeCast.toUint64(deadline),
304+
voteStart: SafeCast.toUint48(snapshot),
305+
voteDuration: SafeCast.toUint32(duration),
309306
executed: false,
310-
canceled: false,
311-
__gap_unused0: 0,
312-
__gap_unused1: 0
307+
canceled: false
313308
});
314309

315310
emit ProposalCreated(
@@ -320,7 +315,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive
320315
new string[](targets.length),
321316
calldatas,
322317
snapshot,
323-
deadline,
318+
snapshot + duration,
324319
description
325320
);
326321

@@ -592,13 +587,12 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive
592587
string memory reason,
593588
bytes memory params
594589
) internal virtual returns (uint256) {
595-
ProposalCore storage proposal = _proposals[proposalId];
596590
ProposalState currentState = state(proposalId);
597591
if (currentState != ProposalState.Active) {
598592
revert GovernorUnexpectedProposalState(proposalId, currentState, _encodeStateBitmap(ProposalState.Active));
599593
}
600594

601-
uint256 weight = _getVotes(account, proposal.voteStart, params);
595+
uint256 weight = _getVotes(account, proposalSnapshot(proposalId), params);
602596
_countVote(proposalId, account, support, weight, params);
603597

604598
if (params.length == 0) {

contracts/governance/IGovernor.sol

+7
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ abstract contract IGovernor is IERC165, IERC6372 {
222222
*
223223
* This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a
224224
* proposal starts.
225+
*
226+
* NOTE: While this interface returns a uint256, timepoints are stored as uint48 following the ERC-6372 clock type.
227+
* Consequently this value must fit in a uint48 (when added to the current clock). See {IERC6372-clock}.
225228
*/
226229
function votingDelay() public view virtual returns (uint256);
227230

@@ -232,6 +235,10 @@ abstract contract IGovernor is IERC165, IERC6372 {
232235
*
233236
* NOTE: The {votingDelay} can delay the start of the vote. This must be considered when setting the voting
234237
* duration compared to the voting delay.
238+
*
239+
* NOTE: This value is stored when the proposal is submitted so that possible changes to the value do not affect
240+
* proposals that have already been submitted. The type used to save it is a uint32. Consequently, while this
241+
* interface returns a uint256, the value it returns should fit in a uint32.
235242
*/
236243
function votingPeriod() public view virtual returns (uint256);
237244

contracts/governance/extensions/GovernorPreventLateQuorum.sol

+7-7
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import "../../utils/math/Math.sol";
1818
* _Available since v4.5._
1919
*/
2020
abstract contract GovernorPreventLateQuorum is Governor {
21-
uint64 private _voteExtension;
21+
uint48 private _voteExtension;
2222

2323
/// @custom:oz-retyped-from mapping(uint256 => Timers.BlockNumber)
24-
mapping(uint256 => uint64) private _extendedDeadlines;
24+
mapping(uint256 => uint48) private _extendedDeadlines;
2525

2626
/// @dev Emitted when a proposal deadline is pushed back due to reaching quorum late in its voting period.
2727
event ProposalExtended(uint256 indexed proposalId, uint64 extendedDeadline);
@@ -34,7 +34,7 @@ abstract contract GovernorPreventLateQuorum is Governor {
3434
* clock mode) that is required to pass since the moment a proposal reaches quorum until its voting period ends. If
3535
* necessary the voting period will be extended beyond the one set during proposal creation.
3636
*/
37-
constructor(uint64 initialVoteExtension) {
37+
constructor(uint48 initialVoteExtension) {
3838
_setLateQuorumVoteExtension(initialVoteExtension);
3939
}
4040

@@ -62,7 +62,7 @@ abstract contract GovernorPreventLateQuorum is Governor {
6262
uint256 result = super._castVote(proposalId, account, support, reason, params);
6363

6464
if (_extendedDeadlines[proposalId] == 0 && _quorumReached(proposalId)) {
65-
uint64 extendedDeadline = clock() + lateQuorumVoteExtension();
65+
uint48 extendedDeadline = clock() + lateQuorumVoteExtension();
6666

6767
if (extendedDeadline > proposalDeadline(proposalId)) {
6868
emit ProposalExtended(proposalId, extendedDeadline);
@@ -78,7 +78,7 @@ abstract contract GovernorPreventLateQuorum is Governor {
7878
* @dev Returns the current value of the vote extension parameter: the number of blocks that are required to pass
7979
* from the time a proposal reaches quorum until its voting period ends.
8080
*/
81-
function lateQuorumVoteExtension() public view virtual returns (uint64) {
81+
function lateQuorumVoteExtension() public view virtual returns (uint48) {
8282
return _voteExtension;
8383
}
8484

@@ -88,7 +88,7 @@ abstract contract GovernorPreventLateQuorum is Governor {
8888
*
8989
* Emits a {LateQuorumVoteExtensionSet} event.
9090
*/
91-
function setLateQuorumVoteExtension(uint64 newVoteExtension) public virtual onlyGovernance {
91+
function setLateQuorumVoteExtension(uint48 newVoteExtension) public virtual onlyGovernance {
9292
_setLateQuorumVoteExtension(newVoteExtension);
9393
}
9494

@@ -98,7 +98,7 @@ abstract contract GovernorPreventLateQuorum is Governor {
9898
*
9999
* Emits a {LateQuorumVoteExtensionSet} event.
100100
*/
101-
function _setLateQuorumVoteExtension(uint64 newVoteExtension) internal virtual {
101+
function _setLateQuorumVoteExtension(uint48 newVoteExtension) internal virtual {
102102
emit LateQuorumVoteExtensionSet(_voteExtension, newVoteExtension);
103103
_voteExtension = newVoteExtension;
104104
}

contracts/governance/extensions/GovernorSettings.sol

+10-7
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ import "../Governor.sol";
1111
* _Available since v4.4._
1212
*/
1313
abstract contract GovernorSettings is Governor {
14-
uint256 private _votingDelay;
15-
uint256 private _votingPeriod;
14+
// amount of token
1615
uint256 private _proposalThreshold;
16+
// timepoint: limited to uint48 in core (same as clock() type)
17+
uint48 private _votingDelay;
18+
// duration: limited to uint32 in core
19+
uint32 private _votingPeriod;
1720

1821
event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay);
1922
event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod);
@@ -22,7 +25,7 @@ abstract contract GovernorSettings is Governor {
2225
/**
2326
* @dev Initialize the governance parameters.
2427
*/
25-
constructor(uint256 initialVotingDelay, uint256 initialVotingPeriod, uint256 initialProposalThreshold) {
28+
constructor(uint48 initialVotingDelay, uint32 initialVotingPeriod, uint256 initialProposalThreshold) {
2629
_setVotingDelay(initialVotingDelay);
2730
_setVotingPeriod(initialVotingPeriod);
2831
_setProposalThreshold(initialProposalThreshold);
@@ -54,7 +57,7 @@ abstract contract GovernorSettings is Governor {
5457
*
5558
* Emits a {VotingDelaySet} event.
5659
*/
57-
function setVotingDelay(uint256 newVotingDelay) public virtual onlyGovernance {
60+
function setVotingDelay(uint48 newVotingDelay) public virtual onlyGovernance {
5861
_setVotingDelay(newVotingDelay);
5962
}
6063

@@ -63,7 +66,7 @@ abstract contract GovernorSettings is Governor {
6366
*
6467
* Emits a {VotingPeriodSet} event.
6568
*/
66-
function setVotingPeriod(uint256 newVotingPeriod) public virtual onlyGovernance {
69+
function setVotingPeriod(uint32 newVotingPeriod) public virtual onlyGovernance {
6770
_setVotingPeriod(newVotingPeriod);
6871
}
6972

@@ -81,7 +84,7 @@ abstract contract GovernorSettings is Governor {
8184
*
8285
* Emits a {VotingDelaySet} event.
8386
*/
84-
function _setVotingDelay(uint256 newVotingDelay) internal virtual {
87+
function _setVotingDelay(uint48 newVotingDelay) internal virtual {
8588
emit VotingDelaySet(_votingDelay, newVotingDelay);
8689
_votingDelay = newVotingDelay;
8790
}
@@ -91,7 +94,7 @@ abstract contract GovernorSettings is Governor {
9194
*
9295
* Emits a {VotingPeriodSet} event.
9396
*/
94-
function _setVotingPeriod(uint256 newVotingPeriod) internal virtual {
97+
function _setVotingPeriod(uint32 newVotingPeriod) internal virtual {
9598
// voting period must be at least one block long
9699
if (newVotingPeriod == 0) {
97100
revert GovernorInvalidVotingPeriod(0);

contracts/governance/extensions/GovernorTimelockCompound.sol

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor {
2424
ICompoundTimelock private _timelock;
2525

2626
/// @custom:oz-retyped-from mapping(uint256 => GovernorTimelockCompound.ProposalTimelock)
27-
mapping(uint256 => uint64) private _proposalTimelocks;
27+
mapping(uint256 => uint256) private _proposalTimelocks;
2828

2929
/**
3030
* @dev Emitted when the timelock controller used for proposal execution is modified.
@@ -100,7 +100,7 @@ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor {
100100
}
101101

102102
uint256 eta = block.timestamp + _timelock.delay();
103-
_proposalTimelocks[proposalId] = SafeCast.toUint64(eta);
103+
_proposalTimelocks[proposalId] = eta;
104104

105105
for (uint256 i = 0; i < targets.length; ++i) {
106106
if (_timelock.queuedTransactions(keccak256(abi.encode(targets[i], values[i], "", calldatas[i], eta)))) {

0 commit comments

Comments
 (0)