Solidity Modifiers: What the Underscore Actually Does
A modifier is a named block of code wrapped around a function body. What the underscore means, why code can sit on both sides of it, and how to read a function that wears one.
TL;DR
- A modifier is a named block of code wrapped around a function body, written once and attached to a function by putting its name in the signature.
- The underscore,
_;, is the whole idea. It marks where the function's own body is spliced in. Code above it runs first, code below runs last. - A modifier can take parameters, and use the argument it was handed to look something up in state.
- Modern libraries write one-line modifiers that call an internal function, because a modifier's code is copied into every function that wears it.
- Several modifiers run in the order written, and the body only runs if every one of them reaches its underscore.
What is a modifier in Solidity?
A modifier is a named piece of code that wraps around a function. You declare it once, attach it to a function by writing its name in the signature, and the function then runs inside it. It is a way of saying the same thing in front of many functions without repeating the words.
OpenZeppelin's Ownable declares one, three lines long:
modifier onlyOwner() {
_checkOwner();
_;
}
And a function wearing it:
function transferOwnership(address newOwner) public virtual onlyOwner {
In that signature the modifier sits alongside public and virtual, but it is a different kind of word: those state facts about the function, while onlyOwner brings code that runs first.
What does the underscore in a Solidity modifier do?
The underscore statement, written _;, marks where the modified function's own body is inserted. Everything above it in the modifier runs before the body, everything below runs after. A modifier is therefore not a prefix but a wrapper, and the underscore is the hole the function drops into.
Most modifiers have nothing below the underscore, so the distinction never comes up. Uniswap V2 has one that does, and it makes the mechanism obvious:
uint private unlocked = 1;
modifier lock() {
require(unlocked == 1, 'UniswapV2: LOCKED');
unlocked = 0;
_;
unlocked = 1;
}
Four statements, and the body goes between the third and the fourth. Attached:
function mint(address to) external lock returns (uint liquidity) {
A call to mint therefore executes, in order: the require, the write setting unlocked to 0, every line inside mint, then the write setting it back to 1. The body is in the middle of the modifier, not after it.
Three of those four statements are invisible in the function you are looking at. They touch state, and the only sign of them in mint is one word in the signature. Move the underscore below unlocked = 1; and the flag would be cleared before the body ran. The position of a single character decides the behaviour.
Can a modifier take a parameter?
Yes. A modifier can declare parameters exactly as a function does, and the function wearing it passes arguments in brackets after the name. Inside the modifier those arguments are ordinary values, so it can use them to index into a mapping or compare against stored state.
The ENS registry is the clearest production example:
// Permits modifications only by the owner of the specified node.
modifier authorised(bytes32 node) {
address owner = records[node].owner;
require(owner == msg.sender || operators[owner][msg.sender]);
_;
}
It takes a node, reads that node's record out of the records mapping, and compares the stored owner against msg.sender. It is not checking a fixed condition, but one about the particular thing this call is trying to change.
function setOwner(
bytes32 node,
address owner
) public virtual override authorised(node) {
_setOwner(node, owner);
emit Transfer(node, owner);
}
authorised(node) hands the function's own first argument to the modifier. The same word appears on four functions in that file, each passing its own node, so each asks a different question. Note what does the stopping: an ordinary require, so a failed check rewinds the whole call. See require and revert.
Why do modern contracts put the check in an internal function?
Because a modifier's body is copied into every function that wears it, while an internal function exists once and is called. Keeping the modifier to a single line means the repeated part is one call instruction rather than the whole check, which keeps the compiled contract smaller.
OpenZeppelin's Pausable shows the pattern twice, with two modifiers reading one boolean:
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
Neither contains a check. Each calls an internal function:
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
Two reasons. The first is size: whatever you write inside a modifier is inlined into each function that uses it when the source is compiled into bytecode. A four-line check on ten functions appears ten times.
The second is that a function can be overridden and a modifier cannot. _requireNotPaused is virtual, so a contract inheriting from Pausable can replace the check while every function still says whenNotPaused. See visibility in Solidity for what those two words are doing there. When you meet a one-line modifier, the sentence you want is one hop away.
What order do modifiers run in?
They run in the order written, left to right. The first modifier's pre-underscore code runs, then the second's, then the function body, and any post-underscore code unwinds in reverse. Modifiers nest rather than queue, which only shows when one of them has code on both sides.
The ENS base registrar declares two:
modifier live() {
require(ens.owner(baseNode) == address(this));
_;
}
modifier onlyController() {
require(controllers[msg.sender]);
_;
}
And wears both on one function:
function renew(
uint256 id,
uint256 duration
) external override live onlyController returns (uint256) {
Calling renew runs the live check, then the onlyController check, then the body. Because both end at their underscore, nothing unwinds afterwards and the order never shows. It starts to matter when a modifier has statements below its underscore: read a pair like lock above as boxes inside boxes rather than as a checklist.
How do you read a function that has a modifier?
Read the modifier first and the body second, because the modifier can stop the body from ever running. The signature line is not where the behaviour begins. Attached modifiers run first, consult state you have not looked at, and can end the call before a single line inside the braces executes.
Three questions per modifier get you what you need:
- Where is it declared? In this contract, or in one it inherits from, often another file entirely.
- What runs before the underscore? That is the function's precondition, written somewhere other than the function.
- Is there anything after it? If yes, the function has an epilogue it does not mention.
Answer those once and you can read every function wearing that modifier at a glance. It slots into how to read a smart contract as part of the signature sweep, not the body sweep.
What does the compiler check about modifiers?
Less than you might hope. It checks that the modifier exists and is reachable, in this contract or one it inherits from, and that arguments match the declared parameters in count and type. A misspelled modifier name is a compile error, not a silently skipped check.
What it does not check is meaning. It will not tell you that a modifier is missing from a function you meant to wrap, because it cannot know what you meant, and it will not object to two modifiers in a surprising order.
And it does not require an underscore at all. A modifier with no _; is valid, and any function wearing it never runs its own body: the call succeeds and does nothing. That is the clearest statement of what a modifier is. The body is not guaranteed to run. It is a block of code the modifier splices in, at a position the modifier chooses, and the language will let a modifier decline.
Delete the line and ask what breaks
- Delete
_;fromlockand it still compiles, but every function wearing it stops running its own body.mintwould take tokens in and mint nothing. - Move
_;belowunlocked = 1;and the flag is cleared before the body runs, so the modifier sets and clears a flag around nothing at all. - Delete
onlyOwnerfromtransferOwnershipand the function still works perfectly, for everyone. That word was doing the only thing that mattered. - Change
authorised(node)toauthorisedand the file stops compiling, because the modifier declares a parameter and none was passed.
Related questions
What does the underscore mean in a Solidity modifier?
It marks where the modified function's body is inserted. Code above _; runs before the body, code below runs after. A modifier without an underscore compiles, but the body never runs.
Can a modifier change state? Yes. A modifier is ordinary code and can write to storage, emit events, and call other functions, on either side of the underscore. One that only reads is a convention, not a rule.
Can a modifier take arguments?
Yes. It declares parameters like a function, and the function wearing it passes values in brackets after the name, for example authorised(node). Inside, those are ordinary values it can use to read state.
In what order do multiple modifiers execute? In the order written, left to right, nesting inwards. The first modifier's pre-underscore code runs first, then the second's, then the body, and any post-underscore code unwinds in reverse.
Why do modifiers call internal functions instead of holding the logic?
Because a modifier's body is copied into every function that uses it, while an internal function is stored once and called. It keeps the contract smaller, and the internal function can be virtual so children may replace it.
Can a modifier be inherited?
Yes. A modifier declared in a contract is available to every contract inheriting from it, which is why onlyOwner is used by contracts that never declare it. It is often defined in a different file from the function using it.
Where to go next
A modifier is a language mechanism, not a guarantee. It is a named block of code with a hole in it, the body goes in the hole, and the position of that hole decides what runs when. Everything else follows from that.
The habit to keep is the reading order: modifier first, body second. Take that into an unfamiliar file with the method in how to read a smart contract, and the signature line stops being scenery and becomes the most information-dense line in the function.
Tagged