Modifier

Reusable code blocks in Solidity that modify function behavior, commonly used for access control and input validation.

Modifiers in Solidity are reusable code blocks that can change or extend function behavior without duplicating code. They're most commonly used for access control, input validation, and reentrancy protection. The modifier code executes before and/or after the function body, with the special _; placeholder indicating where the modified function's code runs.

Basic Modifier Syntax

1contract Example {
2 address public owner;
3
4 modifier onlyOwner() {
5 require(msg.sender == owner, "Not owner");
6 _; // Function body executes here
7 }
8
9 function sensitiveAction() external onlyOwner {
10 // Only owner can call this
11 }
12}

How Modifiers Execute

The _; placeholder determines when the function body runs:

1modifier beforeAndAfter() {
2 // Code here runs BEFORE the function
3 doSomethingFirst();
4
5 _; // Function body executes
6
7 // Code here runs AFTER the function
8 doSomethingAfter();
9}

Common Modifier Patterns

Access Control

1modifier onlyOwner() {
2 require(msg.sender == owner, "Not owner");
3 _;
4}
5
6modifier onlyRole(bytes32 role) {
7 require(hasRole(role, msg.sender), "Missing role");
8 _;
9}
10
11modifier onlyWhitelisted() {
12 require(whitelist[msg.sender], "Not whitelisted");
13 _;
14}

Input Validation

1modifier validAddress(address _addr) {
2 require(_addr != address(0), "Zero address");
3 _;
4}
5
6modifier nonZeroAmount(uint256 _amount) {
7 require(_amount > 0, "Zero amount");
8 _;
9}
10
11modifier withinBounds(uint256 _value, uint256 _min, uint256 _max) {
12 require(_value >= _min && _value <= _max, "Out of bounds");
13 _;
14}

State Checks

1modifier whenNotPaused() {
2 require(!paused, "Contract paused");
3 _;
4}
5
6modifier whenPaused() {
7 require(paused, "Contract not paused");
8 _;
9}
10
11modifier inState(State _state) {
12 require(state == _state, "Invalid state");
13 _;
14}

Reentrancy Protection

1modifier nonReentrant() {
2 require(!locked, "Reentrant call");
3 locked = true;
4 _;
5 locked = false;
6}

OpenZeppelin's ReentrancyGuard is the standard implementation:

1import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
2
3contract Vault is ReentrancyGuard {
4 function withdraw() external nonReentrant {
5 // Protected from reentrancy
6 }
7}

Modifier with Parameters

Modifiers can accept arguments:

1modifier costs(uint256 _amount) {
2 require(msg.value >= _amount, "Insufficient payment");
3 _;
4 // Refund excess
5 if (msg.value > _amount) {
6 payable(msg.sender).transfer(msg.value - _amount);
7 }
8}
9
10function buyItem() external payable costs(1 ether) {
11 // User must send at least 1 ETH
12}

Multiple Modifiers

Functions can have multiple modifiers, executed left to right:

1function criticalAction()
2 external
3 onlyOwner
4 whenNotPaused
5 nonReentrant
6{
7 // All three conditions must pass
8}
9
10// Execution order:
11// 1. onlyOwner check
12// 2. whenNotPaused check
13// 3. nonReentrant lock
14// 4. Function body
15// 5. nonReentrant unlock

Security Considerations

Modifier Execution Order

Order matters for security:

1// WRONG ORDER - reentrancy check after access control
2function withdraw() external onlyOwner nonReentrant { }
3
4// CORRECT - reentrancy protection should be first
5function withdraw() external nonReentrant onlyOwner { }

State Changes in Modifiers

Be careful with state changes after _;:

1modifier countCalls() {
2 _;
3 callCount++; // Runs AFTER function - could be skipped if function reverts
4}

Gas Considerations

Modifiers add gas overhead. Consider inlining for frequently-called functions:

1// Modifier version (more readable)
2function transfer() external nonReentrant { }
3
4// Inlined version (saves gas)
5function transfer() external {
6 require(!locked, "Reentrant");
7 locked = true;
8 // ... function logic ...
9 locked = false;
10}

Modifier vs Internal Function

AspectModifierInternal Function
SyntaxDeclarativeImperative
ReadabilityClear at function signatureLogic hidden in body
ReusabilityHighHigh
FlexibilityLimited (before/after pattern)Full control
GasSlightly higherSlightly lower

Audit Checklist

When auditing modifiers:

  • Access control modifiers on all sensitive functions
  • Correct modifier order on functions
  • No state changes that could be bypassed
  • Reentrancy protection where needed
  • Proper validation of modifier parameters
  • No circular dependencies between modifiers

Modifiers are a fundamental Solidity pattern for writing clean, secure, and maintainable code. Proper use of modifiers significantly reduces the risk of access control vulnerabilities and code duplication.

Need expert guidance on Modifier?

Our team at Zealynx has deep expertise in blockchain security and DeFi protocols. Whether you need an audit or consultation, we're here to help.

Get a Quote

oog
zealynx

Subscribe to Our Newsletter

Stay updated with our latest security insights and blog posts

© 2024 Zealynx