Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
1327b8f
add AccessManager.sol
frangio Mar 1, 2023
b54b922
docs
frangio Mar 1, 2023
5a0fd33
enforce team existence
frangio Mar 1, 2023
ee5de96
remove on-chain enumerability
frangio Mar 15, 2023
6284768
add Authority interface
frangio Mar 15, 2023
bce3641
remove redundancy
frangio Mar 15, 2023
223e6af
simplify and complete features
frangio Mar 16, 2023
eafb571
split in separate files
frangio Mar 16, 2023
24af159
write AccessManager docs
frangio Mar 16, 2023
833f219
add docs for AccessManagerAdapter
frangio Mar 16, 2023
0f27739
add to docs site
frangio Mar 16, 2023
2ac5aa1
docs for IAuthority
frangio Mar 16, 2023
f26c4d3
lint
frangio Mar 16, 2023
63f9393
add initial AccessManaged docs
frangio Mar 16, 2023
6fecd25
typos
frangio Mar 16, 2023
589b5b1
Update contracts/access/manager/AccessManager.sol
frangio Mar 16, 2023
619cc73
fix docs
frangio Mar 16, 2023
1b681a5
rename team "all" -> "public"
frangio Mar 16, 2023
48b31c0
ad hoc selector batching
frangio Mar 16, 2023
b078157
whitespace
frangio Mar 17, 2023
274c04d
add docs on team number choice
frangio Mar 17, 2023
082b05c
add missing function argument
frangio Mar 17, 2023
52f27c8
fix team role decoding
frangio Mar 17, 2023
1285c49
add ungrouped interface
frangio Mar 17, 2023
5c7472a
improve docs
frangio Mar 17, 2023
3cc1e73
remove todo
frangio Mar 17, 2023
ae3482d
add initial tests for teams
frangio Mar 19, 2023
535ea13
rename team -> badge
frangio Mar 19, 2023
68bb5fa
improve testing
frangio Mar 19, 2023
fa58ec5
Update contracts/access/manager/AccessManager.sol
frangio Mar 19, 2023
6d76058
use mapping labels from solidity 0.8.18
frangio Mar 20, 2023
3447438
add AccessManaged tests
frangio Mar 20, 2023
1d10639
fix group encoding
frangio Mar 20, 2023
d875a7b
add test for allowing
frangio Mar 20, 2023
d517d27
lint
frangio Mar 20, 2023
6d33cda
add changeset
frangio Mar 20, 2023
9ed1aef
lint
frangio Mar 20, 2023
6adc0d8
fix mock
frangio Mar 21, 2023
0624794
remove contract groups
frangio Mar 21, 2023
17ec3b6
add transferContractAuthority
frangio Mar 21, 2023
f9210c5
lint
frangio Mar 21, 2023
a54cd10
rename badge -> group
frangio Mar 21, 2023
8af9ca4
change test name
frangio Mar 21, 2023
2a05bcc
add setContractModeCustom and tests
frangio Mar 21, 2023
5f22f2d
remove use of mapping labels
frangio Mar 21, 2023
6a9fbed
Merge branch 'master' into accessmanager
frangio Mar 22, 2023
0580198
tweak comments
frangio Mar 22, 2023
e01e362
add tests for adapter
frangio Mar 22, 2023
cb026bc
lint
frangio Mar 22, 2023
76d35da
add revert reasons and group tests
frangio Mar 22, 2023
4069781
add tests for allowing and disallowing roles
frangio Mar 22, 2023
80ffd2a
lint
frangio Mar 22, 2023
3094d0c
add docs for AccessManaged
frangio Mar 22, 2023
b3a8b1b
Update contracts/access/manager/AccessManager.sol
frangio Mar 22, 2023
8a68322
Update contracts/access/manager/AccessManager.sol
frangio Mar 22, 2023
eeab8cf
Update contracts/access/manager/AccessManager.sol
frangio Mar 22, 2023
ca61e38
add tests for onlyDefaultAdmin
frangio Mar 22, 2023
8824c31
lint
frangio Mar 22, 2023
67e33b6
add internal _setContractMode
frangio Mar 22, 2023
5f270c7
Update .changeset/quiet-trainers-kick.md
frangio Mar 22, 2023
6f7ac96
Update contracts/access/manager/AccessManager.sol
frangio Mar 22, 2023
7ec5311
Update contracts/access/manager/AccessManager.sol
frangio Mar 22, 2023
82402d2
typo
frangio Mar 22, 2023
27807e6
Apply suggestions from code review
frangio Mar 22, 2023
80bc88b
roll back to 0.8.13
frangio Mar 22, 2023
cf4df22
add test for setFunctionAllowedGroup events
frangio Mar 22, 2023
bab4d34
remove mode restriction on setFunctionAllowedGroup
frangio Mar 22, 2023
642e279
simplify use of setFunctionAllowedGroup
frangio Mar 22, 2023
51aff23
lint
frangio Mar 22, 2023
6e3da66
remove unused import
frangio Mar 22, 2023
6b56c8f
remove onlyDefaultAdmin modifier
frangio Mar 22, 2023
b7e3b3f
lint
frangio Mar 22, 2023
8200986
use return value names for _decodeGroupRole
frangio Mar 22, 2023
18e53e2
rename RestrictedMode -> AccessMode
frangio Mar 22, 2023
f94a881
use Context._msgSender
frangio Mar 22, 2023
cd8babd
reorder arguments to grant/revoke/renounceGroup
frangio Mar 22, 2023
597edc0
add IAccessControlDefaultAdminRules
frangio Mar 22, 2023
ee560d0
Apply suggestions from code review
frangio Mar 22, 2023
33f5ace
Fix expected event parameter name in tests
ernestognw Mar 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
rename badge -> group
  • Loading branch information
frangio committed Mar 21, 2023
commit a54cd1082aed32a6ad40e3c4d4ca3f1fd7a60acd
198 changes: 99 additions & 99 deletions contracts/access/manager/AccessManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,29 @@ interface IAccessManager is IAuthority {
Open
}

event BadgeUpdated(uint8 indexed badge, string name);
event GroupUpdated(uint8 indexed group, string name);

event BadgeAllowed(address indexed target, bytes4 indexed selector, uint8 indexed badge, bool allowed);
event GroupAllowed(address indexed target, bytes4 indexed selector, uint8 indexed group, bool allowed);

event RestrictedModeUpdated(address indexed target, RestrictedMode indexed mode);

function createBadge(uint8 badge, string calldata name) external;
function createGroup(uint8 group, string calldata name) external;

function updateBadgeName(uint8 badge, string calldata name) external;
function updateGroupName(uint8 group, string calldata name) external;

function hasBadge(uint8 badge) external view returns (bool);
function hasGroup(uint8 group) external view returns (bool);

function getUserBadges(address user) external view returns (bytes32 badges);
function getUserGroups(address user) external view returns (bytes32 groups);

function grantBadge(address user, uint8 badge) external;
function grantGroup(address user, uint8 group) external;

function revokeBadge(address user, uint8 badge) external;
function revokeGroup(address user, uint8 group) external;

function renounceBadge(address user, uint8 badge) external;
function renounceGroup(address user, uint8 group) external;

function getFunctionAllowedBadges(address target, bytes4 selector) external view returns (bytes32 badges);
function getFunctionAllowedGroups(address target, bytes4 selector) external view returns (bytes32 groups);

function setFunctionAllowedBadge(address target, bytes4[] calldata selectors, uint8 badge, bool allowed) external;
function setFunctionAllowedGroup(address target, bytes4[] calldata selectors, uint8 group, bool allowed) external;

function getContractMode(address target) external view returns (RestrictedMode);

Expand All @@ -52,39 +52,39 @@ interface IAccessManager is IAuthority {
*
* The smart contracts under the control of an AccessManager instance will have a set of "restricted" functions, and the
* exact details of how access is restricted for each of those functions is configurable by the admins of the instance.
* These restrictions are expressed in terms of "badges".
* These restrictions are expressed in terms of "groups".
*
* An AccessManager instance will define a set of badges. Each of them must be created before they can be granted, with
* a maximum of 255 created badges. Users can be granted any number of these badges. Each of them defines an
* An AccessManager instance will define a set of groups. Each of them must be created before they can be granted, with
* a maximum of 255 created groups. Users can be added into any number of these groups. Each of them defines an
* AccessControl role, and may confer access to some of the restricted functions in the system, as configured by admins
* through the use of {setFunctionAllowedBadge}.
* through the use of {setFunctionAllowedGroup}.
*
* Note that a function in a target contract may become permissioned in this way only when: 1) said contract is
* {AccessManaged} and is connected to this contract as its manager, and 2) said function is decorated with the
* `restricted` modifier.
*
* There is a special badge defined by default named "public" which all accounts automatically have.
* There is a special group defined by default named "public" which all accounts automatically have.
*
* Contracts can also be configured in two special modes: 1) the "open" mode, where all functions are allowed to the
* "public" badge, and 2) the "closed" mode, where no function is allowed to any badge.
* "public" group, and 2) the "closed" mode, where no function is allowed to any group.
*
* Since all the permissions of the managed system can be modified by the admins of this instance, it is expected that
* it will be highly secured (e.g., a multisig or a well-configured DAO). Additionally, {AccessControlDefaultAdminRules}
* is included to enforce security rules on this account.
*
* NOTE: Some of the functions in this contract, such as {getUserBadges}, return a `bytes32` bitmap to succintly
* represent a set of badges. In a bitmap, bit `n` (counting from the least significant bit) will be 1 if and only if
* the badge with number `n` is in the set. For example, the hex value `0x05` represents the set of the two badges
* NOTE: Some of the functions in this contract, such as {getUserGroups}, return a `bytes32` bitmap to succintly
* represent a set of groups. In a bitmap, bit `n` (counting from the least significant bit) will be 1 if and only if
* the group with number `n` is in the set. For example, the hex value `0x05` represents the set of the two groups
* numbered 0 and 2.
*/
contract AccessManager is IAccessManager, AccessControlDefaultAdminRules {
bytes32 _createdBadges;
bytes32 _createdGroups;

mapping(address user => bytes32 badges) private _userBadges;
mapping(address target => mapping(bytes4 selector => bytes32 badges)) private _allowedBadges;
mapping(address user => bytes32 groups) private _userGroups;
mapping(address target => mapping(bytes4 selector => bytes32 groups)) private _allowedGroups;
mapping(address target => RestrictedMode mode) private _contractMode;

uint8 private constant _BADGE_PUBLIC = 255;
uint8 private constant _GROUP_PUBLIC = 255;

/**
* @dev Initializes an AccessManager with initial default admin and transfer delay.
Expand All @@ -93,114 +93,114 @@ contract AccessManager is IAccessManager, AccessControlDefaultAdminRules {
uint48 initialDefaultAdminDelay,
address initialDefaultAdmin
) AccessControlDefaultAdminRules(initialDefaultAdminDelay, initialDefaultAdmin) {
_createBadge(_BADGE_PUBLIC, "public");
_createGroup(_GROUP_PUBLIC, "public");
}

/**
* @dev Returns true if the caller can invoke on a target the function identified by a function selector.
* Entrypoint for {AccessManaged} contracts.
*/
function canCall(address caller, address target, bytes4 selector) public view returns (bool) {
bytes32 allowedBadges = getFunctionAllowedBadges(target, selector);
bytes32 callerBadges = getUserBadges(caller);
return callerBadges & allowedBadges != 0;
bytes32 allowedGroups = getFunctionAllowedGroups(target, selector);
bytes32 callerGroups = getUserGroups(caller);
return callerGroups & allowedGroups != 0;
}

/**
* @dev Creates a new badge with a badge number that can be chosen arbitrarily but must be unused, and gives it a
* @dev Creates a new group with a group number that can be chosen arbitrarily but must be unused, and gives it a
* human-readable name. The caller must be the default admin.
*
* Badge numbers are not auto-incremented in order to avoid race conditions, but administrators can safely use
* Group numbers are not auto-incremented in order to avoid race conditions, but administrators can safely use
* sequential numbers.
*
* Emits {BadgeUpdated}.
* Emits {GroupUpdated}.
*/
function createBadge(uint8 badge, string memory name) public virtual onlyDefaultAdmin {
_createBadge(badge, name);
function createGroup(uint8 group, string memory name) public virtual onlyDefaultAdmin {
_createGroup(group, name);
}

/**
* @dev Updates an existing badge's name. The caller must be the default admin.
* @dev Updates an existing group's name. The caller must be the default admin.
*/
function updateBadgeName(uint8 badge, string memory name) public virtual onlyDefaultAdmin {
require(badge != _BADGE_PUBLIC && hasBadge(badge));
emit BadgeUpdated(badge, name);
function updateGroupName(uint8 group, string memory name) public virtual onlyDefaultAdmin {
require(group != _GROUP_PUBLIC && hasGroup(group));
emit GroupUpdated(group, name);
}

/**
* @dev Returns true if the badge has already been created via {createBadge}.
* @dev Returns true if the group has already been created via {createGroup}.
*/
function hasBadge(uint8 badge) public view virtual returns (bool) {
return _getBadge(_createdBadges, badge);
function hasGroup(uint8 group) public view virtual returns (bool) {
return _getGroup(_createdGroups, group);
}

/**
* @dev Returns a bitmap of the badges the user has. See note on bitmaps above.
* @dev Returns a bitmap of the groups the user has. See note on bitmaps above.
*/
function getUserBadges(address user) public view virtual returns (bytes32) {
return _userBadges[user] | _badgeMask(_BADGE_PUBLIC);
function getUserGroups(address user) public view virtual returns (bytes32) {
return _userGroups[user] | _groupMask(_GROUP_PUBLIC);
}

/**
* @dev Grants a user a badge.
* @dev Grants a user a group.
*
* Emits {RoleGranted} with the role id of the badge, if wasn't already held by the user.
* Emits {RoleGranted} with the role id of the group, if wasn't already held by the user.
*/
function grantBadge(address user, uint8 badge) public virtual {
function grantGroup(address user, uint8 group) public virtual {
// grantRole checks that msg.sender is admin for the role
grantRole(_encodeBadgeRole(badge), user);
grantRole(_encodeGroupRole(group), user);
}

/**
* @dev Removes a badge from a user.
* @dev Removes a group from a user.
*
* Emits {RoleRevoked} with the role id of the badge, if previously held by the user.
* Emits {RoleRevoked} with the role id of the group, if previously held by the user.
*/
function revokeBadge(address user, uint8 badge) public virtual {
function revokeGroup(address user, uint8 group) public virtual {
// revokeRole checks that msg.sender is admin for the role
revokeRole(_encodeBadgeRole(badge), user);
revokeRole(_encodeGroupRole(group), user);
}

/**
* @dev Allows a user to renounce a badge.
* @dev Allows a user to renounce a group.
*
* Emits {RoleRevoked} with the role id of the badge, if previously held by the user.
* Emits {RoleRevoked} with the role id of the group, if previously held by the user.
*/
function renounceBadge(address user, uint8 badge) public virtual {
function renounceGroup(address user, uint8 group) public virtual {
// renounceRole checks that msg.sender is user
renounceRole(_encodeBadgeRole(badge), user);
renounceRole(_encodeGroupRole(group), user);
}

/**
* @dev Returns a bitmap of the badges that are allowed to call a function of a target contract. If the target
* @dev Returns a bitmap of the groups that are allowed to call a function of a target contract. If the target
* contract is in open or closed mode it will be reflected in the return value.
*/
function getFunctionAllowedBadges(address target, bytes4 selector) public view virtual returns (bytes32) {
function getFunctionAllowedGroups(address target, bytes4 selector) public view virtual returns (bytes32) {
RestrictedMode mode = getContractMode(target);
if (mode == RestrictedMode.Open) {
return _badgeMask(_BADGE_PUBLIC);
return _groupMask(_GROUP_PUBLIC);
} else if (mode == RestrictedMode.Closed) {
return 0;
} else {
return _allowedBadges[target][selector];
return _allowedGroups[target][selector];
}
}

/**
* @dev Changes whether a badge is allowed to call a function of a contract, according to the `allowed` argument.
* @dev Changes whether a group is allowed to call a function of a contract, according to the `allowed` argument.
* The caller must be the default admin.
*/
function setFunctionAllowedBadge(
function setFunctionAllowedGroup(
address target,
bytes4[] calldata selectors,
uint8 badge,
uint8 group,
bool allowed
) public virtual {
require(_contractMode[target] == RestrictedMode.Custom);
for (uint256 i = 0; i < selectors.length; i++) {
bytes4 selector = selectors[i];
_allowedBadges[target][selector] = _withUpdatedBadge(_allowedBadges[target][selector], badge, allowed);
emit BadgeAllowed(target, selector, badge, allowed);
_allowedGroups[target][selector] = _withUpdatedGroup(_allowedGroups[target][selector], group, allowed);
emit GroupAllowed(target, selector, group, allowed);
}
}

Expand Down Expand Up @@ -237,80 +237,80 @@ contract AccessManager is IAccessManager, AccessControlDefaultAdminRules {
}

/**
* @dev Creates a new badge.
* @dev Creates a new group.
*
* Emits {BadgeUpdated}.
* Emits {GroupUpdated}.
*/
function _createBadge(uint8 badge, string memory name) internal virtual {
require(!hasBadge(badge));
_createdBadges = _withUpdatedBadge(_createdBadges, badge, true);
emit BadgeUpdated(badge, name);
function _createGroup(uint8 group, string memory name) internal virtual {
require(!hasGroup(group));
_createdGroups = _withUpdatedGroup(_createdGroups, group, true);
emit GroupUpdated(group, name);
}

/**
* @dev Augmented version of {AccessControl-_grantRole} that keeps track of user badge bitmaps.
* @dev Augmented version of {AccessControl-_grantRole} that keeps track of user group bitmaps.
*/
function _grantRole(bytes32 role, address user) internal virtual override {
super._grantRole(role, user);
(bool isBadge, uint8 badge) = _decodeBadgeRole(role);
if (isBadge) {
require(hasBadge(badge));
_userBadges[user] = _withUpdatedBadge(_userBadges[user], badge, true);
(bool isGroup, uint8 group) = _decodeGroupRole(role);
if (isGroup) {
require(hasGroup(group));
_userGroups[user] = _withUpdatedGroup(_userGroups[user], group, true);
}
}

/**
* @dev Augmented version of {AccessControl-_revokeRole} that keeps track of user badge bitmaps.
* @dev Augmented version of {AccessControl-_revokeRole} that keeps track of user group bitmaps.
*/
function _revokeRole(bytes32 role, address user) internal virtual override {
super._revokeRole(role, user);
(bool isBadge, uint8 badge) = _decodeBadgeRole(role);
if (isBadge) {
require(hasBadge(badge));
require(badge != _BADGE_PUBLIC);
_userBadges[user] = _withUpdatedBadge(_userBadges[user], badge, false);
(bool isGroup, uint8 group) = _decodeGroupRole(role);
if (isGroup) {
require(hasGroup(group));
require(group != _GROUP_PUBLIC);
_userGroups[user] = _withUpdatedGroup(_userGroups[user], group, false);
}
}

/**
* @dev Returns the {AccessControl} role id that corresponds to a badge.
* @dev Returns the {AccessControl} role id that corresponds to a group.
*
* This role id starts with the ASCII characters `badge:`, followed by zeroes, and ends with the single byte
* corresponding to the badge number.
* This role id starts with the ASCII characters `group:`, followed by zeroes, and ends with the single byte
* corresponding to the group number.
*/
function _encodeBadgeRole(uint8 badge) internal pure virtual returns (bytes32) {
return bytes32("badge:") | bytes32(uint256(badge));
function _encodeGroupRole(uint8 group) internal pure virtual returns (bytes32) {
return bytes32("group:") | bytes32(uint256(group));
}

/**
* @dev Decodes a role id into a badge, if it is a role id of the kind returned by {_encodeBadgeRole}.
* @dev Decodes a role id into a group, if it is a role id of the kind returned by {_encodeGroupRole}.
*/
function _decodeBadgeRole(bytes32 role) internal pure virtual returns (bool, uint8) {
function _decodeGroupRole(bytes32 role) internal pure virtual returns (bool, uint8) {
bytes32 tagMask = ~bytes32(uint256(0xff));
bytes32 tag = role & tagMask;
uint8 badge = uint8(role[31]);
return (tag == bytes32("badge:"), badge);
uint8 group = uint8(role[31]);
return (tag == bytes32("group:"), group);
}

/**
* @dev Returns a bit mask where the only non-zero bit is the badge number bit.
* @dev Returns a bit mask where the only non-zero bit is the group number bit.
*/
function _badgeMask(uint8 badge) private pure returns (bytes32) {
return bytes32(1 << badge);
function _groupMask(uint8 group) private pure returns (bytes32) {
return bytes32(1 << group);
}

/**
* @dev Returns the value of the badge number bit in a bitmap.
* @dev Returns the value of the group number bit in a bitmap.
*/
function _getBadge(bytes32 bitmap, uint8 badge) private pure returns (bool) {
return bitmap & _badgeMask(badge) > 0;
function _getGroup(bytes32 bitmap, uint8 group) private pure returns (bool) {
return bitmap & _groupMask(group) > 0;
}

/**
* @dev Returns a new badge bitmap where a specific badge was updated.
* @dev Returns a new group bitmap where a specific group was updated.
*/
function _withUpdatedBadge(bytes32 bitmap, uint8 badge, bool value) private pure returns (bytes32) {
bytes32 mask = _badgeMask(badge);
function _withUpdatedGroup(bytes32 bitmap, uint8 group, bool value) private pure returns (bytes32) {
bytes32 mask = _groupMask(group);
if (value) {
return bitmap | mask;
} else {
Expand Down
Loading