0

I'm really struggling to understand variable visibility. I'm aware that everything is public on a blockchain. Yes you can make a variable private to make it invisible to other contracts, but so what? What harm does allowing other contracts to view it, as long as they cannot change it?

Can I just set every variable in my contract public without putting too much thought into it? I can't really see why not...

| improve this question | | | | |
2

There are further security considerations.

There are a few possibilities related to inheritance. The compiler is getting better at flagging unintended shadowing of variables used elsewhere but it won't catch everything.

One example is code hygiene. A child contract that inherits the subject contract will be able to inspect and overwrite a public variable, bypassing functions made for that purpose, and possibly violating the intended design.

Consider:

pragma solidity 0.5.16;

contract Ownable {

    address public owner;

    modifier onlyOwner  {
        require(msg.sender == owner);
        _;
    }

    constructor () internal {
        owner = msg.sender;
    }
}

That is an admittedly simple implementation of the classic pattern. Consider a simple example of a mistake:

contract Child is Ownable {

    function changeOwner() public {
        owner = msg.sender;
    }
}

The problem there is the child contract is permitted to write to it, but it doesn't know what it's doing and it created a gaping security hole. Anyone can come along and seize control, so what's the point of having onlyOwner guards at all? Probably, all the money is gone. Yikes!

Now, if the author of Ownable.sol wants to be conscientious, they can consider this instead:

pragma solidity 0.5.16;

contract Ownable {

    address private _owner;

    modifier onlyOwner  {
        require(msg.sender == _owner);
        _;
    }

    constructor () internal {
        _owner = msg.sender;
    }

    function changeOwner(address newOwner) public onlyOwner {
        _owner = newOwner;
    }

    function owner() public view returns(address) {
        return _owner;
    }
}

That makes it very clear that the only way _owner can change is via the function built for that, and it is guarded with a modifier. The first way implies the need to pour over the whole system to make sure no one is stomping on owner in a way that they shouldn't while the second way implies that they couldn't possibly do that. A long-hand version of the "free" getter that was lost is added for good form, so everyone should be happy.

Some variables just aren't meant to be inspected. They're around to assist with a calculation but using them might actually be a mistake and exposing them might cause confusion. For example, if it is necessary to call a function to get accurate information because some computations need to happen.

mapping(address => uint) private processedBalances;
mapping(address => uint) private unprocessedDeposits;
mapping(address => uint) private unprocessedWithdrawals;

function balanceOf(address user) public view returns(uint) {
   return processedBalances[user] + unprocessedDeposits[user] - unprocessedWithdrawals[user];
}

It's a contrived function to compute something on-the-fly. That sort of thing is useful for time-series adjustments like interest or dividends that would be too involved for a simple example.

It might be useful to expose all four for inspection for dev/test purposes, but a case could also be made that it would be confusing to devs and users to expose four functions that sort of sound similar when one of them is the way it is meant to be used, i.e. your intended surface area/ABI.

In any case, remember that the "free" getter is not the only effect of public. It's almost always a good idea to go with private for something important when the contract is meant to be inherited. internal when you intend for child contracts to overwrite as they see fit. If there is any danger, any reason to guard it, then private and a function they can use (possibly internal) so you can be sure the child contracts follow the rules.

Hope it helps.

| improve this answer | | | | |
  • This helps a lot, and is a fantastically comprehensive answer, thank you so much. This is a very noob question- but you cannot 'inherit' an already deployed contract, can you? If you could, this would imply that all public functions on existing contracts can be modified so I assume it is not possible!! – Andrew Stanger Jan 29 at 6:53
  • Generally, no. I know a way to do it with assembler and delegate call but in that case, the proxy (caller) holds their own state and just wants the bytecode, so they can't hurt your data. And, I would say if they want to do that then they should know exactly what they're doing and accept all risk. – Rob Hitchens Jan 29 at 8:54
1

You are correct that even private variables are still publicly visible due to the public nature of blockchain. So marking a variable as private will not hide it from anybody.

However, making a variable public will implicitly make a public contract method for reading that variable, which will increase both the gas cost to deploy that contract, and the storage size of the contract.

I don't know the precise calculations, but I think it's roughly 10,000 extra gas per public variable. So you're essentially paying for every variable you make public.

| improve this answer | | | | |
  • Yeah that makes sense about extra gas. But that is only paid once, it does not impact the users of the contract :) – Andrew Stanger Jan 29 at 6:46

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.