Modifiers
A Solidity modifier is reusable pre/post logic around a function. onlyOwner, nonReentrant, whenNotPaused are the canonical ones — clean, declarative, audit-friendly when used carefully.
Custom modifiers + OpenZeppelin patterns
EXAMPLE
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
contract Vault is Ownable, ReentrancyGuard, Pausable {
mapping(address => uint256) public balanceOf;
constructor() Ownable(msg.sender) {}
// 1) Custom modifier — argument-taking
modifier costs(uint256 price) {
require(msg.value >= price, "insufficient ETH");
_;
if (msg.value > price) {
(bool ok, ) = msg.sender.call{value: msg.value - price}("");
require(ok, "refund failed");
}
}
// 2) Modifier chaining — order matters! Read left-to-right.
function withdraw(uint256 amount)
external
whenNotPaused // 1st check
nonReentrant // 2nd — also locks for the body
{
require(balanceOf[msg.sender] >= amount, "insufficient");
balanceOf[msg.sender] -= amount; // EFFECTS first
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok, "send failed"); // INTERACTIONS last
}
function premiumOnlyAction() external payable costs(0.01 ether) {
// body runs only if msg.value >= 0.01 ether; excess refunded after
}
// 3) Pause / unpause for emergency response — Ownable + Pausable combo
function pause() external onlyOwner { _pause(); }
function unpause() external onlyOwner { _unpause(); }
// 4) Common modifier antipatterns
// • Modifying state in a modifier — opaque, hard to audit. Prefer doing the
// state change inside the function body.
// • Multiple state-changing modifiers on the same function — chain in the
// order the call sites expect; document it.
// • Long modifiers — split into helpers / internal functions.
// • Forgetting the `_;` placeholder — the function body NEVER runs.
}
// 5) When NOT to write a custom modifier
// If the check appears in ONE function, just write `require(...)` inline. Modifiers
// shine when the SAME pre/post is enforced in 3+ places.
// 6) OpenZeppelin AccessControl — role-based instead of single owner
import "@openzeppelin/contracts/access/AccessControl.sol";
contract Treasury is AccessControl {
bytes32 public constant TREASURER_ROLE = keccak256("TREASURER");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER");
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(TREASURER_ROLE, msg.sender);
}
function disburse(address to, uint256 amount) external onlyRole(TREASURER_ROLE) {
// …
}
}
Why it matters
Apply the checks-effects-interactions pattern even with nonReentrant — defence-in-depth means surviving a future Solidity / compiler quirk. Modifiers are scaffolding; the order of state changes inside the function still matters.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
modifier onlyOwner() {
require(msg.sender == owner, "not owner");
_;
}
modifier whenOpen() {
require(open, "closed");
_;
}
Try it Yourself »
Exercise
Solidity placeholder inside a modifier body.
modifier onlyOwner() { require(msg.sender == owner);
; }
One character.
Discussion
Loading…