Solidity Basics: Function

Solidity Basics: Function

Table of Content

  • Introduction

  • Components of a Solidity Function

    • FunctionName

    • Parameters

    • Visibility keywords

      • public

      • external

      • internal

      • private

    • State mutability keywords

      • View

      • Pure

      • Payable

  • Fallback Function

  • Function Modifiers

  • Summary

Introduction

A function is simply a group of code that can be reused anywhere in a program. By creating a function, we reduce the need to write the same code repeatedly, which saves the excessive use of memory and decreases the runtime of a program.

In Solidity, functions are first-class citizens, we use them to carry out various operations in a smart contract. Within a contract, we can define functions that can be called by an Externally-Owned-Account(EOA) transaction or another contract.

The syntax used to declare a function in Solidity is as follows:

function FunctionName([parameters]) {public|private|internal|external} [pure|constant|view|payable] [modifiers] [returns (return types)]

We will have a detailed look at the various components seen in the function syntax, For now, let's look at a simple function example:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract MyContract {
    uint value

    // A function to read value
    function getValue() external view returns(uint) {
        return value;
    }

    // A function to modify value
    function setValue(uint new_value) external {
        value = new_value
    }
}

In the preceding code, we declare simple getter(a function that gets the value of a specific property) and setter(a function that sets the value of a property) functions. Now let's look at the components of a function in more detail.

Components of a Solidity Function

FunctionName

This is the name of the function(usually seen after the function keyword) used to call the function in a transaction(from an EOA) or from another contract. In our example, we have two functions named getValue and setValue respectively.

Parameters

After the name, we define the arguments that we want the function to accept, with their names and type. In our example, the setValue function accepts a new_value argument of the uint type.

Visibility keywords

Function visibility keywords allow you to define how a function can be accessed. Note, that any function or data inside a contract is always visible on the public blockchain, meaning that anyone can see the code or data. The keywords described here only affect how and when a function can be called.

public The visibility of all functions is set to public by default. This means the function can be called from within its contract, an EOA transaction, or another contract.

Example:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract MyContract {
    uint value

    // visibility set to public
    function getValue() public view returns(uint) {
        return value;
    }

    // A function to modify value
    function setValue(uint new_value) external {
        value = new_value
    }
}

external A function that uses the external keyword can only be called outside its contract. It cannot be called from within its contract unless explicitly prefixed with the this keyword.

Example:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract MyContract {
    uint value

    // visibility set to external
    function getValue() external view returns(uint) {
        return value;
    }

    // A function to modify value
    function setValue(uint new_value) external {
        value = new_value
        // external visibility bypassed

    }

    // calls getValue()
    function callGetValue() public view returns (uint) {
        // External visibility? Who cares
        return this.getValue();
    }
}

internal A function that uses the internal keyword is only accessible within its contract—It cannot be called by another contract or EOA transaction. However, they can be called by child contracts(those that inherit the contract the function is declared in).

Example:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

// Parent.sol

contract Parent {
    uint data;
    function foo() internal {}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

// Child.sol

import "./Parent.sol";

contract Child is Parent {

    function bar() external {
        // call function in parent
        foo();
        // manipulate parent data
        data++;
    }
}

private Private functions are similar to internal functions but cannot be called by child contracts.

State mutability keywords

These are keywords that affect the behavior of a function.

view A function marked as view does not modify any value, it can read a value.

Example:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract MyContract {
    uint value

    // getValue is read-only because of view
    function getValue() external view returns(uint) {
        return value;
    }

    // A function to modify value
    function setValue(uint new_value) external {
        value = new_value
    }
}

pure A pure function does not read or modify any stored variables. It can only operate on arguments and return data, without reference to any stored data.

Example:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract Test {
   function getResult() public pure returns(uint product, uint sum){
      uint a = 1; 
      uint b = 2;
      product = a * b;
      sum = a + b; 
   }
}

payable A payable function is a function that can receive payment or ether into a contract.

Example:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract MoneyContract {

    function pay(address payable to, uint amount) external {
            //Send ether to the address stored in the "to" variable
            to.transfer(amount) // 100 wei
    }


    function receive() public payable {
            //Check the amount of ether received
            msg.value
            //Check ether balance in the smart contract
            address(this).balance
    }
}

In the preceding code, we can send ether receive function in the MoneyContract.

Fallback Function

A fallback function is a special function in Solidity with neither a name, parameters, or return values. It is executed when:

  • A function that does not exist in a contract is called.

  • When ether is sent to a contract without an explicitly declared function to receive it—the receive() can also be used to receive the ether in this case.

Characteristics of a fallback function:

  • It is an unnamed function.

  • It accepts no arguments, only the fallback() declaration is required.

  • It does not return any value.

  • It must be used with the external visibility keyword.

  • It is constrained to 2300 gas when it is called by a transfer() or send() method in another function—this enables the function call to be cheap.

  • It must use the payable keyword for it to receive ether sent to the contract.

  • A contract can only have one fallback function.

receive() vs fallback()

The receive() function is similar to the fallback() function. It cannot have arguments, or return values, and must have external visibility and payable state mutability. However, it has only one purpose—to receive ether. It is called when ether is sent to a contract with no calldata. If the receive() function does not exist, the fallback() function is used.

The preceding diagram shows the logic used in a contract that either or both receive() and fallback().

Example:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

//1. Sending ether in a smart contract
//2. Receiving ether
    //-via a name function call
    //-via a non-name function call 


contract MyContract {
        //1.
    function foo(address payable to, uint amount) external {
            // transfer ether from the address stored in "to"
            to.transfer(100) // 100 wei
    }

    //2. named-function call
    function bar() external payable {
            //Check the amount of ether received
            msg.value
            //Check ether balance in the smart contract
            address(this).balance
    }

    // native fallback function.
    fallback() external payable {
        // check the amount of ether received
            msg.value
            // check ether balance in the smart contract
            address(this).balance
    }

    // native receive function.
    receive() external payable {
        // check the amount of ether received
            msg.value
            // check ether balance in the smart contract
            address(this).balance
    }
}

Function Modifiers

A modifier is simply a function used to modify the behavior of another function(s). In solidity, a modifier is used to enhance and add functionality to function(s) without modifying the function(s) itself— It can be executed before or after the function executes its code.

A modifier is declared with the following syntax in solidity:


modifier ModifierName([parameters]) {
    // modifier definition
    ...some condition
    // merge wildcard(executes function be modified)
    _; 
}

The wild card(_;) symbol indicates the end of the modifier and the beginning of the function(which replaces the modifier at runtime) that the modifier is modifying.

  • The wildcard symbol is mandatory for all modifiers.

  • The wildcard can be used anywhere in a modifier.

  • If the wildcard is placed at the end of a modifier, the condition is verified before the "modified" function is executed. However, if it is placed at the beginning the function is executed before the condition is verified.

Using a Modifier

Example: A function without a modifier.

contract FunctionModifier {
    bool public paused;
    uint public count;

    function setPause(bool _paused) external {
        paused = _paused;
    }

    // increased when not paused
    function inc() external {
        require(!paused, "paused");
        count += 1;
    }

    // decrease when not paused
    function dec() external {
        require(!paused, "paused");
        count -= 1;
    }
}

In the preceding code, the count variable is increased and decreased by inc and dec functions respectively. Now let's modify these functions using a function modifier.

contract FunctionModifier {
    bool public paused;
    uint public count;

    function setPause(bool _paused) external {
        paused = _paused;
    }

    // pause functionality
    modifier whenNotPaused() {
        require(!paused, "paused");
        _;
    }


    function inc() external whenNotPaused {
        count += 1;
    }

    function dec() external whenNotPaused {
        count -= 1;
    }
}

In the preceding code, the whenNotPaused function modifier is used to modify the inc and dec functions by adding the pause functionality.

Chaining Modifiers and Passing Arguments

Let's see how multiple modifiers can be used in a function, and how argument(s) can be passed to a modifier.

Example:

contract FunctionModifier {
    bool public paused;
    uint public count;

    function setPause(bool _paused) external {
        paused = _paused;
    }

    // pause functionality
    modifier whenNotPaused() {
        require(!paused, "paused");
        _;
    }

    // modifier accepts argument
    modifier cap(uint _x) {
        require(_x < 100, "x >= 100");
        _;
    }

    // using multiple modifiers & argument
    function incBy(uint _x) external whenNotPaused cap(_x) {

        count += x;
    }
}

In the preceding code, the incBy by function uses both the whenNotPaused and cap modifiers. The cap modifier accepts an argument, which is provided by the function it modifies.

Summary

In this article, you learned the following about functions in solidity:

  • The declaration syntax

  • How to control access to a function with visibility keywords.

  • How to modify function behavior with state-mutability keywords.

  • How to use the fallback function.

  • How to use function modifiers to extend the capability of a function.