Function Modifiers in Solidity for those in a hurry

Function Modifiers in Solidity for those in a hurry

Function Modifiers allow you to change the behavior of functions in your smart contracts but there is more... Let's dig deeper

Function Modifiers

Function Modifiers is one of the many superpowers solidity provides to write clean and secured smart contracts. With a few lines of code and the ease of modifiers you could write checks, protect certain functions from bad actors and reuse fragment of code in multiple places.

In this article, you will explore the beauty of Function Modifiers and learn how to write and use them.

I have outlined the sections contained in this article, and should in case you want to skip to a specific one, be my guest.

What really are Function Modifiers?

The humans authoring the solidity docs have done a great job at simplifying what Modifiers are. So let's start there. They wrote that:

Modifiers can be used to change the behavior of functions in a declarative way.

So two things. One, Modifiers change the behavior of functions and Two, they do this declaratively. One might be obvious from the word "Modifier". Modifiers takes any function it is applied on and alters it's behavior.

The best part is you can determine what this change is and how it should occur. And this can all be done outside that actual function. In fact, that's where number two comes in, Modifiers alter functions in a declarative way.

Declarative means you declare the kind of modification you want. The change is not random. You decide how the modification occurs. Let's apply this idea to an analogy, one that would serve you through out the course of this article.

For instance, Imagine you own an actual apple store, grocery store not iPhones. It makes sense that only you should be able to restock and order for more apples when you are out of apples. Let's write a simple contract for your one-in-town grocery store.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

contract AppleGroceryStore{
    address manager; // Holds your address as the manager
    uint apples = 2;

    // initialize the manager with your address
    // when you deploy the contract
    constructor(){
        manager = msg.sender
    }

    function restock() public {
        apples++;
    }
}

That's the contract for your grocery store. We have two state variables, manager and apples; the contract constructor and the restock function.

Right now, we've got just two apples left so this would be a good time to restock. You then call the restock() function which does like its says. It would increase apples by one every time it's executed and you get more apples.

Everything looks great until you notice that just about anybody can call the restock function since it is a public function. We need to change the behavior of this function and one way to do this is to use FUNCTION MODIFIERS.

Let's look at the anatomy of a function modifier and then write one to secure your store's restock function.

Anatomy of a Function Modifier

To define a modifier, you need to use the Modifier keyword. Then give the modifier a name and then define the Modifiers body. And you have to add the " _ ; " symbol at the end of your modifiers body. Very important

// modifier with parameters
modifier identifier(params){
    // modifier code goes here
    _;
}

// you can leave out the parenthesis if
// your modifier has no parameter
modifier identifier{
    // modifier code goes here
        _;
}

That's the general structure of a modifier. The only difference between both is that one has parameters and can take arguments and the other doesn't. You can leave out the parenthesis ''( )'' if no arguments will be passed. Let's write a modifier for your grocery store.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

contract AppleGroceryStore{
    address manager;
    uint apples = 2;

    constructor(){
        manager = msg.sender
    }

    // this modifier checks who's calling
    // the function before executing said
    // function
    modifier onlyManager {
        require(msg.sender == manager);
        _;
    }

    // Applied the onlyManger modifer while
    // initialising the restock function
    function restock() onlyManager public {
        apples++;
    }
}

You now have a modifier that you can use to protect the restocking operations of your business. This modifier requires that the msg.sender equals the manager before the function runs.

Applying the onlyOwner Modifier on a function is as straightforward as calling the Modifier before declaring the body of the function. See how we did that for the restock function.

    function restock() onlyManager public {
        apples++;
    }
}

If onlyManager were to be a Modifier that receives arguments, you just need to add parenthesis after the modifier name and supply the arguments. We would see an example further along this article. So what about this _; symbol that looks like a typo that got away?

The "_ ;" is serving a real special purpose. The next section is all about that purpose.

The _; symbol

It's called the Merge wildcard. A special ingredient in how the modifier does it's thing. we already established what the modifier does to functions, now let's examine how and why this symbol is so important.

From the example above, you used the onlyManger modifier on the restock() function so only the manager can call the function. So whenever you call restock() the modifier does something cool. It takes the function body, in this case apples++ and replaces the "_ ;" character with it. So it would eventually look something like this.

function restock() public {
    require(msg.sender == manager); // from onlyManger modifier
    apples++; // where "_" used to be
}

Awesome, eh? I don't know about you but this looks like a modified function to me. This looks simple and you might say "Yo, koha! why didn't we just add that line above our functions ourselves?".

One, this is one tiny smart contract. Imagine a huge codebase where you have tons of functions that you need to restrict access to and only be callable by the manager, you could easily just use this one modifier defined here for everyone of them. All without repeating yourself.

Two, modifiers offers so much more and you could do really really crazy things. One of which you would see in the next section.

How many modifiers can I use on a Function?

Two modifiers. Three modifiers. I say, as many as you want. You can add as many modifiers as you need on a function. Just that you should add them in the order you want them to execute.

Let's take a look into that. And the timing is perfect! You have decided to automate how you check your inventory before restocking. Now you only want the restocking ability to be granted when you have a certain amount of apple left in the store and not before. Let's write a modifier to set a minimum amount of apples you've got to have left before restocking is allowed to happen.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

contract AppleGroceryStore{
    address manager;
    uint apples = 2;

    constructor(){
        manager = msg.sender
    }

    modifier onlyManager {
        require(msg.sender == manager);
        _;
    }

    // this modifier accepts an argument
    modifier whenApplesLessThan(uint _amount){
        require(apples < _amount, "You still got apples, mate!");
        _;
    }

    // Applied the onlyManger and whenApplesLessThan 
    // modifer while initialising the function
    function restock() onlyManager whenApplesLessThan(10) public {
        apples++;
    }
}

Modifiers is starting to look cool. Almost anybody who looks at the restock function can interpret what the function requires to be executed.

In plain words, "Restock can only be called by the manager and only when the apples in stock go below 10 otherwise restocking fails". The order which modifiers are arranged is almost as critical as the modifiers themselves.

Then restock function above would eventually resolve into a function that looks something like this.

function restock() public {
    require(msg.sender == manager);
    require(apples < 10, "You still got apples, mate!");
    apples++;
}

Therefore, take heed. You should declare modifiers in the Order you want the function modification to happen. Another thing is you can use multiple merge wildcards in a modifier and every single one of them would be replaced with the function body.

modifier multipleMerge(){
      _; 
      _;
}

This is a valid Modifier code. Every single occurrence of ''_ ;'' will be replaced by the body you modify with multipleMerge. The only effect is that for every time the function body is called, the same parameters are passed through and the eventual effect is, the return values reset to their default condition for every execution.

Are Modifiers like DNA?

If by DNA, you mean can Modifiers be inherited and used in other Contracts? Then absolutely.

Modifiers can be defined in a base contract and applied on functions written in other contracts that inherits the base contract.

Suppose you want to expand to Columbia with your store operations. This time to sell kiwi. Your kiwi store would also have a restock function, and just like in your apple grocery store, you want the ability to call the restock function limited to only you, the manager. Let's look at how you could implement that by inheriting the Apple grocery store contract.

// Use `is` to derive from another contract. Derived 
// contracts can access all non-private members including 
// internal functions and state variables. These cannot be 
// accessed externally via `this`, though.
contract KiwiGroceryStore is AppleGroceryStore{
    uint kiwi = 4;

    // onlyManager is derived from
    // the AppleGroceryStore contract
    // therefore you can use it here
    function restock() onlyManager public {
        kiwi++;
    }
}

That's how easy it is to inherit a function modifier in other contracts. There are even more subtle ways you can improve on this. The Solidity docs has a bangin' content on Modifiers inheritance.

Thank you for reading this post. I'd love your thoughts on Modifiers and would want to know how you are using Modifiers in your code. Comment below! For more content like this, follow GitHuband me on DEV!

Summary

  • Modifiers change how a function should execute. You can decide how this change happens plays out using modifiers
  • Modifiers are often used to perform checks before invoking a functions.
  • Modifiers can be inherited from other contracts.
  • Inherited Modifiers can be overridden provided they are marked as virtual in the base contract.
  • Multiple Modifiers can be applied to a function.
  • Multiple Modifiers applied to a function would affect the functions in the order they are specified.
  • For Modifiers that accept arguments, you can pass in arguments of the functions to them at the point of invocation.
  • All _; symbol in a modifier is replaced by the function body.
  • The _; symbol can appear in a modifier multiple times.

Learn More