Account 0x0000000000000000000000000000000000000000 is missing role

Question

Hey, I deployed a upgradable ERC1155 smart contact with access control. I believe that have necessaey knowleade about 'Access Contol' such as reson behind DEFAULT_ADMIN_ROLE, roleAdmin, etc. I assigned a role 'MINTER_ROLE' to some functions to restrict access and i also grant 'MINTER_ROLE' role to an specific address in constructor. But when i tried to access the function(which has access only by the address with 'MINTER_ROLE') with 'MINTER_ROLE' in etherscan i got an error. then i tried the same thing in remix it works well without any error.
ERROR MESSAGE GOT IN SEPOLIA ETHERSCAN:

Error: execution reverted: AccessControl: account 0x0000000000000000000000000000000000000000 is missing role 0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6 { "originalError": { "code": 3, "data": "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000094416363657373436f6e74726f6c3a206163636f756e7420307830303030303030303030303030303030303030303030303030303030303030303030303030303030206973206d697373696e6720726f6c6520307839663264663066656432633737363438646535383630613463633530386364303831386338356238623861316162346365656566386439383163383935366136000000000000000000000000", "message": "execution reverted: AccessControl: account 0x0000000000000000000000000000000000000000 is missing role 0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6" } }

NOTE: 0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6 is an encode output of keccal256("MINTER_ROLE")
After i grant access to zero address to 'MINER_ROLE' the function works properly.

My Question is "Why Zero Adderss(0x0000000000000000000000000000000000000000) need access to the function in seoplia etherscan?

:1234: Code

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

import "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155URIStorageUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract MAURewards is
    Initializable,
    ERC1155Upgradeable,
    AccessControlUpgradeable,
    ERC1155URIStorageUpgradeable,
    UUPSUpgradeable
{
    bytes32 private constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
    uint256 private numOfRewards;
    mapping(uint256 => uint256) private _totalSupply;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize() public initializer {
        __ERC1155_init("https://www.arweave.net/");
        __AccessControl_init();
        __UUPSUpgradeable_init();

        _setBaseURI("https://www.arweave.net/");
        _grantRole(
            DEFAULT_ADMIN_ROLE,
            0xcC4351eF0555f636A46DaaF82BEfa8F801D50155
        );
        _grantRole(MINTER_ROLE, msg.sender);
        _grantRole(UPGRADER_ROLE, msg.sender);
    }

    function setNumOfRewards(
        uint256 _numOfRewards
    ) public onlyRole(MINTER_ROLE) {
        numOfRewards = _numOfRewards;
    }

    function getNumOfRewards()
        public
        view
        onlyRole(MINTER_ROLE)
        returns (uint256)
    {
        return numOfRewards;
    }

    function setTokenEndpointURI(
        uint256 tokenId,
        string memory tokenEndpointURI
    ) public onlyRole(MINTER_ROLE) {
        require(
            tokenId < numOfRewards,
            "You can't set Endpoint to this Reward, Because Reward does not exist"
        );
        _setURI(tokenId, tokenEndpointURI);
    }

    function uri(
        uint256 tokenId
    )
        public
        view
        virtual
        override(ERC1155Upgradeable, ERC1155URIStorageUpgradeable)
        returns (string memory)
    {
        return ERC1155URIStorageUpgradeable.uri(tokenId);
    }

    function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal override {
        require(
            from == address(0),
            "You can't transfer the Token, Because it is SBT"
        );
        if (from == address(0)) {
            for (uint256 i = 0; i < ids.length; ++i) {
                _totalSupply[ids[i]] += amounts[i];
            }
        }
    }

    function safeMint(
        address account,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) public onlyRole(MINTER_ROLE) {
        require(
            id < numOfRewards,
            "You can't mint this Reward, Because Reward does not exist"
        );
        _mint(account, id, amount, data);
    }

    function mintBatch(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public onlyRole(MINTER_ROLE) {
        _mintBatch(to, ids, amounts, data);
    }

    function totalSupply(
        uint256 id
    ) public view onlyRole(MINTER_ROLE) returns (uint256) {
        return _totalSupply[id];
    }

    function exists(
        uint256 id
    ) public view onlyRole(MINTER_ROLE) returns (bool) {
        return id < numOfRewards;
    }

    function _authorizeUpgrade(
        address newImplementation
    ) internal override onlyRole(UPGRADER_ROLE) {}

    // The following functions are overrides required by Solidity.

    function supportsInterface(
        bytes4 interfaceId
    )
        public
        view
        override(ERC1155Upgradeable, AccessControlUpgradeable)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}

Additional Details

Contract: ERC1155 UUPS Upgradable Contact
Problem: In Access Control
Network: Sepolia Testnet
Etherscan: Got Error
Remix IDE: Works well. without error

Hello @iamRishvanth

Can you share the address of the deployed contract on sepolia (or even better, the hash of a failling transaction) ?

I'd be curious to which function call triggers the error.

On second thought, I'm gessing the issue is with getNumOfRewards

If you do myContract.getNumOfRewards() to view the value, this action is not triggering a transaction (which has a msg.sender) but instead is "performing the call offchain". The context of this call depends on the client you are using, but it may use address(0) as the sender.

AFAIK, you have 2 solutions here:

  • tell the client that you want the view call to use a particular sender (how this is done depend on the library you are using, and might be affected by the client you are connected to)
  • remove access control from view functions.

Note: I don't really see why you would put access control on a view function. The data is public anyway (and in this particular case I'm not sure why you would hide it from other smart contracts)

Address of Deployed Contract:

Proxy Contract: 0x9c51620667FF0c97D311bd4EE08ED4b4f70379eB
Implementation Contact: 0x6Ade907Ba560113B6013324c05083Df8b0eBfd59

I don't know what is 'hashing of a falling transaction'?
could you please explain what is 'hashing of a falling transaction'?

No, I faced the issue for all the functions which has 'onlyRole(MINTER_ROLE)" modifier such as exist(), setNumOfRewards(), getNumOfRewards(), etc.

But I didn't get any Issue for hasRole() function. I think it because it does not have any modifier.

And I also have another doubt.

function _authorizeUpgrade(
        address newImplementation
    ) internal override onlyRole(UPGRADER_ROLE) {}

The above code is provide by Openzeppelin wizard for the Contact with UUPS Upgradability by default. And also the _authorizeUpgrade does not have any logic inside it.
Why it is included by default?
What's the purpose?
Should i include anything inside the _authorizeUpgrade() ?

This is correct. Its the way UUPS upgradeable controls who can upgrade. Its called from here.

When you submit a transaction that fails, the failed transaction is registered on the blockchain. This transaction is identified by a hash.

Unless we can inspect a transaction that fails its hard to say why it is failing.

exists() and getNumOfRewards() are both view function. It might be caused by what I described above. IMO view function should not have these modifier (I don't see why anyone wouldn't be allowed to read them).

setNumOfRewards() is not view, so its different. Again, we would need to inspect a failing transaction.

Based on this, I have managed to locate the following on sepolia.etherscan:

Looking into either one of them (independently), it is clear that you have been able to successfully execute setNumOfRewards, which means that your claim:

Is clearly false with respect to function setNumOfRewards.

With respect to the other functions that you've mentioned - exist and getNumOfRewards - as already stated in @Amxx's answer - those are read-only (view or pure) functions, which do not require a signed transaction in order to be called, hence it is likely that when onlyRole(MINTER_ROLE) is executed in them, the value of msg.sender is the zero address (since no Ethereum account is used in order to sign a transaction when each one of these functions is called).