When a state variable or function is declared without a visibility modifier, Solidity defaults to public for state variables and external for functions. However, this default behavior may not always be secure, especially when sensitive data or restricted operations are involved. Proper use of visibility modifiers ensures that your contract is both secure and efficient.

This article will explore the different visibility levels, demonstrate their usage with code examples, and highlight best practices for access control in Solidity.


Visibility Modifiers in Solidity

Solidity provides four visibility modifiers:

ModifierAccess Scope
publicAccessible from any external address and internal to the contract.
privateAccessible only within the contract that defines it.
internalAccessible within the contract and derived contracts.
externalAccessible only from outside the contract, not from internal calls.

Let’s look at an example to illustrate the use of these modifiers.

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

contract VisibilityExample {
    uint public publicVar = 100;
    uint private privateVar = 200;
    uint internal internalVar = 300;

    function getPrivateVar() public view returns (uint) {
        return privateVar;
    }

    function getInternalVar() internal view returns (uint) {
        return internalVar;
    }

    function getInternalViaPublic() public view returns (uint) {
        return getInternalVar();
    }

    function setPublicVar(uint newValue) external {
        publicVar = newValue;
    }
}

In this example:

  • publicVar is publicly readable and can be modified by external contracts.
  • privateVar is accessible only within VisibilityExample.
  • internalVar is accessible within VisibilityExample and any contract that inherits from it.
  • The getInternalVar function is internal, so it cannot be called from outside the contract.
  • The getInternalViaPublic function is public and can be used to access the internalVar value externally.

Best Practices for Access Control

Access control in Solidity is often implemented using modifiers to restrict function access. A common pattern is to use require with a condition and a custom error message.

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

contract AccessControl {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Not the owner");
        _;
    }

    uint public restrictedData = 42;

    function changeData(uint newData) public onlyOwner {
        restrictedData = newData;
    }
}

In the AccessControl contract:

  • The onlyOwner modifier ensures that only the contract owner can call the changeData function.
  • The msg.sender is compared to the stored owner address.
  • If the sender is not the owner, the transaction is reverted with the message "Not the owner".

This pattern is widely used in real-world applications to enforce role-based access control.


External vs Internal Functions

Understanding the difference between external and internal functions is key to optimizing gas usage and preventing vulnerabilities.

  • External functions are called using the ABI and cannot be called directly from within the contract.
  • Internal functions are more efficient and are used for internal logic.

Here's an example demonstrating the gas efficiency of internal functions:

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

contract FunctionVisibility {
    uint public counter;

    function incrementExternal() external {
        counter += 1;
        logCounter();
    }

    function incrementInternal() external {
        _increment();
    }

    function _increment() internal {
        counter += 1;
        logCounter();
    }

    function logCounter() internal view {
        // Some internal logic
    }
}

In this contract, calling _increment directly from another internal function is more gas-efficient than using an external function call. Always prefer internal functions for internal logic.


Summary of Visibility and Control

ModifierCan be accessed externally?Can be accessed internally?Can be inherited?
public
private
internal
external

Use the table above to quickly reference the correct visibility modifier based on your access requirements.


Learn more with useful resources