Four types of Solidity functions indicate the execution context. The function behavior can be changed by adding modifiers. We’ll discuss these concepts in more detail. We also explain the purpose and use of getter and fallback functions.
This tutorial reviews functions that are set as view or pure. We define the function overloading and its resolution, together with argument matching. The use of for loop is mentioned to make sure you know the risks it brings.
Contents
Solidity Functions: Main Tips
- There are four types of Solidity functions: external, internal, public, and private.
- Modifiers change the way functions work.
- Functions can be set as
view
andpure
to restrict reading and modifying of the state. - Function overloading occurs when several functions in a contract have the same name but differing arguments.
Overview of Functions
Internal, external, public and private Solidity functions modify variables and smart contracts.
pragma solidity >=0.4.0 <0.7.0;
contract SimpleAuction {
function bid() public payable { // Function
// ...
}
}
- Internal functions are invoked inside the current contract and cannot be completed in different contexts.
- External functions are invoked from the outside and through transactions as well. It is not possible to call
f
internally. When dealing with big arrays of information, these functions can be highly effective. - Private functions are visible only inside the contract they are called in.
- The public functions are called from any place.
You can add these modifiers between the list
and return
parameters:
pragma solidity >=0.4.16 <0.7.0;
contract C {
function f(uint a) private pure returns (uint b) { return a + 1; }
function setData(uint a) internal { data = a; }
uint public data;
}
Modifiers
To change the way functions work, you can use Solidity modifiers. By using them, you can set a condition to check before the function execution.
Note: contracts inherit modifiers, but related contracts can suspend these properties.
In the example below, the contract does not use the modifier, only defines it. The derived contracts will use the modifier. The function body is inserted in the place the special symbol _;
is in the modifier definition. When the owner calls this function, it executes. Otherwise, it throws an exception.
pragma solidity >=0.5.0 <0.7.0;
contract owned {
constructor() public { owner = msg.sender; }
address payable owner;
modifier onlyOwner {
require(
msg.sender == owner,
"Only owner can call this function."
);
_;
}
}
The following contract inherits the onlyOwner
modifier from owned
and uses it on close
function. After that, calls to close
will be effective if they are initiated by the stored owner:
contract mortal is owned {
function close() public onlyOwner {
selfdestruct(owner);
}
}
Arguments can be set for modifiers:
contract priced {
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
}
Remember to set payable
keyword to make sure that functions do not reject Ether:
contract Register is priced, owned {
mapping (address => bool) registeredAddresses;
uint price;
constructor(uint initialPrice) public { price = initialPrice; }
{
registeredAddresses[msg.sender] = true;
}
function changePrice(uint _price) public onlyOwner {
price = _price;
}
}
Remember: you can apply several modifiers to functions by placing them in a whitespace-separated list. They are treated in the order you wrote them in the list.
Getter Methods
Solidity generates a getter function for every defined public state variable. Such a function has external visibility. In the example below, the compiler creates a function data
without any arguments. It returns the value of the state variable data
:
pragma solidity >=0.4.0 <0.7.0;
contract C {
uint public data = 42;
}
contract Caller {
C c = new C();
function f() public view returns (uint) {
return c.data();
}
}
Note: Solidity sets getter functions as view by default.
view
Functions set to view
do not change the state. These actions indicate a modification of the state:
- Creating other contracts
- Making Solidity transfer Ether through calls
- Writing to state variables
- Removing events
pragma solidity >=0.5.0 <0.7.0;
contract C {
function f(uint a, uint b) public view returns (uint) {
return a * (b + 42) + now;
}
}
Remember: before the 0.5.0 Solidity version, the compiler did not use STATICCALL opcode for such functions. This opcode makes sure that view functions won’t modify state on the level of the EVM.
pure
Functions set to pure
should not modify or read from the state. It prevents pure
functions from:
- Calling functions that are not marked.
- Using inline assembly, containing specific opcodes.
- Reading from state variables.
- Accessing
address(this).balance
or<address>.balance
. - Accessing members of
block
,tx
,msg
(exceptmsg.sig
andmsg.data
).
pragma solidity >=0.5.0 <0.7.0;
contract C {
function f(uint a, uint b) public pure returns (uint) {
return a * (b + 42);
}
}
Remember: the STATICCALL prevents pure functions from modifying the state, but can’t prevent them from reading it.
Note: Solidity allows pure functions to revert possible state modifications due to errors. They can use revert() and require().
Fallback
Solidity fallback function does not have any arguments, has external visibility and does not return anything.
Note: only one unnamed function can be assigned to a contract.
The function in the example below is invoked for all messages sent to this contract. Sending Ether to this contract triggers an exception since the fallback function is not set as payable
:
contract Test {
function() external { x = 1; }
uint x;
}
The contract Sink
does not allow to retrieve Ether from it:
contract Sink {
function() external payable { }
}
Solidity fallback function executes in such cases:
- If other functions do not equal the provided identifier, it works on a call to the contract.
- When a contract gets Ether without any other data, the function executes (if it is set as Solidity
payable
). - If there is enough gas, this function can execute like any other method.
Remember: the solidity fallback function can accept msg.data as an argument. Then, it returns any payload provided with the call.
Function Overloading
Function overloading occurs when one contract has several functions with the same name, but with distinct parameter types. It affects inherited functions as well.
The example below reveals the overloading of f
function in the scope of contract A:
pragma solidity >=0.4.16 <0.7.0;
contract A {
function f(uint _in) public pure returns (uint out) {
out = _in;
}
function f(uint _in, bool _really) public pure returns (uint out) {
if (_really)
out = _in;
}
}
The external interface also contains overloaded functions. When two functions set to external visibility have different Solidity types (but the same external types), an error occurs.
pragma solidity >=0.4.16 <0.7.0;
contract A {
function f(B _in) public pure returns (B out) {
out = _in;
}
function f(address _in) public pure returns (address out) {
out = _in;
}
}
contract B {
}
Note: in the example above, two overloads of the f function take the address type for the ABI (even though they are seen as different).
Resolving Overload and Matching Arguments
Solidity chooses overload functions by matching the function declarations in the current scope to the argument provided in the function call.
Remember: functions can become overloads if their arguments can be implicitly converted to the necessary types. If not a single function fits the criteria, resolution fails.
pragma solidity >=0.4.16 <0.7.0;
contract A {
function f(uint8 _in) public pure returns (uint8 out) {
out = _in;
}
function f(uint256 _in) public pure returns (uint256 out) {
out = _in;
}
}
Calling f(50)
generates a type error because 50 is implicitly convertible to uint8
and uint256
. However, f(256)
would resolve to f(uint256)
overload as 256
cannot be implicitly converted to uint8
.
Internals - The Optimizer
The Solidity optimizer works on an assembly to allow other languages to use it. It divides instructions into blocks at JUMPs
and JUMPDESTs
.
In them, optimizer investigates instructions and keeps track of all changes done to the stack
, storage
or memory
locations as an expression.
Note: optimizer exploits CommonSubexpressionEliminator component which detects expressions of the same value. Then, it adds them together into an expression class.
Solidity for Loop
The Solidity for
loop performs a function for a set condition. Its rules are the same as in JavaScript or C++. Also, it does not have a limit of iterations.
Warning: in some cases, Solidity for loop is unnecessary as it will only overuse gas and might even overstep the set block’s gas limit.
Solidity Functions: Summary
- Solidity functions can be public, private, external and internal.
- You can change the way function acts with Solidity modifiers.
view
functions cannot modify state, whilepure
functions should not read nor modify it.- Function overloading means multiple functions in the same contract share names but not parameters.