Recommended resources
The freely available resources this lesson will direct you to.
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.

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.
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:
- Create a new modifier named
whenNotPausedthat reverts the transaction ifisPausedistrue. - Apply this new modifier to the
doSomethingImportantfunction.
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
requirestatements. _;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.