Functions, Function Types, Function Visibility, and Function Modifiers in Solidity

Functions, Function Types, Function Visibility, and Function Modifiers in Solidity

Functions, Function Types, Function Visibility, and Function Modifiers in Solidity

Solidity Functions as I like to explain are a lot like functions/methods in JavaScript, with similarities extending to the structure of a function

function( params ) { args }

The difference between the structure of functions in Solidity and JavaScript is that Solidity being a statically typed programming language has extra requirements for defining the data types of params, return statements, and args, and also has to be indicated by what is called visibility levels.

Solidity Functions are the executable units of code. Functions are usually defined inside a contract, but they can also be defined outside of contracts. Functions outside of a contract, also called “free functions”, always have implicit internal visibility.

contract HelloWorld {
    function send(string _greeting) public returns(string memory) {
   // ...
   }
}
// Helper function defined outside of a contract
function helper(uint x) pure returns (uint) {
    return x * 2;
}

Function Visibility

In the above example, you can see that the function defined in the contract has what is called a visibility type. Visibility type is best understood given the explanation that Function calls in Solidity are categorized into 2:

  1. Functions that make state changes to the EVM. These are generally referred to as “External Functions

  2. Functions that do not make state changes to the EVM are also referred to as “Internal Functions

These 2 general categories of functions then give rise to 4 different function visibility types:

  • External

  • Public

  • Internal

  • Private

External:

External functions are part of the contract interface, which means they can be called from other contracts and via transactions. An external function f cannot be called internally (i.e. f() does not work, but this.f() works).

contract Apple { 
function externalFunc() external pure returns (string memory) {
        return "external function called";
    }
}
contract Bee {
  address contractApple = “0x….”;

  function callExternalFuncInApple() internal view returns(string memory) {

  const B = contractApple.externalFunc();
  return B; //this will return the string "external function called"
  }
}

The above example illustrates 2 different contracts, where one function with the visibility of external can be called in another separately deployed contract.

Public:

Public functions are part of the contract interface and can be either called internally or via message calls i.e. any contract that inherits the contract where the public function exists and address account can call these kinds of functions.

contract Apple {
    function publicFunc() public pure returns (string memory) {
        return "public function called";
    }
}

Internal:

Internal functions can only be accessed from within the current contract or contracts deriving from it i.e. inside contracts that inherit the contract holding the internal function. They cannot be accessed externally. Since they are not exposed to the outside through the contract’s ABI, they can take parameters of internal types like mappings or storage references.

Contract Apple {
    function internalFunc() internal pure returns (string memory) {
        return "internal function called";
    }
}

Private:

Private functions are like internal ones but they are not visible in derived contracts i.e Private functions can only be called within the instance of the deployed contract.

contract Bees {
    function privateFunc() private pure returns (string memory) {
        return "private function called";
    }

    function testPrivateFunc() public pure returns (string memory) {
        return privateFunc();
    }
}

In addition to learning about Function Visibility, it is essential to talk about the ability of function types to modify state. State mutability is a concept in Solidity that defines the behavior of functions and how they interact with data stored on the blockchain. The two major state mutability modifiers in solidity are:

  • View

  • Pure

View Functions:

Functions can be declared view in which case they promise not to modify the state.

Pure Functions:

Functions can be declared pure in which case they promise not to read from or modify the state. In particular, it should be possible to evaluate a pure function at compile-time given only its inputs

contract ViewAndPure {

    uint public x = 1;

    // will not modify the state.
    function addToX(uint y) public view returns (uint) {
        return x + y;
    }

    // will not modify or read from the state.
    function add(uint i, uint j) public pure returns (uint) {
        return i + j;
    }
}

Again, pure function can not read state variables and also cannot write to state variables whereas a View function can read state variable but can not modify it.

Where to use various visibility types?

The choice of the use of any of the 4 visibility types is up to the Developer, a handy guide is:

public - use for functions that any contract and address account can call.

private - use only for functions used inside the contract to define such functions

internal- use this for functions only inside the contract that inherits an internal function

external - use for functions that any deployed contract and address account can call.


Function Modifiers

Modifiers can be used to change the behaviour of Solidity functions in a declarative way. For example, you can use a modifier to automatically check a condition prior to executing the function i.e. Modifiers are code that can be run before and/or after a function call.

Modifiers are inheritable properties of contracts and may be overridden by derived contracts, but only if they are marked virtual.

Modifiers can be used to: Restrict access, Validate inputs and Guard against reentrancy hacks.

You can think of a Modifier as an argument passed against a function that could have also been a part of the function. Modifiers are very useful in scenarios where multiple functions have the same line of code passed to them.

contract Bees {

  address owner;

  constructor ( ) {
    owner = msg.sender
  }

  function setName(_greeting) public {
      require(msg.sender == owner)
      const newGreeting = _greeting
  } 

  function updateGreeting(_greeting) public {
      require(msg.sender == owner)
      const newGreeting = _greeting
  } 

}

In the above example, you can see the line require(msg.sender == owner) repeated, which implies that even if we have over 50 different functions that we require only the owner of the deployed contract to be the only person able to call it, we will have to repeat the same line of code over and over again. A way to keep this short is the use of function modifiers.

// Modifier to check that the caller is the owner of the contract.
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        // Underscore is a special character only used inside a    function modifier and it tells     
      //Solidity to execute the rest of the code.
        _;
    }

The functions can thus be rewritten as:

function setName(_greeting) public onlyOwner {
    const newGreeting = _greeting
} 

function updateGreeting(_greeting) public onlyOwner {
   const newGreeting = _greeting
}

Conclusion

That is all there is about Functions Visibility, Types and Modifiers. Congratulations if you read to the very end of this article. You can rely on the guide to understand what visibility types to set your functions and also how to write custom function modifiers. If you have any questions, you can message me on Twitter @emma_odia I look forward to hearing from you! Cheers!