When I first started developing in Solidity, I thought a lot about the security of my contract code and how I would handle a hack or attempt to steal funds. I was primarily building games on blockchains that would handle real money from real players, so protecting the funds collected through my DApp and stored on contracts was of high importance.
What would I do if hackers discovered a way to exploit my code? How would I protect funds if a bug was discovered?
In this article, I’ll share some insights into implementing the pause function in Solidity — an important tool to add to your arsenal for keeping contracts secure. I’ll use a Solidity lottery smart contract project to illustrate one of the methods.
Jump ahead:
Contract code uploaded to a blockchain is immutable, so taking it offline in the event of a hack isn’t possible. And trying to diagnose an issue while your contract functions are potentially under attack could lead to lost time and even greater damage.
Thorough unit testing and code review are essential, but planning for and implementing security controls on your contract is an important precaution for any blockchain project. The possibility of an exploit is very real, especially if your DApp becomes very popular and more people start to look at your contract code.
I recently wrote a lottery game project in Solidity and launched it on Polygon. Security was critical for me because I was planning to hold funds while games were live and then distribute winnings to players after games closed.
There are multiple solutions that I employed to protect my contract functions and the funds attached to them, including the ability to pause functions. In this post, I’ll cover the pros and cons of adding pause functionality. I’ll also review three methods for implementing the pause function in a Solidity smart contract. If you’re looking for information on how to write a lottery smart contract, I hope you’ll find this article on pause functionality useful.
First, let’s consider how the pause function can be a helpful contract modifier.
When you push code to a blockchain, you need to decide what level of visibility to grant to every function. Pause capability is one way to protect functions that will be available to all users (e.g., public
or external
functions). A pause modifier or condition can temporarily remove a function’s ability to work, without impacting other contract functions and without opting to destroy the entire contract and start from scratch.
Imagine your DApp offers a service to mint an NFT, or sell a digital good, facilitated by your smart contract. The ability to put a temporary hold on these sales or mints may be important to you.
In the case of my lottery smart contract, the critical public functions I wanted to protect included buyTicket
, a function that receives funds and mints a lottery ticket for players, and finalizeWinner
, a function that closes a lottery, selects a random winner, and processes transactions to pay out that winner and commission fees. I chose to add a pause capability to my lottery smart contract in order to secure these functions in case I needed to take them offline temporarily.
Pause capability can also be helpful when creating new tokens through initial coin offerings (ICOs). When you offer a new token to users, you may want to pause the ability of token holders to trade them on an exchange while the initial sale is still underway. Speculative buyers who purchase your token with the intent of immediately selling it at a profit can hurt your token’s value. Implementing a temporary pause on transfers can prevent this behavior.
The ability to pause a smart contract is powerful, but it can adversely affect consumer trust in your DApp.
For example, a consumer who purchases a token through an ICO may be comfortable with a temporary pause on future transfers, but wouldn’t want that pause to last forever, or to find that it could be reimplemented without prior notice.
In general, if consumers know that a pause function is possible and could be applied at any time, this can lead to questions about the integrity of your DApp. Implementing an ability to permanently remove a pause condition after a certain point in time, or under certain conditions, may be important for your business. This capability is also possible with smart contracts written in Solidity.
Let’s take a look at three methods for adding pause functionality to a Solidity smart contract: the global Boolean variable method, the Pausable.sol method, and the global Boolean variable with pause control method.
The most straightforward method for adding pause functionality to a Solidity contract is to simply declare a global Boolean variable paused
, add this variable with a require
condition in your function, and create a setPaused
function enabling you to change the value.
Here’s an example:
// A global boolean variable is created to manage pause capabilities called paused bool public paused; // This variable is then implemented in the function with a "require" statement that its value resolve to false, otherwise a message is displayed and the function will not execute function transfer1(address to, uint256 amount) external { require(paused == false, "Function Paused"); require(balances[msg.sender] >= amount, "Not enough tokens"); balances[msg.sender] -= amount; balances[to] += amount; } // Another function must be added to change the value of the bool variable 'paused'. This function must be restricted in use to prevent any user from accessing it. In this case, we use a require statement to restrict use to the contract owner function setPaused(bool _paused) public { require(msg.sender == owner, "You are not the owner"); paused = _paused; }
I decided to incorporate the global Boolean method into my Solidity lottery contract. Here’s how I implemented it:
// This function is used to buy a ticket to a game, and calls another function to mint the NFT function buyTicket(address from, uint lottoID) public payable { require (lottoArray[lottoID - 1].isLive, "Lotto is not live"); require(paused == false, "The Buy Ticket function has been paused"); lottoPlayers.push(Player(from, lottoID)); mintTicket(from, lottoID); if (address(this).balance > lottoArray[lottoID - 1].targetValue) { endLotto(false, lottoID); emit LottoClosed(lottoID); } }
Another method for adding pause functionality is the popular Pausable.sol, a module for the OpenZeppelin open source smart contract library that many DApps employ to standardize pause actions.
First, you’ll use an import
statement to inherit the pause modifiers from Pausable.sol, which can then be employed in any functions you write:
import "@openzeppelin/contracts-ethereum-package/contracts/utils/Pausable.sol";
Next, you’ll include whenNotPaused
as a modifier in your functions if you want to only offer the function when pause
is false
.
// This transfer function has whenNotPause set as a modifier in its function declaration. It can only be run if whenNotPaused is true. function transfer2(address to, uint256 amount) external whenNotPaused { require(balances[msg.sender] >= amount, "Not enough tokens"); balances[msg.sender] -= amount; balances[to] += amount; }
Here’s how whenNotPaused
is defined in the Pausable.sol contract:
/*** @dev Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPaused() { require(!_paused, "Pausable: paused"); _; }
If adding an option to remove pause functionality is important for the integrity of your project, you can add this capability with the global Boolean variable with pause control method.
First, you’ll create a Boolean variable, canPause
, set that variable as a condition in your setPause
function, and then hardcode the setting of that variable to false
in a new function that can only be used to remove pause capabilities. Once the canPause
function is called, it can’t be reversed.
Here’s an example showing the global Boolean variable with pause control method in Solidity:
// A global variable to govern pause capabilities called canPause bool public canPause = true; // The setPaused function here is updated with a new require statement for canPause that ensures canPause is true before it executes function setPaused(bool _paused) public { require(msg.sender == owner, "You are not the owner"); require(canPause == true); paused = _paused; } // This function removes pause capability permanently by the contract owner. Ensure that this function is restricted in usage. function removePauseCapability() public { require(msg.sender == owner, "You are not the owner"); paused = false; canPause = false; emit removePause(); }
In this article, we reviewed three different methods for incorporating pause functionality into a Solidity smart contract. pause is just one tool in your Web3 developer’s arsenal.
Other strategies can be used to enhance the security of smart contracts, including using modifiers to restrict function access to a contract owner or preapproved list, adjusting the visibility setting for functions that can remove access from outside viewers (e.g. the use of internal or private modifiers on functions), and even adding the ability to withdraw all funds and destroy a contract completely. This last option permanently stops all contract functionality.
Although pause isn’t as extreme as some of these methods, it is still an important tool for managing contract security in Solidity. All developers should consider using pause, particularly for public-facing functions.
Before going live with your project, I recommend thinking through scenarios in which you might implement conditions like pause. What potential vulnerabilities exist in your contract? How might a malicious actor attack your app? A secure smart contract will save you many headaches down the road when your project takes off.
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app or site. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web and mobile apps — Start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.