0

Having the following code

pragma solidity ^0.6.4;

contract TestModifier {

    mapping (address => bool) public authorities;
    mapping (address => address) public authorized;

    modifier onlyAuthorized(address _user) {
        require(authorities[authorized[_user]], "Requires authorization");
        _;
    }

    // returns false
    function getStatus1(address _user) external view returns (bool) {
        return authorities[authorized[_user]];
    }

    // returns true
    function getStatus2(address _user) external view onlyAuthorized(_user) returns (bool) {
        return authorities[authorized[_user]];
    }

    // returns true
    function getStatus3(address _user) external view onlyAuthorized(_user) returns (bool) {
        return true;
    }
}

Given any address as the input to getStatus functions, I expect to see false as the return value for the first one and an error for the other two. However, getStatus1 returns false and two others return true.

The documentation states

In an earlier version of Solidity, return statements in functions having modifiers behaved differently.

Is there any better explanation for this behavior?

NodeJS Test

const Web3 = require('web3');

const abi = [{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"authorities","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"authorized","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"getStatus1","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"getStatus2","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"getStatus3","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}];
const address = "0x2e2d61Cb7725b5d381879030d308508d26759aDC";

/************ TESTING Ropsten ***********/
const web3Ropsten = new Web3("https://ropsten.infura.io/v3/17a6aa646dfb46ab880ff2c8aa24cbea");
const contractRopsten = new web3Ropsten.eth.Contract(abi, "0x4457f104861f09240d09d857987290f928d09a1e");

contractRopsten.methods.getStatus1(address).call({}, function(error, result){
    console.log("Ropsten::status1: ", result);
});
contractRopsten.methods.getStatus2(address).call({}, function(error, result){
    console.log("Ropsten::status2: ", result);
});
contractRopsten.methods.getStatus3(address).call({}, function(error, result){
    console.log("Ropsten::status3: ", result);
});

/************ TESTING Kovan ***********/
const web3Kovan = new Web3("https://kovan.infura.io/v3/17a6aa646dfb46ab880ff2c8aa24cbea");
const contractKovan = new web3Kovan.eth.Contract(abi, "0x552748c93a38939f1330629a3016a97accd6d0d9");

contractKovan.methods.getStatus1(address).call({}, function(error, result){
    console.log("Kovan::status1: ", result);
});
contractKovan.methods.getStatus2(address).call({}, function(error, result){
    if (error) {
        console.error("Kovan::status2: ", error.message);
    } else {
        console.log("Kovan::status2: ", result);
    }
});
contractKovan.methods.getStatus3(address).call({}, function(error, result){
    if (error) {
        console.error("Kovan::status3: ", error.message);
    } else {
        console.log("Kovan::status3: ", result);
    }
});
25
  • A better explanation - sure, you're calling getStatus1 with a different input than each one of the other two functions. Mar 25 '20 at 16:41
  • Another explanation - after calling getStatus1 and before calling the other two functions, you are calling a function (which does not show up in your question, but exists in your actual code) to set those two mappings. Mar 25 '20 at 16:44
  • I tried your code with remix.ethereum.org and it works as expected. How are you testing?
    – Ismael
    Mar 25 '20 at 22:14
  • I tried it with truffle. Worked as expected. Wonder if you can share us the contract address in a testnet so I can try to call the functions in your deployed version.
    – AlexW
    Mar 26 '20 at 3:45
  • Ropsten testnet with this address: 0x1C50aDCe3279Ddf577ce15738D8f046B8f647f9b Mar 26 '20 at 20:48
0

This is a bug in either web3.js OR the node that you're communicating with.

It happens only if the require statement includes an error-message.

It happens when the node is Infura, but not when the node is Ganache.

6
  • Thanks a lot for your feedback. If I understood it correctly, you mean calling these functions form source code should return properly. Actually I faced this issue first from the backend call. I've updated my question to include a NodeJS code and when I run it, it still returns wrong values from Ropsten. Mar 28 '20 at 11:14
  • @MehranShakeri: I'm actually re-investigating this. See this question, it seems to be exactly the same problem. Technically, web3.js throws an exception when the contract function returns the revert opcode. I suspect that after the upgrade of the public networks (mainnet, ropsten, kovan, etc) to Constantinople, something has changed, for which web3.js doesn't handle it correctly anymore. Since most apps are using web3.js, that leads to a problem. When I deploy your contract on a (local) Ganache network, every function works as expected. Mar 28 '20 at 17:40
  • I will try to interact with your contract on Ropsten. Mar 28 '20 at 17:40
  • @MehranShakeri: Please see updated answer. Mar 28 '20 at 18:38
  • Thank you. Currently I removed the modifier from the function to continue with development. Mar 29 '20 at 18:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.