Solidity functions and state variables can be defined with visibility.
According to the docs:
public
: visible externally and internally (creates a getter function for storage/state variables)private
: only visible in the current contractexternal
: only visible externally (only for functions) - i.e. can only be message-called (via this.func)internal
: only visible internallyIf those visibility patterns are summarised in the table, it looks like as follows:
callable? | private | internal | external | public |
---|---|---|---|---|
inside the same contract | ✅ | ✅ | 🚫 | ✅ |
from inherited contracts | 🚫 | ✅ | 🚫 | ✅ |
outside the contract | 🚫 | 🚫 | ✅ | ✅ |
Let's check this by example. In this example, Truffle Suite is used to test locally.
If you'd like to know how to test your smarts contract locally, please check out this blog.
Here is a contract called Visibility
with four different types of visibility.
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.7.0 <0.9.0;
contract Visibility {
function privateFn() private pure returns (string memory) {
return "private";
}
function internalFn() internal pure returns (string memory) {
return "internal";
}
function externalFn() external pure returns (string memory) {
return "external";
}
function publicFn() public pure returns (string memory) {
return "public";
}
}
Once you deploy this to the contract, both public
and external
functions are callable.
truffle(development)> let vi = await Visibility.deployed()
undefined
truffle(development)> vi.publicFn()
'public'
truffle(development)> vi.externalFn()
However, both private
and internal
functions cannot be called with TypeError
error messages.
truffle(development)> vi.internalFn()
evalmachine.<anonymous>:0
vi.internalFn()
^
Uncaught TypeError: vi.internalFn is not a function
(...8<...)
truffle(development)> vi.privateFn()
evalmachine.<anonymous>:0
vi.privateFn()
^
Uncaught TypeError: vi.privateFn is not a function
(...8<...)
Next, let's define another contract that inherits from Visibility
contract.
contract VisibilityChild is Visibility {
function canCallInternalFn() public pure returns (string memory) {
return internalFn();
}
}
Now, internalFn()
can be called.
truffle(development)> let child = await VisibilityChild.deployed()
undefined
truffle(development)> child.canCallInternalFn()
'internal'
How about external
and private
visibility? Actually, compilers are smart enough to warn you that they cannot be defined in the VisibilityChild
contract.
Here is a invalid contract.
contract VisibilityChild is Visibility {
function canCallInternalFn() public pure returns (string memory) {
return internalFn();
}
//XXX: compilation fails
function tryPrivateFn() public pure returns (string memory) {
return privateFn();
}
//XXX: compilation fails
function tryExternalFn() public pure returns (string memory) {
return externalFn();
}
}
If you try compiling this contract, it'll throw CompileError: DeclarationError: Undeclared identifier.
error messages.
$ truffle compile
Compiling your contracts...
===========================
> Compiling ./contracts/visibility.sol
CompileError: DeclarationError: Undeclared identifier.
--> project:/contracts/visibility.sol:28:16:
|
28 | return privateFn();
| ^^^^^^^^^
,DeclarationError: Undeclared identifier. Did you mean "internalFn"?
--> project:/contracts/visibility.sol:32:16:
|
32 | return externalFn();
| ^^^^^^^^^^
Compilation failed. See above.
Visibility in Solidity gives developers flexibility to design well-modulised smart contracts and avoid unintentional accesss to private variables.