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,
whenNotPausedandwhenPaused, 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.
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.

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:
- Define a
PAUSER_ROLEin our contract. - Create public
pause()andunpause()functions. - Protect these functions with the
onlyRole(PAUSER_ROLE)modifier. - In the constructor, grant the
PAUSER_ROLE(and theDEFAULT_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, andAccessControl. - Roles: We define both a
MINTER_ROLEand aPAUSER_ROLE. pause()andunpause(): These functions arepublicbut are protected byonlyRole(PAUSER_ROLE). They simply call theinternal_pause()and_unpause()functions provided byPausable.overridekeyword: You seeoverride(ERC20, ERC20Pausable)on the_beforeTokenTransferfunction. This is required by Solidity because both parent contracts (ERC20andERC20Pausable) have a definition for this function. We must explicitly state that we are using the version fromERC20Pausablewhich 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.solprovides the core logic with_pause(),_unpause(), and thewhenNotPausedmodifier. - The
ERC20Pausable.solextension 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()andunpause()functions themselves. CombiningPausablewithAccessControlto create aPAUSER_ROLEis 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.