Solidity: Shadowing of inherited state variables should be an error (override keyword)

Created on 12 Jul 2017  路  20Comments  路  Source: ethereum/solidity

Code like the following should be an error:

contract Base {
  uint x;
}
contract Derived is Base {
  uint x;
}

Functions defined in Base that use the x state variable will access Base.x, and those defined in Derived will silently access the different slot Derived.x. This is a common problem for beginners that don't understand inheritance very well, and it could happen accidentally to anyone as well. The compiler should fail to compile it to prevent these errors.

This is different (and I believe more serious) than #973. It is about state variables shadowing other (_inherited_) state variables, possibly defined in different files and so harder to see.

language design

Most helpful comment

Agree with all of the above and want to point out another dangerous example that I just came across:

Libraries like OpenZeppelin implement common tasks in generalised code with a long inheritance graph. That is all well until a developer overwrites a state variable of the base contract that is within the same contract still being used (but should refer to the base's version of it). E.g. the base Crowdsale contract has a token that a user might accidentally silently overwrite and assign another token in some other function.

The issue was luckily not "out there" yet but shows how dangerous this behaviour is.

All 20 comments

We had a short discussion with @federicobond yesterday about the possibility of introducing an override keyword (a la C++) when overriding an inherited variable and issuing an error otherwise.

C++ override applies to virtual _functions_, though. What would be the semantics here?

The override keyword lets you be explicit about your intention to override a variable higher up the inheritance graph. Take OpenZeppelin's SimpleToken for example:

contract SimpleToken is StandardToken {
聽 string public name = "SimpleToken";
 聽string public symbol = "SIM";
 聽uint256 public decimals = 18;
  // ...
}

If you were to change the initial value of any of these state variables, you would need to be explicit:

contract MyToken is SimpleToken {
  string override public name = "MyToken";
  string override public symbol = "MYT";
}

It can also help prevent typos such as these:

function trasnfer(address _to, uint256 _value) override {
  require(veryImportantSecurityCheck());
  super.transfer(_to, _value);
}

In the last case, since you are not overriding any function, the compiler will complain.

I'm not sure override can cleanly apply to state variables, because the only thing that is overridden is the assignment that is done as part of the constructor, but I guess it is intuitive enough.

@federicobond SimpleToken is an example of a concrete token, not meant to be inherited. It's confusing, we should move it to a different directory together with other examples.

If I understand well, the only use of override for state variables would be redefining their initial value. Since this can already be done in the constructor, the only real value it would add on this front (other than syntax) is redefining the initial value of _constant_ variables. I would say it's too much complexity for such small returns.

In any case I think we agree that this kind of shadowing should be an error by default.

Agree with all of the above and want to point out another dangerous example that I just came across:

Libraries like OpenZeppelin implement common tasks in generalised code with a long inheritance graph. That is all well until a developer overwrites a state variable of the base contract that is within the same contract still being used (but should refer to the base's version of it). E.g. the base Crowdsale contract has a token that a user might accidentally silently overwrite and assign another token in some other function.

The issue was luckily not "out there" yet but shows how dangerous this behaviour is.

@axic @chriseth Leaving discussion of override aside, do we agree that shadowing of inherited state variables should at least produce a warning?

Yes, I agree, and we should probably apply the same logic to functions unless they have an override modifier.

I'm not sure override can cleanly apply to state variables, because the only thing that is overridden is the assignment that is done as part of the constructor, but I guess it is intuitive enough.

For state variables currently we do not produce any warning even if the types differ during shadowing. I'd suggest that unless it is marked private in the parent:

  • a) show a warning if it has the same type, unless the override keyword is present
  • b) throw an error if the types differ

For b) we can only introduce a warning now, but should turn into an error in 0.5.0.

From BLWS'17:

  • Raise a warning now (an error in the breaking release) for shadowing storage variables
  • Require qualification in terms of conflicting variable names from two parent contracts (e.g. refer to them as Parent1.variable and Parent2.variable)
  • Require the override keyword for functions

Also cover #2116.

Re "Functions defined in Base that use the x state variable will access Base.x, and those defined in Derived will silently access the different slot Derived.x. This is a common problem for beginners that don't understand inheritance very well" : different languages handle this differently, so this is about understanding Solidity inheritance, not inheritance in general. Most languages do allow different forms of overriding, some even by default.

Personally I would very much welcome the "override" keyword, recently had a concrete need to change a few constants and "override" would have made the process simpler and more elegant.

Is there any way to override variable now?

contract Tokensale {
    uint hardcap = 10000 ether;

    function Tokensale() {}

    function fetchCap() public constant returns(uint) {
        return hardcap;
    }

}

contract Presale is Tokensale {
    uint hardcap = 1000 ether;

    function Presale() Tokensale() {}
}

To my disappointment, fetchCap returns 10000 ether. How to deal with this when I have a lot of common code in parent contract?

@7flash - add the fetchCap() function into the Presale contract, which should override the parent's fetchCap

contract Presale is Tokensale {

   ...

    function fetchCap() public constant returns(uint) {
        return hardcap;
    }
   ...
}

This is a pretty boring limitation of Solidity - in your case not a big deal as the function affected is short. Much more annoying if dealing with a long/complex function.

@sessai Exactly, that's the problem. Tokensale contract has complex functions related to hardcap. Should I duplicate these functions in presale? There is any better solution?

Here is valid solution, validPayment returns false as expected.

pragma solidity ^0.4.11;

contract Tokensale {
    function Tokensale(){
    }

    function validPayment() public constant returns(bool) {
        bool confirmed = true; // some complex calculations
        uint weiRaised = 1001 ether;
        return confirmed && weiRaised <= getHardcap();
    }

    function getHardcap() public constant returns(uint) {
        return 10000 ether;
    }
}

contract Presale is Tokensale {
    function Presale() Tokensale() {}

    function getHardcap() public constant returns(uint) {
        return 1000 ether;
    }
}

But there is a way to override generated getter function to write something like this?

// doesn't compile
pragma solidity ^0.4.11;

contract Tokensale {
    uint hardcap = 10000 ether;

    function Tokensale(){
    }

    function validPayment() public constant returns(bool) {
        bool confirmed = true; // some complex calculations
        uint weiRaised = 1001 ether;
        return confirmed && weiRaised <= hardcap;
    }
}

contract Presale is Tokensale {
    function Presale() Tokensale() {}

    function hardcap() public constant returns(uint) {
        return 1000 ether;
    }
}

@7flash - as far as I know you can't override a variable with a function.

The only solution I can imagine is to foresee this in the parent contract already, and instead of declaring variables write functions. So instead of:

contract Tokensale {
    uint hardcap = 10000 ether;
 }

write:

contract Tokensale {
    function hardcap() public constant returns(uint) { return 10000 ether; }
 }

And then override the function in the child contract.

Yes this is VERY BORING! If anyone here has another solution, please let us know.

The safer option is to assign TokenSale.hardcap in Presale constructor.

contract Presale is Tokensale {
    function Presale() Tokensale() {
        hardcap = 1000;
    }
}
Was this page helpful?
0 / 5 - 0 ratings