Using AccessControl hasRole?

I am using AccessControl and created a new role SPECIAL_ROLE perform special tasks, for example below

function doSomethingSpecial() public returns (bool) {
    require(hasRole(SPECIAL_ROLE, _msgSender()), "Error: must have SPECIAL_ROLE");
    return true;
}

If I have the above function, I get a warning to make it view function when I try to execute from an account which does not have the SPECIAL_ROLE.

Without specifying the function as a view function, it triggers a Metamask warning allowing me to reject the transaction, although I don’t know the reason for the warning.

If I specify the function as a view function,

function doSomethingSpecial() public view returns (bool) {
    require(hasRole(SPECIAL_ROLE, _msgSender()), "Error: must have SPECIAL_ROLE");
    return true;
}

it does not trigger Metamask to allow a reject of the transaction and no errors are logged on the console and there is no warning or reason for the rejection.

This is using truffle to compile and deploy to Rinkeby Testnet. When using Remix, I do get console revert messages and the reason for the failure, but perhaps this is only on the JV (haven’t tried injected Web3)

Is this the expected behavior? And how can I better alert the user with a warning or message that special privileges are required?

1 Like

Researching further, I guess it’s a solidity/web3 issue in that the require error message is not handled, whereas it is in Remix. Anyone, please correct me if I’m wrong.

1 Like

Hi @guiguy,

I assume that in your doSomethingSpecial function that you will change state.

Currently your function doesn't change state so the compiler will suggest that you could make it a view function.

When using JavaScript VM on Remix and calling from an account which doesn't have the role, then the revert error will be displayed.

When using MetaMask on a public network and calling from an account which doesn't have the role, then MetaMask shows:

Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending?
gas required exceeds allowance (8000000) or always failing transaction

If you proceed with the transaction, it will fail and Etherscan will show the revert reason (if it can).
https://ropsten.etherscan.io/tx/0x8753fef48995bd18de75980010a3d93ea283b11ff084b9828073da3956d78ef6

Until fairly recently, only ganache-cli provided the revert reasons making determining the reason why a transaction failed on a public network a challenge.

This is expected behaviour. Whilst the smart contract will enforce access control, on your front end you can make checks to advise users if they don't have permission before they create a transaction.

I used the following contract on Remix to check.

MyContract.sol

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

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.1.0/contracts/access/AccessControl.sol";

contract MyContract is AccessControl {
    bytes32 public constant SPECIAL_ROLE = keccak256("SPECIAL_ROLE");
    constructor() public {
        _setupRole(SPECIAL_ROLE, msg.sender);
    }
    
    function doSomethingSpecial() public returns (bool) {
        require(hasRole(SPECIAL_ROLE, msg.sender), "MyContract: must have SPECIAL_ROLE");
        return true;
    }
}

ok thanks.
The doSomethingSpecial function does not change state, just returns uint. If the calling account has the role, the correct value is returned, otherwise it will strangely return 396387, without any errors.

If it is a view function, Metamask option to reject the transaction does not get triggered.

I think it is the injected web3 not handling the require rejection properly and not returning the reason.

1 Like

Hi @guiguy,

In case you aren't aware, adding access control to a view function only restricts accessing the values on chain. Off chain any data in a contract can be read.

As for the error 396387... that you are seeing, please see the following post for an explanation: