CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-openzeppelin-solidity

Secure Smart Contract library providing battle-tested implementations of industry-standard Solidity contracts including ERC20, ERC721, ERC1155 tokens, access control mechanisms, proxy patterns, and governance systems for Ethereum and EVM-compatible blockchains.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

access-control.mddocs/

Access Control

OpenZeppelin Contracts provides flexible access control mechanisms through role-based and ownership-based patterns, enabling secure function access management in smart contracts.

Role-Based Access Control

AccessControl

Flexible role-based access control mechanism where roles are identified by bytes32 identifiers.

abstract contract AccessControl is Context, IAccessControl, ERC165 {
    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
    
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool);
    function hasRole(bytes32 role, address account) public view virtual returns (bool);
    function getRoleAdmin(bytes32 role) public view virtual returns (bytes32);
    function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role));
    function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role));
    function renounceRole(bytes32 role, address account) public virtual;
}

interface IAccessControl {
    function hasRole(bytes32 role, address account) external view returns (bool);
    function getRoleAdmin(bytes32 role) external view returns (bytes32);
    function grantRole(bytes32 role, address account) external;
    function revokeRole(bytes32 role, address account) external;
    function renounceRole(bytes32 role, address account) external;
}

Events:

  • RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)
  • RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)
  • RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)

Modifiers:

  • onlyRole(bytes32 role) - Restricts access to accounts with specific role

Usage Example:

contract MyContract is AccessControl {
    bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");

    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(MANAGER_ROLE, msg.sender);
    }

    function managerOnlyFunction() public onlyRole(MANAGER_ROLE) {
        // Only accounts with MANAGER_ROLE can call this
    }

    function operatorFunction() public onlyRole(OPERATOR_ROLE) {
        // Only accounts with OPERATOR_ROLE can call this
    }
}

AccessControlEnumerable

Extension of AccessControl that allows enumeration of role members.

abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool);
    function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address);
    function getRoleMemberCount(bytes32 role) public view virtual returns (uint256);
}

interface IAccessControlEnumerable is IAccessControl {
    function getRoleMember(bytes32 role, uint256 index) external view returns (address);
    function getRoleMemberCount(bytes32 role) external view returns (uint256);
}

Usage Example:

contract MyEnumerableContract is AccessControlEnumerable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

    function getAllMinters() public view returns (address[] memory) {
        uint256 count = getRoleMemberCount(MINTER_ROLE);
        address[] memory minters = new address[](count);
        for (uint256 i = 0; i < count; i++) {
            minters[i] = getRoleMember(MINTER_ROLE, i);
        }
        return minters;
    }
}

AccessControlCrossChain

Extension for cross-chain access control scenarios.

abstract contract AccessControlCrossChain is AccessControl, CrossChainEnabled {
    function hasRole(bytes32 role, address account) public view virtual override returns (bool);
}

Ownership-Based Access Control

Ownable

Simple ownership access control with a single owner.

abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    constructor();
    
    function owner() public view virtual returns (address);
    function renounceOwnership() public virtual onlyOwner;
    function transferOwnership(address newOwner) public virtual onlyOwner;
}

Events:

  • OwnershipTransferred(address indexed previousOwner, address indexed newOwner)

Modifiers:

  • onlyOwner() - Restricts access to the contract owner

Usage Example:

contract MyOwnableContract is Ownable {
    uint256 public value;

    function setValue(uint256 _value) public onlyOwner {
        value = _value;
    }

    function emergencyStop() public onlyOwner {
        // Only owner can call emergency functions
    }
}

Ownable2Step

Two-step ownership transfer for enhanced security.

abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

    function pendingOwner() public view virtual returns (address);
    function transferOwnership(address newOwner) public virtual override onlyOwner;
    function acceptOwnership() external;
}

Events:

  • OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner)

Usage Example:

contract SecureContract is Ownable2Step {
    function initiateOwnershipTransfer(address newOwner) public onlyOwner {
        transferOwnership(newOwner);
        // newOwner must call acceptOwnership() to complete the transfer
    }
}

Access Control Patterns

Combining Access Control Mechanisms

You can combine different access control patterns for more sophisticated permission systems:

contract HybridAccessControl is AccessControl, Ownable {
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 public constant USER_ROLE = keccak256("USER_ROLE");

    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(ADMIN_ROLE, msg.sender);
    }

    // Only owner or admin role can call
    function sensitiveFunction() public {
        require(
            owner() == msg.sender || hasRole(ADMIN_ROLE, msg.sender),
            "Not authorized"
        );
        // Function logic
    }

    // Only owner can grant admin role
    function grantAdminRole(address account) public onlyOwner {
        grantRole(ADMIN_ROLE, account);
    }
}

Role Hierarchy

Create role hierarchies by setting role admins:

contract HierarchicalRoles is AccessControl {
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); 
    bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");

    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(ADMIN_ROLE, msg.sender);
        
        // Set role hierarchy
        _setRoleAdmin(MANAGER_ROLE, ADMIN_ROLE);
        _setRoleAdmin(OPERATOR_ROLE, MANAGER_ROLE);
    }

    function adminFunction() public onlyRole(ADMIN_ROLE) {
        // Only admins can call
    }

    function managerFunction() public onlyRole(MANAGER_ROLE) {
        // Only managers can call
    }

    function operatorFunction() public onlyRole(OPERATOR_ROLE) {
        // Only operators can call
    }
}

Security Best Practices

Role Management

  1. Least Privilege: Grant minimum required permissions
  2. Role Separation: Separate concerns into different roles
  3. Admin Role Protection: Carefully manage DEFAULT_ADMIN_ROLE holders
  4. Role Renunciation: Allow role holders to renounce their roles

Ownership Transfer

  1. Two-Step Transfer: Use Ownable2Step for critical contracts
  2. Verification: Always verify new owner addresses
  3. Emergency Procedures: Have procedures for ownership recovery
  4. Multi-Signature: Consider multi-signature wallets for critical owners

Common Pitfalls

  1. Role Admin Loops: Avoid circular role admin dependencies
  2. Ownership Renunciation: Be cautious about renouncing ownership permanently
  3. Role Discovery: Remember that role membership is public on-chain
  4. Gas Costs: Consider gas costs when enumerating role members

Testing Access Control

// Example test patterns
contract AccessControlTest {
    function testOnlyOwnerFunctions() public {
        // Test that only owner can call owner-only functions
        vm.prank(owner);
        contract.ownerOnlyFunction();
        
        vm.expectRevert("Ownable: caller is not the owner");
        vm.prank(user);
        contract.ownerOnlyFunction();
    }

    function testRoleBasedAccess() public {
        // Test role-based access
        vm.prank(admin);
        contract.grantRole(MANAGER_ROLE, manager);
        
        vm.prank(manager);
        contract.managerFunction();
        
        vm.expectRevert();
        vm.prank(user);
        contract.managerFunction();
    }
}

Install with Tessl CLI

npx tessl i tessl/npm-openzeppelin-solidity

docs

access-control.md

crosschain.md

finance.md

governance.md

index.md

metatx.md

proxy.md

security.md

tokens.md

utilities.md

tile.json