Securing Solidity Smart Contracts with Roles & Pausable

Erin Lim
6 min readMay 13, 2023

As we’d already know, everything on blockchains such as Ethereum, Polygon, BNB Chain & quite a few others, are public, observable & the smart contracts interactable by just any Tom, Dick & Harry.

That brings us to the question of:

  • How can I secure my smart contracts if literally anyone can interact with it?
  • How can I prevent malicious actors from exploiting my smart contracts?

The solution is with Roles & Pausable.

In this article, I’m going to show you how can secure your smart contracts with Roles & Pausable by OpenZeppelin Contracts. OpenZeppelin Contracts is a library for secure smart contract development, it has implementation of standards such as the ERC-20 & ERC-721, most importantly, it has been audited by various security firms.

Why is it so important to have a secure smart contract?

Let’s imagine that you have a smart contract for a collection of NFTs which you plan to market & sell to collectors. The images to this collection of NFTs will remain hidden until much later, to keep collectors from guessing which NFTs are worth more in terms of their rarity & whatnot.

You plan to reveal the images yourself in a month time through the NFT’s smart contract but before you were able to do that, someone has found out that your smart contract is not secure & has found a way to reveal your NFTs, now everyone can see the actual images of the NFTs which caused the collectors to buy only the rarest NFTs from your collection while the others are just sitting there, collecting dust, dropping in value.

This is just one of the many ways people can exploit an unsecure smart contract.

So how can we secure a smart contract?

As mentioned before, we’re going to use the OpenZeppelin Contracts library with our smart contract, so let’s create a .sol file.
(You can do that with your preferred IDE or just use Remix Online IDE that lets you deploy smart contracts as well.)

We’re going to create our smart contract with the ERC-1155 standard. Let’s start with a base for the contract…

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

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/Pausable.sol";

contract AccessControlWithRoles is ERC1155, AccessControl, Pausable {
constructor() ERC1155("default") {}
}

We’re importing the ERC1155, AccessControl & Pausable contracts from the OpenZeppelin library.

  • ERC1155 comes with functions such as Batch Transfers & Safe Transfers
  • AccessControl allows you to assign & revoke roles to wallet addresses, which will then grants them permission to run certain functions within a smart contract.
  • Pausable allows you to disable functions within a smart contract when the contract is paused.

Now let’s add a few more lines to our smart contract…

...

contract AccessControlWithRoles is ERC1155, AccessControl, Pausable {
bytes32 public constant SECOND_ADMIN_ROLE = keccak256("SECOND_ADMIN_ROLE");

uint256 private defAdminCount = 0;
uint256 private secAdminCount = 0;
uint256 private mintsLeft = 1000;

constructor(
address _defAdmin,
address _secAdmin
) ERC1155("default") {
_setupRole(DEFAULT_ADMIN_ROLE, _defAdmin);
_setupRole(SECOND_ADMIN_ROLE, _secAdmin);
}
}

By default, AccessControl comes with a default admin named DEFAULT_ADMIN_ROLE, if you need more than one admin or role, then you will have to use keccak256() to create a new one.

We’ve added a few private variables which we will do something to them later. We’re also assigning admin roles to the two wallet addresses parsed into our constructor.

Let us add some write functions below the constructor method…

...

// Allows Default Admin to pause the contract
function pause() public onlyRole(DEFAULT_ADMIN_ROLE) {
_pause();
}

// Allows Default Admin to unpause the contract
function unpause() public onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}

// Increments defAdminCount's count
function incDefAdminCount() public {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Unauthorized: You do not have the DEFAULT_ADMIN_ROLE");
defAdminCount++;
}

// Increments secAdminCount's count
function incSecAdminCount() public {
require(hasRole(SECOND_ADMIN_ROLE, msg.sender), "Unauthorized: You do not have the SECOND_ADMIN_ROLE");
secAdminCount++;
}

// Decrements mintsLeft
function mintNft() public whenNotPaused {
require(mintsLeft > 0, "Error: No more mints left");
mintsLeft--;
}

...
  • pause() allows addresses with DEFAULT_ADMIN_ROLE to pause the contract
  • unpause() allows addresses with DEFAULT_ADMIN_ROLE to unpause the contract
  • incDefAdminCount() increments the count of defAdminCount only if address has the role of DEFAULT_ADMIN_ROLE
  • incSecAdminCount() increments the count of secAdminCount only if address has the role of SECOND_ADMIN_ROLE
  • mintNft() allows any address to call it, as long as the contract is not paused & mintsLeft is more than 0.

Now let’s add some read functions so we can read our variables…

...

// Returns defAdminCount when called
function getDefAdminCount() public view returns (uint256) {
return defAdminCount;
}

// Returns secAdminCount when called
function getSecAdminCount() public view returns (uint256) {
return secAdminCount;
}

// Returns mintsLeft when called
function getMintsLeft() public view returns (uint256) {
return mintsLeft;
}

// Override required by Solidity
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC1155, AccessControl)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
  • getDefAdminCount() returns defAdminCount
  • getSecAdminCount() returns secAdminCount
  • getMintsLeft() returns mintsLeft
  • supportsInterface() is an override function for “Derived contract must override function “supportsInterface”.” error that will occur because of the AccessControl contract.

And that is our smart contract, simple yet concise enough to show you what we can do with AccessControl & Pausable.

Deploy contract with Remix Online IDE

Time for us to deploy our smart contract to a network.
For this tutorial’s purposes, we’re going to deploy our contract to the Polygon Testnet, Mumbai. If you need some MATIC for testing, you can get some from Alchemy’s Mumbai Faucet.

After creating a .sol file on Remix, you need to first flatten the file before you can deploy it.

Right-click on your Solidity file, then select “Flatten”.

Once flattened, you may deploy it.

Select “Injected Provider — MetaMask” to connect to your MetaMask wallet. Paste your wallet addresses into the fields, then click “transact” to deploy.

A MetaMask popup would appear asking you to confirm your contract deployment.

Once deployed, you should be able to see your contract address under the Deployed Contracts tab, copy the address so you can search for your contract on Mumbai Polygonscan.

Verify & Publish contract

Before we’re able to read our smart contract on Polygonscan, we need to first verify & publish it.

Click on “Verify and Publish” to publish your contract

Paste flattened code in the box then verify. Upon successful verification, you should see a link to view your contract, click on it.

Then under “Contract”, click on “Read Contract”.
Click on getDefAdminCount, getSecAdminCount & getMintsLeft to see each of their value. They should be 0, 0 & 1000 respectively.

Write contract

Click on “Write Contract” then click on “Connect to Web3” to connect to your wallet so you can start calling your contract functions.

Click “Write” to call the functions. If your wallet has the correct role then the transactions should be successful.
Your transactions would fail if you do not have the correct role.
You should see the value changes under “Read Contract” if your transactions were successful.

That was for us to test addresses with different roles, now let’s see what happens when our contract is paused.

Go back to “Write Contract” & click “Write” to the mintNFT function.

Because the contract is not yet paused, the transaction should be a success & getMintsLeft should decrement by 1.

Now let us pause the contract by running the pause function under “Write Contract” then try to call mintNFT function again.

Your mintNFT transactions should fail until your default admin address unpauses the contract again.

Final Thoughts

So there you have it. That is how you can better secure your smart contracts with Roles & Pausable.

  • With Roles, you can be sure that only people with the right permissions are allowed to interact with your smart contracts.
  • With Pausable, you can pause your smart contracts at anytime to stop people from further exploiting a loophole found in your smart contracts.

Security has always been my biggest concern whilst working with smart contracts, hopefully these insights will help you write better smart contracts in the future.

If you need the source code, you can find them here.

Thank you for reading. Till next time! 👋

--

--

Erin Lim

Photographer turned Full Stack Developer. Tech nerd, loves music, gaming & memes..