Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Function Modifiers: Reusable Validation & Access Control

Hello! Welcome back to our course on building blockchain applications.

In our last lesson, we learned how to use Solidity events to log important contract activities, creating a bridge for off-chain applications to listen to on-chain state changes. We now know how to report on what's happening in our contract. Today, we'll focus on controlling who is allowed to make those changes in the first place.

Your goal for this lesson is to learn how to create and apply function modifiers for reusable validation and access control.

As a backend developer, you might be familiar with concepts like decorators in Python or annotations in Java that declaratively add functionality to methods. Solidity's function modifiers serve a very similar purpose. They allow us to write clean, reusable code for checks that must run before a function executes, which is crucial for building secure and maintainable smart contracts.

The Problem: Repetitive and Error-Prone Checks

Imagine a simple contract that holds funds. It might have a function to change a withdrawal fee or a function to withdraw the contract's entire balance. Naturally, you'd only want the contract's owner to be able to call these sensitive functions.

The most straightforward way to implement this is to add a require statement at the beginning of each protected function:

address public owner;

// In the constructor:
// owner = msg.sender;

function changeFee(uint newFee) public {
    require(msg.sender == owner, "Only owner can change the fee.");
    // ... logic to change the fee
}

function withdrawBalance() public {
    require(msg.sender == owner, "Only owner can withdraw.");
    // ... logic to withdraw balance
}

This works, but it violates a core principle of good software engineering: Don't Repeat Yourself (DRY). This repetition is not just inefficient; it's a source of potential bugs. What if you add a third administrative function and forget to copy-paste the require statement? You've just created a security hole.

This video from Block Explorer demonstrates this exact problem with a simple real-estate contract example.

Access Control | Smart Contract Security Tutorial Part 1

Watch this first segment to see how easily a contract can become insecure if its functions are not properly protected.

Watch from 00:54 to 06:36. The video first shows a vulnerable contract where anyone can change the price (the problem). It then introduces the require statement as the initial fix, highlighting the need for access control.

The Solution: Reusable Logic with modifier

Solidity provides an elegant solution to this problem: function modifiers. A modifier is a piece of code that can be attached to a function to change its behavior. Its primary use case is to run checks before the function body is executed.

Defining a Modifier

You define a modifier using the modifier keyword. Inside the modifier, you place your validation logic (like a require statement). The special placeholder _; is used to indicate where the body of the function being modified should be executed.

Here is how we can encapsulate our ownership check into a modifier called onlyOwner:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract AccessControlExample {
    address public owner;

    // The modifier definition
    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can call this function.");
        _; // This is where the function's code will run
    }

    constructor() {
        owner = msg.sender;
    }

    // Applying the modifier
    function changeOwner(address _newOwner) public onlyOwner {
        owner = _newOwner;
    }
}

The _; symbol is crucial. When changeOwner is called, the EVM first executes the code inside onlyOwner. If the require statement passes, the EVM then executes the code at the _; placeholder—which is the body of the changeOwner function itself. If the require statement fails, the transaction reverts, and the function body is never executed.

This image provides a clear visual of this pattern.

Solidity Function Modifier Example: onlyOwner
This code snippet shows the definition of an `onlyOwner` modifier and its application to both a `deposit` and a `withdrawal` function, ensuring both are protected by the same reusable access control logic.

Now, let's see this principle in action. The same video you just watched continues by refactoring the require statements into a reusable onlyOwner modifier.

Access Control | Smart Contract Security Tutorial Part 1

This next clip demonstrates how to create and apply the onlyOwner modifier to solve the code repetition problem we identified earlier.

Watch from 06:36 to 09:34. Pay close attention to how the require logic is moved into the modifier block and how the modifier is then attached to multiple functions, cleaning up the code and improving security.

Advanced Modifier Patterns

Modifiers can do more than just simple ownership checks. They can take arguments and be chained together for more complex validation logic.

Modifiers with Arguments

You can pass arguments from your function directly into a modifier. This is incredibly useful for validating inputs. For example, you could create a modifier to ensure a certain amount of ETH is sent with a function call.

modifier costs(uint _amount) {
    require(msg.value >= _amount, "Not enough ETH sent.");
    _;
}

// The `_price` is passed from the function into the modifier
function purchaseItem(uint _itemId, uint _price) public payable costs(_price) {
    // ... logic to purchase the item
}

Chaining Multiple Modifiers

You can apply multiple modifiers to a single function. They are executed in the order they are listed.

// This function will first check if the caller is the owner,
// and then check if the contract is not paused.
function someAdminTask() public onlyOwner whenNotPaused {
    // ...
}

This video from EatTheBlocks provides a concise walkthrough of the syntax for modifiers with arguments and for chaining them.

Solidity Tutorial: Modifier

Watch these segments to understand how to make your modifiers more flexible with arguments and how to compose them by chaining.

Watch the following two clips: Passing Arguments (02:13 - 02:53): See how arguments from a function can be forwarded to a modifier. Chaining Modifiers (02:53 - 04:11): Understand the execution order when multiple modifiers are applied to one function.

For a comprehensive written reference on these patterns, including inheritance and overriding modifiers (an advanced topic for later), the following article is an excellent resource.

What Are Solidity Modifiers? Explained with Examples

This article from freeCodeCamp covers the concepts we've discussed and provides clear code examples for each.

Read the sections 'What are Solidity Modifiers?', 'How Does the _; Symbol Work?', 'How to Use Multiple Modifiers on a Function', and 'How to Use Modifiers with Arguments'. This will solidify your understanding of the syntax and common patterns.

Test your understanding!

You are building a contract that can be paused in emergencies. You need to ensure that certain functions cannot be executed while the contract is paused.

Here is the starting code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Pausable {
    address public owner;
    bool public isPaused;

    modifier onlyOwner() {
        require(msg.sender == owner, "Caller is not the owner");
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    function setPaused(bool _paused) public onlyOwner {
        isPaused = _paused;
    }

    function doSomethingImportant() public /* Add modifier here */ {
        // important logic...
    }
}

Your task is to:

  1. Create a new modifier named whenNotPaused that reverts the transaction if isPaused is true.
  2. Apply this new modifier to the doSomethingImportant function.
Show answer
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Pausable {
    address public owner;
    bool public isPaused;

    modifier onlyOwner() {
        require(msg.sender == owner, "Caller is not the owner");
        _;
    }

    // 1. Define the whenNotPaused modifier
    modifier whenNotPaused() {
        require(!isPaused, "Contract is paused");
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    function setPaused(bool _paused) public onlyOwner {
        isPaused = _paused;
    }

    // 2. Apply the modifier to the function
    function doSomethingImportant() public whenNotPaused {
        // important logic...
    }
}

With this implementation, only the owner can pause or unpause the contract, and doSomethingImportant will only execute when the contract is not paused.

Conclusion

You have now mastered function modifiers, a cornerstone of secure and elegant Solidity development. By encapsulating validation logic, you make your contracts more readable, less error-prone, and easier to maintain. The onlyOwner pattern is one you will use constantly, and it serves as the foundation for more complex access control systems you'll encounter later.

Key Takeaways:

  • Modifiers promote DRY code: They provide a reusable way to add checks to functions, avoiding repetitive require statements.
  • _; is the execution point: This special placeholder marks where the modified function's body is inserted.
  • Access Control: Modifiers are the primary tool for implementing access control patterns like onlyOwner.
  • Flexibility: Modifiers can accept arguments and be chained together to build sophisticated validation logic.

Preview of the Next Lesson:
So far, our contracts have managed data, but they haven't been able to directly handle the blockchain's native currency, Ether. In the next lesson, we will explore how to enable contracts to receive Ether using payable functions. This will be our first step towards building contracts that can participate in financial transactions, like the token sale contract for our cryptocurrency.

Can't find a good explanation? Sign up and we'll make it for you

Sign up