function (<parameter types>) {internal|external|public|private} [pure|constant|view|payable] [(modifiers)] [returns (<return types>)]

Solidity is a programming language specifically designed for writing smart contracts on the Ethereum blockchain. It provides a range of features to ensure that contracts can execute safely and efficiently. In this third function installment, we’ll be discussing function modifiers.

see also: Solidity Functions 1, Solidity Functions 2

Function Modifiers in Solidity

Function modifiers are a way to modify the behavior of a function in Solidity. Modifiers are similar to regular functions in that they have a name, may have parameters, and contain code that is executed when called. Unlike functions, they cannot be called directly. Modifiers can add additional checks or constraints to a function before or after it executes. This can be useful for adding security features to a contract or ensuring that certain conditions are met before or after executing a function.

Here’s an example of a function modifier in Solidity:

modifier onlyOwner() {    
	require(msg.sender == owner);    
	_;
}

In this example, we define a modifier called onlyOwner(). Note the use of the modifier keyword. This modifier checks that the msg.sender (i.e. the address that called the function) is the contract’s owner. If the check passes, the function continues to execute using the placeholder. What this means is that the calling function’s logic will be executed at the position of the . If the require check fails, the function is aborted and an exception is thrown.

At times, it may be necessary to perform certain actions after a function’s logic has been executed. Modifiers are very useful in this case. In this case, there is an added check to ensure that the msg.sender still has a balance after the modified function’s logic has executed.

modifier onlyOwnerAndFunded() {
    require(msg.sender == owner);
    _;
    require(balanceOf(msg.sender) > 0);
}

Wherever the is included in the modifier is where the modified function’s logic will be executed. The does not have to be the last line in the function and it may be repeated as necessary. This means that the logic of the function may be also repeated so it is very important to use caution with the _. In this example, the function logic will be repeated.

modifier doubleLogicModifier() {
    require(msg.sender == owner);
    _;
    _;
}

It is still acceptable to use additional require statements in the modified function. They do not all have to be in the modifier. In this example, the onlyOwner modifier is applied to the withdraw function. This ensures that only the owner of the contract can withdraw funds. The require statement inside the function adds an additional check only to this function to ensure that the requested amount is less than or equal to the balance of the contract.

function withdraw(uint amount) public onlyOwner {
    require(amount <= balance);
    balance -= amount;
    msg.sender.transfer(amount);
}

Modifiers can not be added to other modifiers but multiple modifiers can be applied to functions. This is very useful for validation and/or permission checks and helps to increase code modularity while decreasing redundancy. The modifiers will be executed in the order they are listed so in the following example, onlyOwner will be executed first. At first glance, this would seem problematic because one would assume that the function logic is executed each time the is encountered. However, Solidity is smart enough to know this would not be a desired result. Instead, if multiple modifiers are present, the code from the subsequent modifier is injected in the first modifier’s position. So the execution sequence is: onlyOwner, onlyEOA, and then the withdraw logic.

function withdraw(uint amount) public onlyOwner onlyEOA {
    require(amount <= balance);
    balance -= amount;
    msg.sender.transfer(amount);
}
modifier onlyOwner() {
    require(msg.sender == owner);
    _;}
modifier onlyEOA() {
    require(!msg.sender.code.length > 0);
    _;
}

Modifiers may also accept parameters. For instance, if we wanted to make the function require that the amount be less than or equal to balance, we could add:

function withdraw(uint amount) public onlyLessThanBalance(amount) {
	balance -= amount;
    msg.sender.transfer(amount);
}
modifier onlyLessThanBalance(uint256 amount) {
    require(amount <= balance);
    _;
}

Common Misconception

Several sources claim that modifiers cannot change storage variables. This may have been the case with older versions of Solidity but is no longer true. Modifiers can modify state variables (variables defined outside of function definitions) but cannot modify the calling function’s input parameters or return values. The Solidity Docs say, “Modifiers cannot implicitly access or change the arguments and return values of functions they modify. Their values can only be passed to them explicitly at the point of invocation.” However, modifying storage is allowed: