Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Implementing Emergency Stops with OpenZeppelin's Pausable

Recommended resources

The freely available resources this lesson will direct you to.

Hello! Welcome back to our module on smart contract security.

In the last lesson, we took a significant step forward by implementing Role-Based Access Control (RBAC). You learned how to move beyond a single "owner" to a more granular system with distinct roles like MINTER_ROLE and a DEFAULT_ADMIN_ROLE. We even previewed the idea of a PAUSER_ROLE, which is the perfect segue into today's topic.

While RBAC controls who can perform actions, we also need a mechanism to handle unforeseen emergencies. What if a critical bug is discovered in your contract after it's deployed? You need a "big red button" to press that can halt activity and prevent further damage. This concept, often called a circuit breaker in backend systems, is a vital security pattern in smart contracts.

Today, you will learn how to add this emergency stop functionality to a contract using OpenZeppelin's Pausable utility. We will combine this with the RBAC knowledge you just gained to ensure only a designated PAUSER_ROLE can press that button.


1. The Circuit Breaker Pattern: Introducing Pausable

The core idea of an emergency stop is simple: the contract has two states, "paused" and "not paused." When paused, critical functions are disabled. OpenZeppelin's Pausable.sol contract provides a standard, audited implementation of this pattern.

It works by introducing:

  • A boolean state variable that tracks if the contract is paused.
  • Two modifiers, whenNotPaused and whenPaused, that you can add to your functions to restrict their execution based on the contract's state.
  • Internal functions, _pause() and _unpause(), to switch between states.

Let's look at the official OpenZeppelin documentation to see the contract's structure and a basic usage example.

Security Utilities - Pausable

This documentation page shows the abstract Pausable contract and a straightforward example of how to integrate it. It's the foundational building block for our emergency stop feature.

Read the section 'Emergency Pause Functionality' and the 'Usage Example' that follows. Notice how the example contract inherits from Pausable and Ownable, exposes public emergencyPause() and unpause() functions, and protects a criticalFunction() with the whenNotPaused modifier.

As you saw in the example, the _pause() and _unpause() functions are internal. This is intentional. The Pausable contract provides the mechanism, but it's our responsibility to expose it securely. The example uses Ownable, but we will use the more flexible AccessControl pattern from our previous lesson.


2. Pausing an ERC-20 Token

For an ERC-20 token, the most critical functions to pause are those related to token movement: transfer, transferFrom, and approve. We could manually add the whenNotPaused modifier to each of these functions, but that would require us to override them all.

Fortunately, OpenZeppelin provides a much more elegant solution: the ERC20Pausable.sol extension.

This contract inherits from ERC20 and Pausable and cleverly uses a hook called _beforeTokenTransfer. This is a function provided by the base ERC20 contract that is automatically executed just before any token transfer occurs. The ERC20Pausable extension overrides this hook to add a single check: require(!paused()).

The result is that by simply inheriting from ERC20Pausable, all token transfers are automatically protected without any extra effort on our part. This is an excellent example of the power of composition and hooks in smart contract development, which is similar to using middleware in a web framework to intercept requests.

The following animation from the OpenZeppelin Contracts Wizard shows just how simple it is to add this feature.

OpenZeppelin Contracts Wizard: Adding Pausable Functionality to an ERC20 Token
This GIF shows the OpenZeppelin Contracts Wizard. As the 'Pausable' feature is selected on the left, the code on the right automatically includes the necessary `ERC20Pausable` inheritance and overrides, providing an instant, secure implementation.

Now, let's read a brief explanation that reinforces this concept.

ERC-20 Token with Mint, Burn, and Pause

This article explains how the ERC20Pausable extension works and why it's a convenient way to secure a token.

Read the short section titled '4. Implementing Pausable Functionality'. Focus on how ERC20Pausable simplifies the process by pre-wiring the whenNotPaused modifier to all transfer functions via the _beforeTokenTransfer hook.


3. Combining Pausable with Role-Based Access Control

We now have a token that can be paused, but we still need to create the public functions that allow a privileged user to trigger the pause and unpause actions. This is where we combine today's lesson with the last one.

We will:

  1. Define a PAUSER_ROLE in our contract.
  2. Create public pause() and unpause() functions.
  3. Protect these functions with the onlyRole(PAUSER_ROLE) modifier.
  4. In the constructor, grant the PAUSER_ROLE (and the DEFAULT_ADMIN_ROLE) to the contract deployer.

Here is a complete contract, MyToken.sol, that integrates ERC20, AccessControl, and ERC20Pausable.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract MyToken is ERC20, ERC20Burnable, ERC20Pausable, AccessControl {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

    constructor() ERC20("MyToken", "MTK") {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(MINTER_ROLE, msg.sender);
        _grantRole(PAUSER_ROLE, msg.sender);
    }

    // Public function to pause the contract, restricted to PAUSER_ROLE
    function pause() public onlyRole(PAUSER_ROLE) {
        _pause();
    }

    // Public function to unpause the contract, restricted to PAUSER_ROLE
    function unpause() public onlyRole(PAUSER_ROLE) {
        _unpause();
    }

    // Public function to mint new tokens, restricted to MINTER_ROLE
    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
        _mint(to, amount);
    }
    
    // The following function is what makes ERC20Pausable work.
    // It's called before any token transfer.
    // We need to override it because it's defined in both ERC20 and ERC20Pausable.
    function _beforeTokenTransfer(address from, address to, uint256 amount)
        internal
        whenNotPaused
        override(ERC20, ERC20Pausable)
    {
        super._beforeTokenTransfer(from, to, amount);
    }
}

Key things to note in this code:

  • Inheritance: The contract inherits from ERC20, ERC20Burnable, ERC20Pausable, and AccessControl.
  • Roles: We define both a MINTER_ROLE and a PAUSER_ROLE.
  • pause() and unpause(): These functions are public but are protected by onlyRole(PAUSER_ROLE). They simply call the internal _pause() and _unpause() functions provided by Pausable.
  • override keyword: You see override(ERC20, ERC20Pausable) on the _beforeTokenTransfer function. This is required by Solidity because both parent contracts (ERC20 and ERC20Pausable) have a definition for this function. We must explicitly state that we are using the version from ERC20Pausable which builds upon the base version.
Test your understanding!

Imagine your project has a security team that is responsible for pausing the contract, and a treasury team responsible for minting tokens. Neither team should have DEFAULT_ADMIN_ROLE privileges.

How would you modify the constructor of the MyToken contract above to assign the PAUSER_ROLE to a securityTeamAddress and the MINTER_ROLE to a treasuryTeamAddress, while the contract deployer retains only the admin rights?

Show answer

You would modify the constructor to accept the addresses for the security and treasury teams, and then grant the roles accordingly. The deployer (msg.sender) only gets the DEFAULT_ADMIN_ROLE.

contract MyToken is ERC20, ERC20Burnable, ERC20Pausable, AccessControl {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

    constructor(address securityTeamAddress, address treasuryTeamAddress) ERC20("MyToken", "MTK") {
        // The deployer gets admin rights to manage roles
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        
        // The security team gets the ability to pause/unpause
        _grantRole(PAUSER_ROLE, securityTeamAddress);
        
        // The treasury team gets the ability to mint
        _grantRole(MINTER_ROLE, treasuryTeamAddress);
    }

    // ... rest of the contract ...
}

This is a perfect practical application of the RBAC principles you learned in the previous lesson!


Conclusion

You have now added a critical layer of security to your smart contract. The pausable pattern is an industry-standard best practice that provides a safety net in case of emergencies, giving you and your users peace of mind.

Key Takeaways:

  • The Pausable pattern acts as a circuit breaker, allowing you to halt critical functions in an emergency.
  • OpenZeppelin's Pausable.sol provides the core logic with _pause(), _unpause(), and the whenNotPaused modifier.
  • The ERC20Pausable.sol extension is a convenient way to apply this protection to all token transfer functions automatically by hooking into _beforeTokenTransfer.
  • It is crucial to protect the public pause() and unpause() functions themselves. Combining Pausable with AccessControl to create a PAUSER_ROLE is a robust and flexible approach.

Preview of the Next Lesson:
So far, we have focused on security mechanisms that prevent unauthorized actions (AccessControl) and provide manual safety switches (Pausable). However, there are other, more subtle risks on a public blockchain. In the next lesson, we will explore one of these: front-running. You will learn what it is, why it happens, and discuss basic strategies to mitigate it in your contracts.

Can't find a good explanation? Sign up and we'll make it for you

Sign up