logoAcademy

Modifiers

More about Solidity

Before discussing the concept of modifiers, we will first start by talking about the limitations of the visibilities provided by Solidity

Aside: Limitations of Visibility

Recall that with regards to functions, we have the following four visibilities available:

  • Public
  • Private
  • Internal
  • External Let's now consider the following contract:
contract Safe {
  
  function deposit() public {}
  
  function withdraw() public {}

}

Above, we have the outline of a very basic safe contract which is meant to be accessed only by the deployer of the Safe contract. Currently, this Safe contract does not maintain the invariant previously mentioned as both deposit and withdraw are marked as public. However, even if we were to modify the visibility of the following functions, we would have the following:

  • The functions become inaccessible to all accounts except the contract itself
  • The functions still remain accessible to all accounts

The scenario above outlines the following problem: for functions whose access we want to restrict to only authorized users, we cannot rely on visibility.

Modifying the Behavior of Functions via Modifiers

To combat the problem previously mentioned, we now look at modifiers. Modifiers behave similarly to functions and they modify the logic of the functions they are attached to. The syntax for defining a modifier is as follows:

modifier <modifier-name>(<arguments>) {}

To attach a modifier to a function, we have the following:

function func() public <modifier>(<arguments>){}

To introduce the concept of modifiers to our Safe contract, let's first modify it to the following:

contract Safe {
  
  address owner;
  
  constructor() {
    owner = msg.sender; 
  }
  
  function deposit() public {}
  
  function withdraw() public {}

}

With the above changes, we are setting the state variable owner equal to the contract deployer (i.e. msg.sender) and so we are now able to store the address of the contract deployer in our Safe contract. Let's now write a modifier to only allows for the owner of the Safe contract to call the function it is attached to:

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

Examining each line, we have:

  • Line 2: we are using a require statement; the require statement has the following syntax: require(boolean condition, error message). If the boolean condition is true, we move onto the next line of code. Otherwise, we raise an error with the error message provided in the statement. In this context, we are requiring that any person calling the function onlyOwner is attached to is the owner of the Safe contract.
  • Line 3: assuming that the owner of the Safe contract is calling the associated function, we now revert control back to the original function via the _; keyword. Incorporating the modifier into our Safe contract we now have:
contract Safe {
  
  address owner;
  
  modifier onlyOwner() {
  require(msg.sender == owner, "You are not the owner"!);
  _;
  }
  
  constructor() {
    owner = msg.sender; 
  }
  
  function deposit() public onlyOwner() {}
  
  function withdraw() public onlyOwner() {}

}

For either deposit or withdraw, we now have the following execution flow:

  • An account first calls either deposit or withdraw
  • Since the onlyOwner modifier is attached to either function, onlyOwner is first executed
  • If the account is the contract owner, we return the execution flow back to the parent function (in this case, either deposit or withdraw)

On this page