0
# Access Control
1
2
OpenZeppelin Contracts provides flexible access control mechanisms through role-based and ownership-based patterns, enabling secure function access management in smart contracts.
3
4
## Role-Based Access Control
5
6
### AccessControl
7
8
Flexible role-based access control mechanism where roles are identified by bytes32 identifiers.
9
10
```solidity { .api }
11
abstract contract AccessControl is Context, IAccessControl, ERC165 {
12
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
13
14
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool);
15
function hasRole(bytes32 role, address account) public view virtual returns (bool);
16
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32);
17
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role));
18
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role));
19
function renounceRole(bytes32 role, address account) public virtual;
20
}
21
22
interface IAccessControl {
23
function hasRole(bytes32 role, address account) external view returns (bool);
24
function getRoleAdmin(bytes32 role) external view returns (bytes32);
25
function grantRole(bytes32 role, address account) external;
26
function revokeRole(bytes32 role, address account) external;
27
function renounceRole(bytes32 role, address account) external;
28
}
29
```
30
31
**Events:**
32
- `RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)`
33
- `RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)`
34
- `RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)`
35
36
**Modifiers:**
37
- `onlyRole(bytes32 role)` - Restricts access to accounts with specific role
38
39
**Usage Example:**
40
41
```solidity
42
contract MyContract is AccessControl {
43
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
44
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
45
46
constructor() {
47
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
48
_grantRole(MANAGER_ROLE, msg.sender);
49
}
50
51
function managerOnlyFunction() public onlyRole(MANAGER_ROLE) {
52
// Only accounts with MANAGER_ROLE can call this
53
}
54
55
function operatorFunction() public onlyRole(OPERATOR_ROLE) {
56
// Only accounts with OPERATOR_ROLE can call this
57
}
58
}
59
```
60
61
### AccessControlEnumerable
62
63
Extension of AccessControl that allows enumeration of role members.
64
65
```solidity { .api }
66
abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
67
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool);
68
function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address);
69
function getRoleMemberCount(bytes32 role) public view virtual returns (uint256);
70
}
71
72
interface IAccessControlEnumerable is IAccessControl {
73
function getRoleMember(bytes32 role, uint256 index) external view returns (address);
74
function getRoleMemberCount(bytes32 role) external view returns (uint256);
75
}
76
```
77
78
**Usage Example:**
79
80
```solidity
81
contract MyEnumerableContract is AccessControlEnumerable {
82
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
83
84
function getAllMinters() public view returns (address[] memory) {
85
uint256 count = getRoleMemberCount(MINTER_ROLE);
86
address[] memory minters = new address[](count);
87
for (uint256 i = 0; i < count; i++) {
88
minters[i] = getRoleMember(MINTER_ROLE, i);
89
}
90
return minters;
91
}
92
}
93
```
94
95
### AccessControlCrossChain
96
97
Extension for cross-chain access control scenarios.
98
99
```solidity { .api }
100
abstract contract AccessControlCrossChain is AccessControl, CrossChainEnabled {
101
function hasRole(bytes32 role, address account) public view virtual override returns (bool);
102
}
103
```
104
105
## Ownership-Based Access Control
106
107
### Ownable
108
109
Simple ownership access control with a single owner.
110
111
```solidity { .api }
112
abstract contract Ownable is Context {
113
address private _owner;
114
115
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
116
117
constructor();
118
119
function owner() public view virtual returns (address);
120
function renounceOwnership() public virtual onlyOwner;
121
function transferOwnership(address newOwner) public virtual onlyOwner;
122
}
123
```
124
125
**Events:**
126
- `OwnershipTransferred(address indexed previousOwner, address indexed newOwner)`
127
128
**Modifiers:**
129
- `onlyOwner()` - Restricts access to the contract owner
130
131
**Usage Example:**
132
133
```solidity
134
contract MyOwnableContract is Ownable {
135
uint256 public value;
136
137
function setValue(uint256 _value) public onlyOwner {
138
value = _value;
139
}
140
141
function emergencyStop() public onlyOwner {
142
// Only owner can call emergency functions
143
}
144
}
145
```
146
147
### Ownable2Step
148
149
Two-step ownership transfer for enhanced security.
150
151
```solidity { .api }
152
abstract contract Ownable2Step is Ownable {
153
address private _pendingOwner;
154
155
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
156
157
function pendingOwner() public view virtual returns (address);
158
function transferOwnership(address newOwner) public virtual override onlyOwner;
159
function acceptOwnership() external;
160
}
161
```
162
163
**Events:**
164
- `OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner)`
165
166
**Usage Example:**
167
168
```solidity
169
contract SecureContract is Ownable2Step {
170
function initiateOwnershipTransfer(address newOwner) public onlyOwner {
171
transferOwnership(newOwner);
172
// newOwner must call acceptOwnership() to complete the transfer
173
}
174
}
175
```
176
177
## Access Control Patterns
178
179
### Combining Access Control Mechanisms
180
181
You can combine different access control patterns for more sophisticated permission systems:
182
183
```solidity
184
contract HybridAccessControl is AccessControl, Ownable {
185
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
186
bytes32 public constant USER_ROLE = keccak256("USER_ROLE");
187
188
constructor() {
189
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
190
_grantRole(ADMIN_ROLE, msg.sender);
191
}
192
193
// Only owner or admin role can call
194
function sensitiveFunction() public {
195
require(
196
owner() == msg.sender || hasRole(ADMIN_ROLE, msg.sender),
197
"Not authorized"
198
);
199
// Function logic
200
}
201
202
// Only owner can grant admin role
203
function grantAdminRole(address account) public onlyOwner {
204
grantRole(ADMIN_ROLE, account);
205
}
206
}
207
```
208
209
### Role Hierarchy
210
211
Create role hierarchies by setting role admins:
212
213
```solidity
214
contract HierarchicalRoles is AccessControl {
215
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
216
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
217
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
218
219
constructor() {
220
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
221
_grantRole(ADMIN_ROLE, msg.sender);
222
223
// Set role hierarchy
224
_setRoleAdmin(MANAGER_ROLE, ADMIN_ROLE);
225
_setRoleAdmin(OPERATOR_ROLE, MANAGER_ROLE);
226
}
227
228
function adminFunction() public onlyRole(ADMIN_ROLE) {
229
// Only admins can call
230
}
231
232
function managerFunction() public onlyRole(MANAGER_ROLE) {
233
// Only managers can call
234
}
235
236
function operatorFunction() public onlyRole(OPERATOR_ROLE) {
237
// Only operators can call
238
}
239
}
240
```
241
242
## Security Best Practices
243
244
### Role Management
245
246
1. **Least Privilege**: Grant minimum required permissions
247
2. **Role Separation**: Separate concerns into different roles
248
3. **Admin Role Protection**: Carefully manage DEFAULT_ADMIN_ROLE holders
249
4. **Role Renunciation**: Allow role holders to renounce their roles
250
251
### Ownership Transfer
252
253
1. **Two-Step Transfer**: Use Ownable2Step for critical contracts
254
2. **Verification**: Always verify new owner addresses
255
3. **Emergency Procedures**: Have procedures for ownership recovery
256
4. **Multi-Signature**: Consider multi-signature wallets for critical owners
257
258
### Common Pitfalls
259
260
1. **Role Admin Loops**: Avoid circular role admin dependencies
261
2. **Ownership Renunciation**: Be cautious about renouncing ownership permanently
262
3. **Role Discovery**: Remember that role membership is public on-chain
263
4. **Gas Costs**: Consider gas costs when enumerating role members
264
265
## Testing Access Control
266
267
```solidity
268
// Example test patterns
269
contract AccessControlTest {
270
function testOnlyOwnerFunctions() public {
271
// Test that only owner can call owner-only functions
272
vm.prank(owner);
273
contract.ownerOnlyFunction();
274
275
vm.expectRevert("Ownable: caller is not the owner");
276
vm.prank(user);
277
contract.ownerOnlyFunction();
278
}
279
280
function testRoleBasedAccess() public {
281
// Test role-based access
282
vm.prank(admin);
283
contract.grantRole(MANAGER_ROLE, manager);
284
285
vm.prank(manager);
286
contract.managerFunction();
287
288
vm.expectRevert();
289
vm.prank(user);
290
contract.managerFunction();
291
}
292
}
293
```