Skip to content

Commit

Permalink
feat: admin 2-step
Browse files Browse the repository at this point in the history
  • Loading branch information
ypatil12 committed Nov 22, 2024
1 parent 343b44e commit 66aa6e9
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 43 deletions.
50 changes: 45 additions & 5 deletions src/contracts/interfaces/IPermissionController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ pragma solidity ^0.8.27;
interface IPermissionControllerErrors {
/// @notice Thrown when the caller is not the admin
error NotAdmin();
/// @notice Thrown when an admin is set to the zero address
error AdminAlreadySet();
/// @notice Thrown when the admin to remove is not an admin
error AdminNotSet();
/// @notice Thrown when an appointee is already set for the account's function
Expand All @@ -14,6 +12,12 @@ interface IPermissionControllerErrors {
error AppointeeNotSet();
/// @notice Thrown when the account attempts to remove the only admin
error CannotHaveZeroAdmins();
/// @notice Thrown when an admin is already set
error AdminAlreadySet();
/// @notice Thrown when an admin is not pending
error AdminNotPending();
/// @notice Thrown when an admin is already pending
error AdminAlreadyPending();
}

interface IPermissionControllerEvents {
Expand All @@ -23,6 +27,12 @@ interface IPermissionControllerEvents {
/// @notice Emitted when an appointee is revoked
event AppointeeRemoved(address indexed account, address indexed appointee, address target, bytes4 selector);

/// @notice Emitted when an admin is set as pending for an account
event PendingAdminAdded(address indexed account, address admin);

/// @notice Emitted when an admin is removed as pending for an account
event PendingAdminRemoved(address indexed account, address admin);

/// @notice Emitted when an admin is set for a given account
event AdminSet(address indexed account, address admin);

Expand All @@ -32,12 +42,29 @@ interface IPermissionControllerEvents {

interface IPermissionController is IPermissionControllerErrors, IPermissionControllerEvents {
/**
* @notice Set the admin of an account
* @param account to set admin for
* @notice Sets a pending admin of an account
* @param account to set pending admin for
* @param admin to set
* @dev Multiple admins can be set for an account
*/
function addAdmin(address account, address admin) external;
function addPendingAdmin(address account, address admin) external;

/**
* @notice Removes a pending admin of an account
* @param account to remove pending admin for
* @param admin to remove
* @dev Only the admin of the account can remove a pending admin
*/
function removePendingAdmin(address account, address admin) external;

/**
* @notice Accepts the admin role of an account
* @param account to accept admin for
* @dev Only a pending admin for the account can become an admin
*/
function acceptAdmin(
address account
) external;

/**
* @notice Remove an admin of an account
Expand Down Expand Up @@ -74,6 +101,11 @@ interface IPermissionController is IPermissionControllerErrors, IPermissionContr
*/
function isAdmin(address account, address caller) external view returns (bool);

/**
* @notice Checks if the `pendingAdmin` is a pending admin of the `account`
*/
function isPendingAdmin(address account, address pendingAdmin) external view returns (bool);

/**
* @notice Get the admins of an account
* @param account The account to get the admin of
Expand All @@ -83,6 +115,14 @@ interface IPermissionController is IPermissionControllerErrors, IPermissionContr
address account
) external view returns (address[] memory);

/**
* @notice Get the pending admins of an account
* @param account The account to get the pending admin of
*/
function getPendingAdmins(
address account
) external view returns (address[] memory);

/**
* @notice Checks if the given caller has permissions to call the fucntion
* @param account to check
Expand Down
56 changes: 50 additions & 6 deletions src/contracts/permissions/PermissionController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,45 @@ contract PermissionController is Initializable, PermissionControllerStorage {
*/

/// @inheritdoc IPermissionController
function addAdmin(address account, address admin) external onlyAdmin(account) {
function addPendingAdmin(address account, address admin) external onlyAdmin(account) {
AccountPermissions storage permissions = _permissions[account];

// Revert if the admin is already set
require(!permissions.admins.contains(admin), AdminAlreadySet());

// Add the admin to the account's pending admins
// If the admin is already pending, the add will fail
require(permissions.pendingAdmins.add(admin), AdminAlreadyPending());

emit PendingAdminAdded(account, admin);
}

/// @inheritdoc IPermissionController
function removePendingAdmin(address account, address admin) external onlyAdmin(account) {
EnumerableSet.AddressSet storage pendingAdmins = _permissions[account].pendingAdmins;

// Remove the admin from the account's pending admins
// Revert if the admin is not pending
require(pendingAdmins.remove(admin), AdminNotPending());

emit PendingAdminRemoved(account, admin);
}

/// @inheritdoc IPermissionController
function acceptAdmin(
address account
) external {
AccountPermissions storage permissions = _permissions[account];

// Remove the admin from the pending list
// Revert if the admin is not pending
require(permissions.pendingAdmins.remove(msg.sender), AdminNotPending());

// Add the admin to the account's admins
// If the admin is already set, the set will fail
EnumerableSet.AddressSet storage admins = _permissions[account].admins;
require(admins.add(admin), AdminAlreadySet());
// Not wrapped in a require since it must be the case the admin is not one
permissions.admins.add(msg.sender);

emit AdminSet(account, admin);
emit AdminSet(account, msg.sender);
}

/// @inheritdoc IPermissionController
Expand Down Expand Up @@ -110,7 +142,7 @@ contract PermissionController is Initializable, PermissionControllerStorage {
}

/// @dev Decodes the target and selector from a single bytes32 value
/// @dev Encoded Format: [160 bits target][32 bits selector][64 bits padding],
/// @dev Encoded Format: [160 bits target][32 bits selector][64 bits padding],
function _decodeTargetSelector(
bytes32 targetSelector
) internal pure returns (address, bytes4) {
Expand Down Expand Up @@ -139,6 +171,11 @@ contract PermissionController is Initializable, PermissionControllerStorage {
}
}

/// @inheritdoc IPermissionController
function isPendingAdmin(address account, address pendingAdmin) external view returns (bool) {
return _permissions[account].pendingAdmins.contains(pendingAdmin);
}

/// @inheritdoc IPermissionController
function getAdmins(
address account
Expand All @@ -152,6 +189,13 @@ contract PermissionController is Initializable, PermissionControllerStorage {
}
}

/// @inheritdoc IPermissionController
function getPendingAdmins(
address account
) external view returns (address[] memory) {
return _permissions[account].pendingAdmins.values();
}

/// @inheritdoc IPermissionController
function canCall(address account, address caller, address target, bytes4 selector) external view returns (bool) {
return isAdmin(account, caller)
Expand Down
2 changes: 2 additions & 0 deletions src/contracts/permissions/PermissionControllerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ abstract contract PermissionControllerStorage is IPermissionController {
using EnumerableSet for EnumerableSet.AddressSet;

struct AccountPermissions {
/// @notice The pending admins of the account
EnumerableSet.AddressSet pendingAdmins;
/// @notice The admins of the account
EnumerableSet.AddressSet admins;
/// @notice Mapping from an appointee to the list of encoded target & selectors
Expand Down
Loading

0 comments on commit 66aa6e9

Please sign in to comment.