forked from poa-box/POP
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathToggleModule.sol
More file actions
154 lines (132 loc) · 5.54 KB
/
ToggleModule.sol
File metadata and controls
154 lines (132 loc) · 5.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
import "@openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
/**
* @title ToggleModule
* @notice A module for toggling Hats active or inactive.
*
* The Hats Protocol calls getHatStatus(uint256) and expects a uint256:
* 1 indicates "active," and 0 indicates "inactive."
*/
contract ToggleModule is Initializable {
/*═══════════════════════════════════════════ ERRORS ═══════════════════════════════════════════*/
error NotToggleAdmin();
error ZeroAddress();
/*═════════════════════════════════════ ERC-7201 STORAGE ═════════════════════════════════════*/
/// @custom:storage-location erc7201:poa.togglemodule.storage
struct Layout {
/// @notice The admin who can toggle hat status
address admin;
/// @notice The eligibility module address that can also toggle hat status
address eligibilityModule;
/// @notice Whether each hat is active or not
/// @dev hatId => bool (true = active, false = inactive)
mapping(uint256 => bool) hatActive;
}
bytes32 private constant _STORAGE_SLOT = keccak256("poa.togglemodule.storage");
function _layout() private pure returns (Layout storage s) {
bytes32 slot = _STORAGE_SLOT;
assembly {
s.slot := slot
}
}
/// @notice Emitted when a hat's status is toggled
event HatToggled(uint256 indexed hatId, bool newStatus);
/// @notice Emitted when admin is transferred
event AdminTransferred(address indexed oldAdmin, address indexed newAdmin);
/// @notice Emitted when the module is initialized
event ToggleModuleInitialized(address indexed admin);
/**
* @notice Initialize the module with admin
* @param _admin The admin address
*/
function initialize(address _admin) external initializer {
if (_admin == address(0)) revert ZeroAddress();
Layout storage l = _layout();
l.admin = _admin;
emit ToggleModuleInitialized(_admin);
}
constructor() {
_disableInitializers();
}
/**
* @dev Restricts certain calls so only the admin or eligibility module can perform them
*/
modifier onlyAdmin() {
Layout storage l = _layout();
if (msg.sender != l.admin && msg.sender != l.eligibilityModule) revert NotToggleAdmin();
_;
}
/**
* @notice Sets an individual hat's active status
* @param hatId The ID of the hat being toggled
* @param _active Whether this hat is active (true) or inactive (false)
*/
function setHatStatus(uint256 hatId, bool _active) external onlyAdmin {
Layout storage l = _layout();
l.hatActive[hatId] = _active;
emit HatToggled(hatId, _active);
}
/**
* @notice Batch set multiple hats' active status
* @dev Sets status for multiple hats in a single call - gas optimized for HatsTreeSetup
* @param hatIds Array of hat IDs to toggle
* @param actives Array of active statuses (must match hatIds length)
*/
function batchSetHatStatus(uint256[] calldata hatIds, bool[] calldata actives) external onlyAdmin {
uint256 length = hatIds.length;
require(length == actives.length, "Array length mismatch");
Layout storage l = _layout();
unchecked {
for (uint256 i; i < length; ++i) {
l.hatActive[hatIds[i]] = actives[i];
emit HatToggled(hatIds[i], actives[i]);
}
}
}
/**
* @notice The Hats Protocol calls this function to determine if `hatId` is active.
* @param hatId The ID of the hat being checked
* @return status 1 if active, 0 if inactive
*/
function getHatStatus(uint256 hatId) external view returns (uint256 status) {
// Return 1 for active, 0 for inactive
return _layout().hatActive[hatId] ? 1 : 0;
}
/**
* @notice Transfer admin rights of this module to a new admin
* @param newAdmin The new admin address
*/
function transferAdmin(address newAdmin) external onlyAdmin {
if (newAdmin == address(0)) revert ZeroAddress();
Layout storage l = _layout();
address oldAdmin = l.admin;
l.admin = newAdmin;
emit AdminTransferred(oldAdmin, newAdmin);
}
/**
* @notice Set the eligibility module address that can also toggle hat status
* @param _eligibilityModule The eligibility module address
*/
function setEligibilityModule(address _eligibilityModule) external {
Layout storage l = _layout();
// Only allow admin to set this, but don't use the modifier since eligibility module might not be set yet
if (msg.sender != l.admin) revert NotToggleAdmin();
l.eligibilityModule = _eligibilityModule;
}
/**
* @notice Get the current admin address
* @return admin The admin address
*/
function admin() external view returns (address) {
return _layout().admin;
}
/**
* @notice Check if a hat is active
* @param hatId The hat ID to check
* @return active Whether the hat is active
*/
function hatActive(uint256 hatId) external view returns (bool) {
return _layout().hatActive[hatId];
}
}