/// @title Roles Authority
/// @notice SPDX-License-Identifier: MIT
/// @author asnared <https://github.com/abigger87>
/// @notice A Role based Authority that supports up to 256 roles
/// @notice Adapted from Solmate (<https://github.com/transmissions11/solmate/blob/main/src/auth/authorities/RolesAuthority.sol)
// Imports
#include "./Auth.huff"
#include "../data-structures/Hashmap.huff"
// Interface
#define function hasRole(address, uint8) view returns (bool)
#define function doesRoleHaveCapability(uint8, address, bytes4) nonpayable returns (bool)
#define function canCall(address, address, bytes4) nonpayable returns (bool)
#define function setPublicCapability(address, bytes4, bool) nonpayable returns ()
#define function setRoleCapability(uint8, address, bytes4, bool) nonpayable returns ()
#define function setUserRole(address, uint8, bool) nonpayable returns ()
// Events
#define event UserRoleUpdated(address indexed user, uint8 indexed role, bool enabled)
#define event PublicCapabilityUpdated(address indexed target, bytes4 indexed functionSig, bool enabled)
#define event RoleCapabilityUpdated(uint8 indexed role, address indexed target, bytes4 indexed functionSig, bool enabled)
// MAPPINGS
#define constant USER_ROLES_LOCATION = FREE_STORAGE_POINTER()
#define constant IS_CAPABILITY_PUBLIC_LOCATION = FREE_STORAGE_POINTER()
#define constant GET_ROLES_WITH_CAPABILITY_LOCATION = FREE_STORAGE_POINTER()
/// @notice Helper to get the roles for a user
#define macro GET_ROLES_FOR_ACCOUNT() = takes (1) returns (1) {
[USER_ROLES_LOCATION] // [location, account]
LOAD_ELEMENT_FROM_KEYS(0x00) // [roles]
}
/// @notice Returns if an account has a role
/// @param {account} [address] - The account to check roles against
/// @param {role} [uint8] - The role to check
/// @return {bool} - If the account has the role
#define macro HAS_ROLE() = takes (0) returns (0) {
0x04 calldataload // [account]
GET_ROLES_FOR_ACCOUNT() // [roles]
0x24 calldataload // [role, roles]
shr 0x01 and // [authed]
0x00 mstore // []
0x20 0x00 return // []
}
/// @notice Helper to get the role for a capability
#define macro GET_ROLE_FOR_CAPABILITY() = takes (2) returns (1) {
LOAD_ELEMENT_FROM_KEYS(0x00) // [role]
}
/// @notice Checks if a Role has a Capability
/// @param {role} [uint8] - The role to check
/// @param {target} [address] - The target contract
/// @param {functionSig} [bytes4] - The function signature
/// @return {bool} - If the role has the capability
#define macro ROLE_CAPABILITY() = takes (0) returns (0) {
0x44 calldataload // [sig]
0x24 calldataload // [target, sig]
GET_ROLE_FOR_CAPABILITY() // [role]
0x04 calldataload // [input, role]
shr 0x01 and // [authed]
0x00 mstore // []
0x20 0x00 return // []
}
/// @notice Helper to get if a capability is public
#define macro IS_CAPABILITY_PUBLIC() = takes (2) returns (1) {
LOAD_ELEMENT_FROM_KEYS(0x00) // [public]
}
/// @notice Checks if an account can call a function on a given address
/// Does the account have a role with the respective capability
/// @param {account} [address] - The account to check
/// @param {target} [address] - The target contract
/// @param {functionSig} [bytes4] - The function signature
/// @return {bool} - If the account can call the function
#define macro CAN_CALL() = takes (0) returns (0) {
// Is the capability public
// return isCapabilityPublic[target][functionSig]
0x44 calldataload // [sig]
0x24 calldataload // [target, sig]
IS_CAPABILITY_PUBLIC() // [public]
approved jumpi // []
// Check if the user has the role for the capability
// return bytes32(0) != getUserRoles[user] & getRolesWithCapability[target][functionSig]
0x04 calldataload // [account]
GET_ROLES_FOR_ACCOUNT() // [roles]
0x44 calldataload // [sig, roles]
0x24 calldataload // [target, sig, roles]
GET_ROLE_FOR_CAPABILITY() // [capabilities, roles]
and approved jumpi // []
// Return false otherwise
false 0x00 mstore
0x20 0x00 return
approved:
true 0x00 mstore
0x20 0x00 return
}
/// @notice Set a Public Capability
/// @notice Requires Auth
/// @param {target} [address] - The target contract
/// @param {functionSig} [bytes4] - The function signature
/// @param {value} [bool] - The value to set
#define macro SET_PUBLIC_CAPABILITY() = takes (0) returns (0) {
// Check that the caller is authorized
REQUIRES_AUTH()
// Set the capability to the passed in value
0x44 calldataload // [value]
0x24 calldataload // [sig, value]
0x04 calldataload // [target, sig, value]
STORE_ELEMENT_FROM_KEYS(0x00) // []
// Emit the capability updated event
0x44 calldataload // [value]
0x24 calldataload // [func, value]
0x04 calldataload // [target, func, value]
__EVENT_HASH(PublicCapabilityUpdated) // [sig, target, func, value]
0x00 0x00 // [0, 0, sig, target, func, value]
log3 // []
// End Execution
stop
}
/// @notice Sets the capability for a role
/// @notice Requires Auth
/// @param {role} [uint8] - The role to set the capability for
/// @param {target} [address] - The target contract
/// @param {functionSig} [bytes4] - The function signature
/// @param {value} [bool] - The value to set
#define macro SET_ROLE_CAPABILITY() = takes (0) returns (0) {
// Check that the caller is authorized
REQUIRES_AUTH()
// Check if enabled
0x64 calldataload // [value]
enable jumpi // []
// Disable the capability
disable:
// Get the current roles with the capability
0x44 calldataload // [sig]
0x24 calldataload // [target, sig]
GET_ROLE_FOR_CAPABILITY() // [roles]
// Shift 1 left the role
0x01 0x04 calldataload shl // [role, roles]
not // [others, roles]
and // [updated]
// Store the new capability
0x44 calldataload // [sig, updated]
0x24 calldataload // [target, sig, updated]
STORE_ELEMENT_FROM_KEYS(0x00) // []
// Jump to the emit log label
emit_log jump
// Enable the capability
enable:
// Get the current roles with the capability
0x44 calldataload // [sig]
0x24 calldataload // [target, sig]
GET_ROLE_FOR_CAPABILITY() // [roles]
// Shift 1 left the role
0x01 0x04 calldataload shl // [role, roles]
or // [capabilies]
// Store the new capability
0x44 calldataload // [sig, capabilies]
0x24 calldataload // [target, sig, capabilies]
STORE_ELEMENT_FROM_KEYS(0x00) // []
// Emit the capability updated event
emit_log:
0x64 calldataload // [enabled]
0x44 calldataload // [func, enabled]
0x24 calldataload // [target, func, enabled]
0x04 calldataload // [role, target, func, enabled]
__EVENT_HASH(RoleCapabilityUpdated) // [sig, role, target, func, enabled]
0x00 0x00 // [0, 0, sig, role, target, func, enabled]
log4 // []
// End Execution
stop
}
/// @notice Sets an accounts role
/// @notice Requires Auth
/// @param {account} [address] - The account to set the role for
/// @param {newRole} [uint8] - The new role
/// @param {enabled} [bool] - Whether to enable or disable the role
#define macro SET_ACCOUNT_ROLE() = takes (0) returns (0) {
// Check that the caller is authorized
REQUIRES_AUTH()
// Check if enabled
0x44 calldataload // [enabled]
enable jumpi // []
// Disable the role
disable:
// Get the account roles
0x04 calldataload // [account]
GET_ROLES_FOR_ACCOUNT() // [roles]
// Shift 1 left the role
0x01 0x24 calldataload shl // [new_role, roles]
not // [others, roles]
and // [updated]
// Store the new roles
0x04 calldataload // [account, updated]
[USER_ROLES_LOCATION] // [key, account, updated]
STORE_ELEMENT_FROM_KEYS(0x00) // []
// Jump to the emit log label
emit_log jump
// Enable the role
enable:
// Get the account roles
0x04 calldataload // [account]
GET_ROLES_FOR_ACCOUNT() // [roles]
// Shift 1 left the role
0x01 0x24 calldataload shl // [new_role, roles]
or // [updated]
// Store the new roles
0x04 calldataload // [account, updated]
[USER_ROLES_LOCATION] // [key, account, updated]
STORE_ELEMENT_FROM_KEYS(0x00) // []
// Emit the user role updated event
emit_log:
0x44 calldataload // [enabled]
0x24 calldataload // [role, enabled]
0x04 calldataload // [account, role, enabled]
__EVENT_HASH(UserRoleUpdated) // [sig, account, role, enabled]
0x00 0x00 // [0, 0, sig, account, role, enabled]
log3 // []
// End Execution
stop
}
/// @notice Main Function Dispatcher
#define macro ROLES_AUTHORITY_MAIN() = takes (1) returns (1) {
// Input Stack: [function_selector]
dup1 __FUNC_SIG(hasRole) eq has_role jumpi
dup1 __FUNC_SIG(doesRoleHaveCapability) eq role_capability jumpi
dup1 __FUNC_SIG(canCall) eq can_call jumpi
dup1 __FUNC_SIG(setPublicCapability) eq set_public_capability jumpi
dup1 __FUNC_SIG(setRoleCapability) eq set_role_capability jumpi
dup1 __FUNC_SIG(setUserRole) eq set_account_role jumpi
// Auth Sigs
dup1 __FUNC_SIG(setOwner) eq set_owner jumpi
dup1 __FUNC_SIG(setAuthority) eq set_authority jumpi
dup1 __FUNC_SIG(owner) eq owner jumpi
dup1 __FUNC_SIG(authority) eq authority jumpi
// Bubble up function selector to parent macro
no_match jump
has_role:
HAS_ROLE()
role_capability:
ROLE_CAPABILITY()
can_call:
CAN_CALL()
set_public_capability:
SET_PUBLIC_CAPABILITY()
set_role_capability:
SET_ROLE_CAPABILITY()
set_account_role:
SET_ACCOUNT_ROLE()
set_owner:
SET_OWNER()
set_authority:
SET_AUTHORITY()
owner:
OWNER()
authority:
AUTHORITY()
no_match:
}