Skip to content

Commit d3bc619

Browse files
feat(risk): implement dynamic MMR system with market classification
- Replace hardcoded market MMR logic with scalable RiskParameterManager - Add market classification system for African/event derivatives - Implement fallback mechanisms for unknown markets - Add admin functions for real-time parameter updates - Include comprehensive event logging and error handling BREAKING CHANGE: MMR calculation now uses leverage-based formula instead of collateral arithmetic - Liquidation formula changed from collateral-based to: LiqPrice = Entry × (1 ± ((1/Leverage) - MMR)) - Markets now classified dynamically instead of hardcoded IDs Fixes: #128 Risk-Level: HIGH Signed-off-by: ADEBAKIN OLUJIMI
1 parent bfc21f7 commit d3bc619

4 files changed

Lines changed: 822 additions & 19 deletions

File tree

src/access/AccessManager.sol

Lines changed: 356 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,361 @@
1-
// SPDX-License-Identifier: MIT
1+
// SPDX-License-Identifier: BUSL-1.1
22
pragma solidity ^0.8.24;
33

4+
import {RoleRegistry} from "./RoleRegistry.sol";
5+
import {SecurityBase} from "../security/SecurityBase.sol";
6+
47
/**
58
* @title AccessManager
6-
* @notice TODO: Add contract description
9+
* @author BAOBAB Protocol
10+
* @notice Central access control for all protocol contracts
11+
* @dev Role-based access control with hierarchical permissions
12+
*
13+
* ═══════════════════════════════════════════════════════════════════════════════════════════════════
14+
* ACCESS MANAGER
15+
* ═══════════════════════════════════════════════════════════════════════════════════════════════════
716
*/
8-
contract AccessManager {
9-
// TODO: Implement contract
10-
}
17+
contract AccessManager is SecurityBase {
18+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
19+
// STATE VARIABLES
20+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
21+
22+
/// @notice Role memberships: role => account => isMember
23+
mapping(bytes32 => mapping(address => bool)) private _roles;
24+
25+
/// @notice Role admin: role => adminRole (who can grant/revoke this role)
26+
mapping(bytes32 => bytes32) private _roleAdmins;
27+
28+
/// @notice Account roles: account => roles[]
29+
mapping(address => bytes32[]) private _accountRoles;
30+
31+
/// @notice Role members: role => members[]
32+
mapping(bytes32 => address[]) private _roleMembers;
33+
34+
/// @notice Protocol owner (highest authority)
35+
address public owner;
36+
37+
/// @notice Pending owner for two-step transfer
38+
address public pendingOwner;
39+
40+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
41+
// EVENTS
42+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
43+
44+
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
45+
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
46+
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
47+
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
48+
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
49+
50+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
51+
// ERRORS
52+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
53+
54+
error AccessManager__OnlyOwner();
55+
error AccessManager__MissingRole(bytes32 role);
56+
error AccessManager__AlreadyHasRole();
57+
error AccessManager__UnauthorizedRoleAdmin();
58+
error AccessManager__CannotRevokeOwnRole();
59+
error AccessManager__InvalidAddress();
60+
61+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
62+
// CONSTRUCTOR
63+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
64+
65+
constructor(address _owner) {
66+
if (_owner == address(0)) revert AccessManager__InvalidAddress();
67+
68+
owner = _owner;
69+
70+
// Grant owner the OWNER_ROLE
71+
_grantRole(RoleRegistry.OWNER_ROLE, _owner);
72+
73+
// Set OWNER_ROLE as admin of all roles
74+
_setRoleAdmin(RoleRegistry.OWNER_ROLE, RoleRegistry.OWNER_ROLE);
75+
_setRoleAdmin(RoleRegistry.ADMIN_ROLE, RoleRegistry.OWNER_ROLE);
76+
_setRoleAdmin(RoleRegistry.GUARDIAN_ROLE, RoleRegistry.OWNER_ROLE);
77+
_setRoleAdmin(RoleRegistry.KEEPER_ROLE, RoleRegistry.ADMIN_ROLE);
78+
_setRoleAdmin(RoleRegistry.LIQUIDATOR_ROLE, RoleRegistry.ADMIN_ROLE);
79+
_setRoleAdmin(RoleRegistry.ORACLE_UPDATER_ROLE, RoleRegistry.ADMIN_ROLE);
80+
_setRoleAdmin(RoleRegistry.MARKET_MAKER_ROLE, RoleRegistry.ADMIN_ROLE);
81+
_setRoleAdmin(RoleRegistry.FEE_MANAGER_ROLE, RoleRegistry.ADMIN_ROLE);
82+
_setRoleAdmin(RoleRegistry.BASKET_MANAGER_ROLE, RoleRegistry.ADMIN_ROLE);
83+
_setRoleAdmin(RoleRegistry.EVENT_SETTLER_ROLE, RoleRegistry.ADMIN_ROLE);
84+
_setRoleAdmin(RoleRegistry.PAUSER_ROLE, RoleRegistry.GUARDIAN_ROLE);
85+
_setRoleAdmin(RoleRegistry.UPGRADER_ROLE, RoleRegistry.OWNER_ROLE);
86+
_setRoleAdmin(RoleRegistry.TRADING_OPERATOR_ROLE, RoleRegistry.ADMIN_ROLE);
87+
_setRoleAdmin(RoleRegistry.VAULT_OPERATOR_ROLE, RoleRegistry.ADMIN_ROLE);
88+
_setRoleAdmin(RoleRegistry.RISK_MANAGER_ROLE, RoleRegistry.ADMIN_ROLE);
89+
}
90+
91+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
92+
// MODIFIERS
93+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
94+
95+
modifier onlyOwner() {
96+
if (msg.sender != owner) revert AccessManager__OnlyOwner();
97+
_;
98+
}
99+
100+
modifier onlyRole(bytes32 role) {
101+
if (!hasRole(role, msg.sender)) {
102+
revert AccessManager__MissingRole(role);
103+
}
104+
_;
105+
}
106+
107+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
108+
// ROLE MANAGEMENT
109+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
110+
111+
/**
112+
* @notice Grant role to account
113+
* @param role Role to grant
114+
* @param account Account receiving role
115+
* @dev Only callable by role admin
116+
*/
117+
function grantRole(bytes32 role, address account) external {
118+
if (!hasRole(_roleAdmins[role], msg.sender)) {
119+
revert AccessManager__UnauthorizedRoleAdmin();
120+
}
121+
_grantRole(role, account);
122+
}
123+
124+
/**
125+
* @notice Revoke role from account
126+
* @param role Role to revoke
127+
* @param account Account losing role
128+
* @dev Only callable by role admin
129+
*/
130+
function revokeRole(bytes32 role, address account) external {
131+
if (!hasRole(_roleAdmins[role], msg.sender)) {
132+
revert AccessManager__UnauthorizedRoleAdmin();
133+
}
134+
if (msg.sender == account) {
135+
revert AccessManager__CannotRevokeOwnRole();
136+
}
137+
_revokeRole(role, account);
138+
}
139+
140+
/**
141+
* @notice Renounce own role
142+
* @param role Role to renounce
143+
* @dev Account voluntarily gives up role
144+
*/
145+
function renounceRole(bytes32 role) external {
146+
_revokeRole(role, msg.sender);
147+
}
148+
149+
/**
150+
* @notice Batch grant roles
151+
* @param roles Array of roles to grant
152+
* @param accounts Array of accounts receiving roles
153+
*/
154+
function batchGrantRoles(
155+
bytes32[] calldata roles,
156+
address[] calldata accounts
157+
) external {
158+
require(roles.length == accounts.length, "Length mismatch");
159+
160+
for (uint256 i = 0; i < roles.length; i++) {
161+
if (!hasRole(_roleAdmins[roles[i]], msg.sender)) {
162+
revert AccessManager__UnauthorizedRoleAdmin();
163+
}
164+
_grantRole(roles[i], accounts[i]);
165+
}
166+
}
167+
168+
/**
169+
* @notice Batch revoke roles
170+
* @param roles Array of roles to revoke
171+
* @param accounts Array of accounts losing roles
172+
*/
173+
function batchRevokeRoles(
174+
bytes32[] calldata roles,
175+
address[] calldata accounts
176+
) external {
177+
require(roles.length == accounts.length, "Length mismatch");
178+
179+
for (uint256 i = 0; i < roles.length; i++) {
180+
if (!hasRole(_roleAdmins[roles[i]], msg.sender)) {
181+
revert AccessManager__UnauthorizedRoleAdmin();
182+
}
183+
if (msg.sender == accounts[i]) {
184+
revert AccessManager__CannotRevokeOwnRole();
185+
}
186+
_revokeRole(roles[i], accounts[i]);
187+
}
188+
}
189+
190+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
191+
// OWNERSHIP TRANSFER
192+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
193+
194+
/**
195+
* @notice Start ownership transfer (step 1 of 2)
196+
* @param newOwner New owner address
197+
* @dev Two-step process for safety
198+
*/
199+
function transferOwnership(address newOwner) external onlyOwner {
200+
if (newOwner == address(0)) revert AccessManager__InvalidAddress();
201+
pendingOwner = newOwner;
202+
emit OwnershipTransferStarted(owner, newOwner);
203+
}
204+
205+
/**
206+
* @notice Accept ownership transfer (step 2 of 2)
207+
* @dev Must be called by pending owner
208+
*/
209+
function acceptOwnership() external {
210+
if (msg.sender != pendingOwner) revert AccessManager__OnlyOwner();
211+
212+
address oldOwner = owner;
213+
owner = pendingOwner;
214+
pendingOwner = address(0);
215+
216+
// Transfer OWNER_ROLE
217+
_revokeRole(RoleRegistry.OWNER_ROLE, oldOwner);
218+
_grantRole(RoleRegistry.OWNER_ROLE, owner);
219+
220+
emit OwnershipTransferred(oldOwner, owner);
221+
}
222+
223+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
224+
// INTERNAL FUNCTIONS
225+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
226+
227+
/**
228+
* @notice Internal grant role function
229+
* @param role Role to grant
230+
* @param account Account receiving role
231+
*/
232+
function _grantRole(bytes32 role, address account) internal {
233+
if (_roles[role][account]) revert AccessManager__AlreadyHasRole();
234+
235+
_roles[role][account] = true;
236+
_accountRoles[account].push(role);
237+
_roleMembers[role].push(account);
238+
239+
emit RoleGranted(role, account, msg.sender);
240+
}
241+
242+
/**
243+
* @notice Internal revoke role function
244+
* @param role Role to revoke
245+
* @param account Account losing role
246+
*/
247+
function _revokeRole(bytes32 role, address account) internal {
248+
if (!_roles[role][account]) return;
249+
250+
_roles[role][account] = false;
251+
252+
// Remove from account roles
253+
bytes32[] storage accountRoles = _accountRoles[account];
254+
for (uint256 i = 0; i < accountRoles.length; i++) {
255+
if (accountRoles[i] == role) {
256+
accountRoles[i] = accountRoles[accountRoles.length - 1];
257+
accountRoles.pop();
258+
break;
259+
}
260+
}
261+
262+
// Remove from role members
263+
address[] storage members = _roleMembers[role];
264+
for (uint256 i = 0; i < members.length; i++) {
265+
if (members[i] == account) {
266+
members[i] = members[members.length - 1];
267+
members.pop();
268+
break;
269+
}
270+
}
271+
272+
emit RoleRevoked(role, account, msg.sender);
273+
}
274+
275+
/**
276+
* @notice Set role admin
277+
* @param role Role to configure
278+
* @param adminRole Admin role for this role
279+
*/
280+
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal {
281+
bytes32 previousAdminRole = _roleAdmins[role];
282+
_roleAdmins[role] = adminRole;
283+
emit RoleAdminChanged(role, previousAdminRole, adminRole);
284+
}
285+
286+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
287+
// VIEW FUNCTIONS
288+
// ═══════════════════════════════════════════════════════════════════════════════════════════════
289+
290+
/**
291+
* @notice Check if account has role
292+
* @param role Role to check
293+
* @param account Account to check
294+
* @return bool True if account has role
295+
*/
296+
function hasRole(bytes32 role, address account) public view returns (bool) {
297+
return _roles[role][account];
298+
}
299+
300+
/**
301+
* @notice Get role admin
302+
* @param role Role to query
303+
* @return bytes32 Admin role identifier
304+
*/
305+
function getRoleAdmin(bytes32 role) external view returns (bytes32) {
306+
return _roleAdmins[role];
307+
}
308+
309+
/**
310+
* @notice Get all roles for an account
311+
* @param account Account to query
312+
* @return roles Array of role identifiers
313+
*/
314+
function getAccountRoles(address account) external view returns (bytes32[] memory roles) {
315+
return _accountRoles[account];
316+
}
317+
318+
/**
319+
* @notice Get all members of a role
320+
* @param role Role to query
321+
* @return members Array of addresses
322+
*/
323+
function getRoleMembers(bytes32 role) external view returns (address[] memory members) {
324+
return _roleMembers[role];
325+
}
326+
327+
/**
328+
* @notice Get role member count
329+
* @param role Role to query
330+
* @return count Number of members
331+
*/
332+
function getRoleMemberCount(bytes32 role) external view returns (uint256 count) {
333+
return _roleMembers[role].length;
334+
}
335+
336+
/**
337+
* @notice Check if account has any of the roles
338+
* @param roles Array of roles to check
339+
* @param account Account to check
340+
* @return bool True if account has at least one role
341+
*/
342+
function hasAnyRole(bytes32[] calldata roles, address account) external view returns (bool) {
343+
for (uint256 i = 0; i < roles.length; i++) {
344+
if (hasRole(roles[i], account)) return true;
345+
}
346+
return false;
347+
}
348+
349+
/**
350+
* @notice Check if account has all roles
351+
* @param roles Array of roles to check
352+
* @param account Account to check
353+
* @return bool True if account has all roles
354+
*/
355+
function hasAllRoles(bytes32[] calldata roles, address account) external view returns (bool) {
356+
for (uint256 i = 0; i < roles.length; i++) {
357+
if (!hasRole(roles[i], account)) return false;
358+
}
359+
return true;
360+
}
361+
}

0 commit comments

Comments
 (0)