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);
    }
});
  • A better explanation - sure, you're calling getStatus1 with a different input than each one of the other two functions. – goodvibration Mar 25 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. – goodvibration Mar 25 at 16:44
  • I tried your code with remix.ethereum.org and it works as expected. How are you testing? – Ismael Mar 25 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 at 3:45
  • Ropsten testnet with this address: 0x1C50aDCe3279Ddf577ce15738D8f046B8f647f9b – Mehran Shakeri Mar 26 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.

| improve this answer | |
  • 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. – Mehran Shakeri Mar 28 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. – goodvibration Mar 28 at 17:40
  • I will try to interact with your contract on Ropsten. – goodvibration Mar 28 at 17:40
  • @MehranShakeri: Please see updated answer. – goodvibration Mar 28 at 18:38
  • Thank you. Currently I removed the modifier from the function to continue with development. – Mehran Shakeri Mar 29 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.