🎓 A1Learner
🔍
0 of 48 lessons completed

Solidity Role Based Access

Role based access is control by assigned roles. Each role is a permission, and only addresses with that role can call certain functions.

Only the owner can grant a role

The owner grants the issuer role to one address. A stranger who tries to grant a role is rejected.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract IssuerControl {
    address public owner;
    mapping(address => bool) public isIssuer;
    mapping(address => uint256) public balances;

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "not owner");
        _;
    }

    function grantIssuer(address account) public onlyOwner {
        isIssuer[account] = true;
    }

    function issue(address to, uint256 amount) public {
        require(isIssuer[msg.sender], "not issuer");
        balances[to] += amount;
    }
}

A issuer can issue, a stranger cannot

The granted address issues successfully. An address without the role is rejected at the same function.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract IssuerControl {
    address public owner;
    mapping(address => bool) public isIssuer;
    mapping(address => uint256) public balances;

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "not owner");
        _;
    }

    function grantIssuer(address account) public onlyOwner {
        isIssuer[account] = true;
    }

    function issue(address to, uint256 amount) public {
        require(isIssuer[msg.sender], "not issuer");
        balances[to] += amount;
    }
}

Revoking a role removes the permission

The owner revokes the role after granting it. The former issuer is rejected just like a stranger.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract IssuerControl {
    address public owner;
    mapping(address => bool) public isIssuer;
    mapping(address => uint256) public balances;

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "not owner");
        _;
    }

    function grantIssuer(address account) public onlyOwner {
        isIssuer[account] = true;
    }

    function revokeIssuer(address account) public onlyOwner {
        isIssuer[account] = false;
    }

    function issue(address to, uint256 amount) public {
        require(isIssuer[msg.sender], "not issuer");
        balances[to] += amount;
    }
}

Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract IssuerControl {
    address public owner;
    mapping(address => bool) public isIssuer;
    mapping(address => uint256) public balances;

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "not owner");
        _;
    }

    function grantIssuer(address account) public onlyOwner {
        isIssuer[account] = true;
    }

    function revokeIssuer(address account) public onlyOwner {
        isIssuer[account] = false;
    }

    function issue(address to, uint256 amount) public {
        require(isIssuer[msg.sender], "not issuer");
        balances[to] += amount;
    }
}

Key points

  • Role: a named permission, stored as a true or false value per address.
  • Grant: the owner sets an address role to true.
  • Revoke: the owner sets an address role back to false.
  • Modifier: a check that requires the caller to hold the role.
  • Owner: the single-role special case, where only one address may call.

In this example, the owner grants the issuer role to two addresses. Both issue their amounts. The owner then revokes one issuer, and that address is rejected on its next issue while the other issuer keeps working.

Note

granting a role gives no ether. It only unlocks the protected function.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Remix IDE

Exercise

What happens after the owner revokes a issuer role?