Special Halloween Offer

Save 20% on all courses by purchasing them now

Code has been added to clipboard!

Master Solidity Functions: Modifiers and Overloading Explained

Reading time 6 min
Published Jul 4, 2019
Updated Jul 25, 2019

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.

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 and pure 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.

Example
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:

Example
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.

Example
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:

Example
contract mortal is owned {
    function close() public onlyOwner {
        selfdestruct(owner);
    }
}

Arguments can be set for modifiers:

Example
contract priced {
    modifier costs(uint price) {
        if (msg.value >= price) {
            _;
        }
    }
}

Remember to set payable keyword to make sure that functions do not reject Ether:

Example
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:

Example
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
Example
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 (except msg.sig and msg.data).
Example
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:

Example
contract Test {
    function() external { x = 1; }
    uint x;
}

The contract Sink does not allow to retrieve Ether from it:

Example
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:

Example
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.

Example
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.

Example
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, while pure functions should not read nor modify it.
  • Function overloading means multiple functions in the same contract share names but not parameters.