22
33pragma solidity ^ 0.8.8 ;
44
5+ import {DAO, IDAO, Action} from "@aragon/osx/core/dao/DAO.sol " ;
6+ import {PluginUUPSUpgradeable} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol " ;
57import {IVotesUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/utils/IVotesUpgradeable.sol " ;
68import {SafeCastUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol " ;
79import {IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol " ;
10+ import {IERC6372Upgradeable } from "@openzeppelin/contracts-upgradeable/interfaces/IERC6372Upgradeable.sol " ;
811
912import {IMembership} from "@aragon/osx-commons-contracts/src/plugin/extensions/membership/IMembership.sol " ;
1013import {_applyRatioCeiled} from "@aragon/osx-commons-contracts/src/utils/math/Ratio.sol " ;
@@ -20,7 +23,7 @@ import {MajorityVotingBase} from "./base/MajorityVotingBase.sol";
2023/// @notice The majority voting implementation using an
2124/// [OpenZeppelin `Votes`](https://docs.openzeppelin.com/contracts/4.x/api/governance#Votes)
2225/// compatible governance token.
23- /// @dev v1.3 (Release 1, Build 3 ). For each upgrade, if the reinitialization step is required,
26+ /// @dev v1.4 (Release 1, Build 4 ). For each upgrade, if the reinitialization step is required,
2427/// increment the version numbers in the modifier for both the initialize and initializeFrom functions.
2528/// @custom:security-contact sirt@aragon.org
2629contract TokenVoting is IMembership , MajorityVotingBase {
@@ -33,6 +36,9 @@ contract TokenVoting is IMembership, MajorityVotingBase {
3336 /// compatible contract referencing the token being used for voting.
3437 IVotesUpgradeable private votingToken;
3538
39+ /// @notice Wether the token contract indexes past voting power by timestamp.
40+ bool private tokenIndexedByTimestamp;
41+
3642 /// @notice Thrown if the voting power is zero
3743 error NoVotingPower ();
3844
@@ -54,11 +60,28 @@ contract TokenVoting is IMembership, MajorityVotingBase {
5460 TargetConfig calldata _targetConfig ,
5561 uint256 _minApprovals ,
5662 bytes calldata _pluginMetadata
57- ) external onlyCallAtInitialization reinitializer (2 ) {
63+ ) external onlyCallAtInitialization reinitializer (3 ) {
5864 __MajorityVotingBase_init (_dao, _votingSettings, _targetConfig, _minApprovals, _pluginMetadata);
5965
6066 votingToken = _token;
6167
68+ // Check if the given token indexes past voting power by blocks or by timestamp
69+ try IERC6372Upgradeable (address (_token)).CLOCK_MODE () returns (string memory ms ) {
70+ if (keccak256 (bytes (ms)) == keccak256 (bytes ("mode=timestamp&version=1 " ))) {
71+ tokenIndexedByTimestamp = true ;
72+ }
73+ } catch {
74+ // CLOCK_MODE() not found, reverted, or other issue.
75+ try IERC6372Upgradeable (address (_token)).clock () returns (uint48 cv ) {
76+ if (cv == block .timestamp ) {
77+ tokenIndexedByTimestamp = true ;
78+ }
79+ } catch {
80+ // clock() not found, reverted, or other issue.
81+ // Assuming that the token indexes by block number
82+ }
83+ }
84+
6285 emit MembershipContractAnnounced ({definingContract: address (_token)});
6386 }
6487
@@ -70,7 +93,7 @@ contract TokenVoting is IMembership, MajorityVotingBase {
7093 /// @param _fromBuild Build version number of previous implementation contract this upgrade is transitioning from.
7194 /// @param _initData The initialization data to be passed to via `upgradeToAndCall`
7295 /// (see [ERC-1967](https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Upgrade)).
73- function initializeFrom (uint16 _fromBuild , bytes calldata _initData ) external reinitializer (2 ) {
96+ function initializeFrom (uint16 _fromBuild , bytes calldata _initData ) external reinitializer (3 ) {
7497 if (_fromBuild < 3 ) {
7598 (uint256 minApprovals , TargetConfig memory targetConfig , bytes memory pluginMetadata ) =
7699 abi.decode (_initData, (uint256 , TargetConfig, bytes ));
@@ -115,14 +138,18 @@ contract TokenVoting is IMembership, MajorityVotingBase {
115138 VoteOption _voteOption ,
116139 bool _tryEarlyExecution
117140 ) public override auth (CREATE_PROPOSAL_PERMISSION_ID) returns (uint256 proposalId ) {
118- uint256 snapshotBlock ;
141+ uint256 snapshotTimepoint ;
119142 unchecked {
120- // The snapshot block must be mined already to
121- // protect the transaction against backrunning transactions causing census changes.
122- snapshotBlock = block .number - 1 ;
143+ // The time point must be already mined (block) or in the past (timestamp) to
144+ // protect against backrunning transactions causing census changes.
145+ if (tokenIndexedByTimestamp) {
146+ snapshotTimepoint = block .timestamp - 1 ;
147+ } else {
148+ snapshotTimepoint = block .number - 1 ;
149+ }
123150 }
124151
125- uint256 totalVotingPower_ = totalVotingPower (snapshotBlock );
152+ uint256 totalVotingPower_ = totalVotingPower (snapshotTimepoint );
126153
127154 if (totalVotingPower_ == 0 ) {
128155 revert NoVotingPower ();
@@ -135,13 +162,13 @@ contract TokenVoting is IMembership, MajorityVotingBase {
135162 // Store proposal related information
136163 Proposal storage proposal_ = proposals[proposalId];
137164
138- if (proposal_.parameters.snapshotBlock != 0 ) {
165+ if (proposal_.parameters.snapshotTimepoint != 0 ) {
139166 revert ProposalAlreadyExists (proposalId);
140167 }
141168
142169 proposal_.parameters.startDate = _startDate;
143170 proposal_.parameters.endDate = _endDate;
144- proposal_.parameters.snapshotBlock = snapshotBlock .toUint64 ();
171+ proposal_.parameters.snapshotTimepoint = snapshotTimepoint .toUint64 ();
145172 proposal_.parameters.votingMode = votingMode ();
146173 proposal_.parameters.supportThreshold = supportThreshold ();
147174 proposal_.parameters.minVotingPower = _applyRatioCeiled (totalVotingPower_, minParticipation ());
@@ -209,7 +236,7 @@ contract TokenVoting is IMembership, MajorityVotingBase {
209236 Proposal storage proposal_ = proposals[_proposalId];
210237
211238 // This could re-enter, though we can assume the governance token is not malicious
212- uint256 votingPower = votingToken.getPastVotes (_voter, proposal_.parameters.snapshotBlock );
239+ uint256 votingPower = votingToken.getPastVotes (_voter, proposal_.parameters.snapshotTimepoint );
213240 VoteOption state = proposal_.voters[_voter];
214241
215242 // If voter had previously voted, decrease count
@@ -266,7 +293,7 @@ contract TokenVoting is IMembership, MajorityVotingBase {
266293 }
267294
268295 // The voter has no voting power.
269- if (votingToken.getPastVotes (_account, proposal_.parameters.snapshotBlock ) == 0 ) {
296+ if (votingToken.getPastVotes (_account, proposal_.parameters.snapshotTimepoint ) == 0 ) {
270297 return false ;
271298 }
272299
@@ -296,5 +323,5 @@ contract TokenVoting is IMembership, MajorityVotingBase {
296323 /// @dev This empty reserved space is put in place to allow future versions to add new
297324 /// variables without shifting down storage in the inheritance chain.
298325 /// https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
299- uint256 [49 ] private __gap;
326+ uint256 [48 ] private __gap;
300327}
0 commit comments