1- // SPDX-License-Identifier: GNU
1+ // SPDX-License-Identifier: GPL-3.0-or-later
22pragma solidity ^ 0.8.24 ;
33
4- import "@openzeppelin/contracts/access/AccessControlEnumerable.sol " ;
4+ import { AccessControlEnumerable } from "@openzeppelin/contracts/access/extensions /AccessControlEnumerable.sol " ;
55
66
7- contract TaskCreatorAccess is AccessControlEnumerable {
7+ contract TaskAccess is AccessControlEnumerable {
88
9- EnumerableSet.AddressSet private _candidates; // candidates under voting
109 mapping (address => uint256 ) public voteCount; /// vote count of each task creator
11- mapping (address => mapping (address => bool )) public hasVoted; // voter => candidate => voted
10+ mapping (address => uint256 ) public candidateEpoch; /// epoch of each candidate
11+ mapping (address => mapping (address => uint256 )) public lastVotedEpoch; /// last epoch a voter voted for a candidate
1212
1313 bytes32 public constant TASK_CREATOR_ROLE = keccak256 ("TASK_CREATOR_ROLE " );
1414
15- event CandidateProposed (address indexed candidate );
1615 event Voted (address indexed voter , address indexed candidate , uint256 votes );
1716 event Unvoted (address indexed voter , address indexed candidate , uint256 votes );
1817 event TaskCreatorAdded (address indexed newCreator );
1918 event TaskCreatorRemoved (address indexed exCreator );
19+ event AdminAdded (address indexed newAdmin );
20+ event AdminRemoved (address indexed exAdmin );
2021
2122
22- constructor (address initialCreator ) {
23+ constructor () {
2324 _grantRole (DEFAULT_ADMIN_ROLE, msg .sender );
2425 }
2526
26- /// @dev Check if an address is a TaskCreator
27- function isTaskCreator (address account ) public view returns (bool ) {
28- return hasRole (TASK_CREATOR_ROLE, account);
29- }
30-
31- /// @dev Get the number of TaskCreators
32- function taskCreatorCount () public view returns (uint256 ) {
33- return getRoleMemberCount (TASK_CREATOR_ROLE);
34- }
3527
3628 /// @dev Dynamic voting threshold based on number of TaskCreators
3729 function threshold () public view returns (uint256 ) {
3830 uint256 n = taskCreatorCount ();
3931 if (n <= 20 ) {
4032 return (n / 2 ) + 1 ; // majority
4133 } else if (n < 200 ) {
42- return n / 3 ; // ~1/3 when moderately sized
34+ return (n + 2 ) / 3 ; // ~1/3 when moderately sized
4335 } else {
4436 return 100 ; // capped threshold
4537 }
4638 }
4739
40+ /// TaskCreator FUNCTIONS
41+
4842 /// @dev Vote for a new TaskCreator
4943 function voteForTaskCreator (address candidate ) external onlyRole (TASK_CREATOR_ROLE) {
44+ require (candidate != address (0 ), "Zero address " );
45+ require (candidate != msg .sender , "Self-vote not allowed " );
5046 require (! hasRole (TASK_CREATOR_ROLE, candidate), "Already TaskCreator " );
51- require (! hasVoted [msg .sender ][candidate], "Already voted " );
47+ require (lastVotedEpoch [msg .sender ][candidate] < candidateEpoch [candidate], "Already voted " );
5248
53- hasVoted [msg .sender ][candidate] = true ;
49+ lastVotedEpoch [msg .sender ][candidate] = candidateEpoch[candidate] ;
5450 voteCount[candidate]++ ;
5551
5652 emit Voted (msg .sender , candidate, voteCount[candidate]);
5753
5854 if (voteCount[candidate] >= threshold ()) {
55+ candidateEpoch[candidate] += 1 ;
56+ voteCount[candidate] = 0 ; // reset votes
5957 _grantRole (TASK_CREATOR_ROLE, candidate);
6058 emit TaskCreatorAdded (candidate);
6159 }
6260 }
6361
6462 /// @dev Remove vote for a TaskCreator
65- function unvoteTaskCreator (address candidate ) external {
66- require (voteCount[ candidate] > 0 , "Candidate has no votes " );
67- require (hasVoted [msg .sender ][candidate], "Not voted " );
63+ function unvoteTaskCreator (address candidate ) external onlyRole (TASK_CREATOR_ROLE) {
64+ require (candidate != address ( 0 ) , "Zero address " );
65+ require (lastVotedEpoch [msg .sender ][candidate] == candidateEpoch[candidate] , "Epoch mismatch " );
6866
69- hasVoted [msg .sender ][candidate] = false ;
67+ lastVotedEpoch [msg .sender ][candidate] = 0 ; // reset epoch
7068 voteCount[candidate]-- ;
7169
7270 emit Unvoted (msg .sender , candidate, voteCount[candidate]);
7371 }
7472
73+ /// @dev Check if an address is a TaskCreator
74+ function isTaskCreator (address account ) public view returns (bool ) {
75+ return hasRole (TASK_CREATOR_ROLE, account);
76+ }
77+
78+ /// @dev Get the number of TaskCreators
79+ function taskCreatorCount () public view returns (uint256 ) {
80+ return getRoleMemberCount (TASK_CREATOR_ROLE);
81+ }
82+
83+ function getTaskCreators () public view returns (address [] memory ) {
84+ return getRoleMembers (TASK_CREATOR_ROLE);
85+ }
86+
87+ /// ADMIN FUNCTIONS
88+
7589 /// @dev Directly grant TaskCreator role
7690 function addTaskCreator (address account ) external onlyRole (DEFAULT_ADMIN_ROLE) {
7791 _grantRole (TASK_CREATOR_ROLE, account);
@@ -87,10 +101,20 @@ contract TaskCreatorAccess is AccessControlEnumerable {
87101 /// @dev Add new Admin
88102 function addAdmin (address account ) external onlyRole (DEFAULT_ADMIN_ROLE) {
89103 _grantRole (DEFAULT_ADMIN_ROLE, account);
104+ emit AdminAdded (account);
90105 }
91106
92107 /// @dev Remove Admin
93108 function removeAdmin (address account ) external onlyRole (DEFAULT_ADMIN_ROLE) {
94109 _revokeRole (DEFAULT_ADMIN_ROLE, account);
110+ emit AdminRemoved (account);
111+ }
112+
113+ function isTaskAdmin (address account ) public view returns (bool ) {
114+ return hasRole (DEFAULT_ADMIN_ROLE, account);
115+ }
116+
117+ function getAdmins () public view returns (address [] memory ) {
118+ return getRoleMembers (DEFAULT_ADMIN_ROLE);
95119 }
96120}
0 commit comments