Modifiers

A New Type of Function

Although we have already covered functions, there is one aspect of functions that we have yet to explore.

To see the current problem with our understanding of functions, consider the visibility of functions; by specifying the visibility of a function, we can restrict actors from accessing a function. However, this becomes an issue if we want to allow some actors to call a function, while denying permission to others. With our understanding of visibility, we know that this is not possible. Therefore, are we doomed?

It seems as if we could implement permissioned functions if we could modify the logic of our function. And indeed, this is what modifiers allow us to do! Below is the general syntax for modifiers:

modifier modifierName(P) {
    // Insert logic here
}

where P are the parameters that the modifier takes in. To see how modifiers are used, assume we have the following contract:

contract Bank {

    address owner;
    
    function withdraw() public {
        // Withdraw logic goes here
    }

}

As the name intends, Bank holds a large sum of money and withdraw is a function is which we only want the owner to be able to call. For this guarantee to hold true, lets create a modifier onlyOwner that, when assigned to a function, only allows for the owner (i.e. deployer) of a contract to be able to call.

modifier onlyOwner() {
    require(msg.sender == owner, "You are not the owner!");
    _;
}

To understand the logic of onlyOwner, it is best to examine the modifier line-by-line:

  • Line 2: we are requiring that the current message sender is the owner of the contract. If this invariant does not hold, the transaction will fail

  • Line 3: the underscore implies that we are reverting control back to the function itself

The following code utilizes onlyOwner:

contract Bank {

    address owner;
    
    modifier onlyOwner() {
        require(msg.sender == owner, "You are not the owner!");
        _;
    }
    
    function withdraw() public onlyOwner {
        // Withdraw logic goes here
    }

}

In the function header of withdraw, we are calling onlyOwner; withdraw will execute if the owner of Bank is the one calling the function.

Although the _ symbol inside modifiers might imply that we are reverting control back to the calling function for the rest of the execution context, this is actually not the case! Consider the following code:

contract Restroom {

    bool isOccupied;
    
    modifier lock() {
        require(!isOccupied, "Bathroom is already occupied!");
        isOccupied = true;
        _;
        isOccupied = false;
    }
    
    function useRestroom() public lock {
        // Logic goes here
    }

}

The contract represents a restroom where only one person is allowed at a time. Assuming that anyone can call useRestroom at any time, the lock modifier allows us to adhere to this invariant. Examining the modifier line-by-line:

  • Line 6: we are checking if the restroom is occupied; if so, our function will revert

  • Line 7: we are "locking" the restroom

  • Line 8: we delegate control back to useRestroom

  • Line 9: after useRestroom is finished executing, control is delegated back to lock, where it "unlocks" the restroom

Last updated