iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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); ; }

Discussion

Loading…