Can I dynamic create role with OpenZeppelin access control?

I'm learning RBAC on blockchain, by OpenZeppelin Accesscontrol library I can set roles before the contract is deployed.

contract MyToken is  AccessControl {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

    constructor(address minter) {
        _grantRole(MINTER_ROLE, minter);
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }
}

The role set in above contract is Minter_role, which is static.
My question is, how can I create a function to set roles after the contract is deployed? And if it's possible, is this idea useful?

Just like the following codes, although it fails to compile.

    function dynamicCreateRole(bytes32 roleName, string calldata roleString) public pure returns (bytes32)  {
        roleName = keccak256(roleString);
        return roleName;
    }

Hey @Huowuge!

how can I create a function to set roles after the contract is deployed?

Access Control already exposes public functions for granting and revoking roles after the contract is deployed, so by inheriting from it (is AccessControl), your contract will have them.

Note that grantRole and revokeRole can be only called by the admin of the role.

The example you shared might work but is not necessary since the new roles can be computed of chain, and technically, any bytes32 is a valid role.

Hope this helps

Thanks for your reply. You mentioned "any bytes32 is a valid role", it makes me clear.

I actually want to create bytes32 role dynamically after the contract is deployed, and now I noticed that I can not do this. Maybe update the contract is the only approach to do that.

1 Like

I tried my best to finish the following codes. Could anyone check my code and give some advice? Is there any security problem? And could you please push some project code like this?

import "@openzeppelin/contracts/access/AccessControl.sol";


contract X is AccessControl {

    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }
    // dynamic role
    function _encodeString(string calldata stringEncoded) internal pure returns(bytes32) {
        return keccak256(bytes(stringEncoded));
    }
    // admin call this function to grant dynamic role to certain address.
    function dynamicGrantRole(string calldata stringEncoded, address addressSpecific) public {
        grantRole(_encodeString(stringEncoded), addressSpecific);
    }
    // caller of this function should input correct password. 
    function getDynamic(string calldata password) public view {
        require(hasRole(_encodeString(password), msg.sender));
    }
}

After contract deployment you can't really add new roles as you can't change the code (particularly the modifiers). So you'll be stuck with the ones that were created on contract creation e.g. DEFAULT_ADMIN_ROLEand whatever was declared near the top e.g.:

contract MyToken is ... AccessControl {
  bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
  bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

If you want to be more future-proofed you could be highly granular by creating a different role for each function in your contract and use onlyRole() modifier for each, e.g.:

bytes32 public constant FUNCTION1_CALLER_ROLE = keccak256("FUNCTION1_CALLER_ROLE");
bytes32 public constant FUNCTION2_CALLER_ROLE = keccak256("FUNCTION2_CALLER_ROLE");

constructor() ... {
_grantRole(FUNCTION1_CALLER_ROLE, msg.sender);
_grantRole(FUNCTION2_CALLER_ROLE, msg.sender);
...

function function1() public onlyRole(FUNCTION1_CALLER_ROLE) {

function function2() public onlyRole(FUNCTION2_CALLER_ROLE) {

The contract deployer is granted all roles on contract creation (in the constructor). To later change who's allowed to call e.g. function2, you can run something like this JavaScript:

const FUNCTION2_CALLER_ROLE = ethers.keccak256(ethers.toUtf8Bytes("FUNCTION2_CALLER_ROLE"))

const addressOfDeployedContract =  "0x..."
const instanceOfDeployedContract = new ethers.Contract(addressOfDeployedContract, abiOfDeployedContract, signer)

instanceOfDeployedContract.revokeRole(FUNCTION2_CALLER_ROLE, oldAddress)

instanceOfDeployedContract.grantRole(FUNCTION2_CALLER_ROLE, newAddress)