James Bachini

OpenZeppelin Libraries | Solidity Tips & Examples

Solidity Libraries

OpenZeppelin libraries are popular for building secure smart contracts on top of pre-audited code in Solidity. The framework includes a number of reusable smart contract components, such as token contracts, access control contracts, and math libraries.

In this article we will explore the various Libraries available within the OpenZeppelin framework to give you a tool kit with which to build your smart contracts.

  1. How To Use OpenZeppelin Libraries
  2. ERC Token Standards
  3. Access Control
  4. Governance
  5. Upgradeability
  6. Security
  7. Utilities
  8. Conclusion

How To Use OpenZeppelin Libraries

If we are using https://remix.ethereum.org the OpenZeppelin libraries are already installed.

In hardhat we need to run the following command

npm install @openzeppelin/contracts

In Foundry we need to run:

forge install openzeppelin/openzeppelin-contracts

Within our solidity contract we can then use the import keyword to bring in a library. Note that this is done outside of the contract itself like in this example which demonstrates how easy it is to build an ERC20 token with OpenZeppelin libraries

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyToken is ERC20 {
    constructor() ERC20("MyToken", "MYTKN") {
        _mint(msg.sender, 1000000 ether);
    }
}

To get the correct address/URL for the library I generally just use their Github repository to find what I am looking for and edit the later part of the import URL.


ERC Token Standards

OpenZeppelin currently offers four different ERC token standard libraries

  • ERC20.sol – standard for non fungible tokens like governance tokens etc.
  • ERC777.sol – a newer fungible token standard that added some additional functionality to ERC20 like metadata but isn’t as widely used
  • ERC721.sol – the industry standard for single collection NFTs
  • ERC1155.sol – a more flexible NFT contract for things like in game items

Note there is a comparisom of ERC721 vs ERC1155 token standards here:

Note that for all the token libraries there are also interfaces you can import by adding a I to the contract name. Interfaces can be used to interact with external contracts.

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
IERC20(wethAddress).balanceOf(address(this));

Access Control

There are two main libraries relating to access control in the OpenZeppelin framework:

  • Ownable.sol – used to define and update the contract owner. Also adds the widely overused modifier onlyOwner. The ownable library allows you to restrict access to a function so that only the deployer of the contract or a specified address can call it.
    Note to self: A multi-sig wallet is not a decentralized solution
  • AccessControl.sol – This is a Solidity contract library that provides a lightweight implementation of a role-based access control mechanism. The library allows contracts to define roles, which are referred to by their bytes32 identifier, and then restrict access to certain functions using the hasRole function. Roles can be granted and revoked dynamically via the grantRole and revokeRole functions, with each role having an associated admin role. The DEFAULT_ADMIN_ROLE is the admin role for all roles, and only accounts with this role can grant or revoke other roles. However, extra precautions should be taken to secure accounts that have been granted the DEFAULT_ADMIN_ROLE, as it has permission to grant and revoke this role.

Governance

OpenZeppelin has developed a modular system of Governor contracts for on-chain governance that can be easily customized to fit the needs of different projects. This system allows for different requirements to be accommodated by writing small modules using Solidity inheritance, and the most common requirements are available out of the box.

OpenZeppelin’s Governor system was designed to make it easy to integrate with existing systems based on Compound’s Governor Systems.

Many modules are presented in two variants, one of which is built for compatibility with those systems. For example, the ERC20 extension to keep track of votes and vote delegation has a generic version and a “Comp” variant that fits the interface of the COMP token used by GovernorAlpha and Bravo.

There are also different Governor modules to choose from based on the choice of timelock being used. OpenZeppelin’s TimelockController or Compound’s Timelock can be used with the corresponding Governor module: GovernorTimelockControl or GovernorTimelockCompound.

Tally is a full-fledged application for user-owned on-chain governance that includes a voting dashboard, proposal creation wizard, real-time research and analysis, and educational content. The Governor will be compatible with Tally, allowing users to create proposals, visualize voting power and advocates, navigate proposals, and cast votes. For proposal creation, projects can also use Defender Admin as an alternative interface.


Upgradeability

On Ethereum smart contracts are immutable, meaning once they are deployed their code cannot be modified. However it can be beneficial to upgrade a smart contract without losing the state and functionality of the original contract.

Proxy contracts are smart contracts that delegate all functionality to an underlying implementation contract. Instead of executing the code in the proxy contract, the proxy forwards all method calls to the implementation contract. When an upgrade is needed, a new implementation contract is deployed, and the proxy contract is updated to delegate to the new implementation contract. This way, the state and functionality of the original contract are preserved while allowing for upgrades.

There are different types of proxy contracts, including:

  • Transparent Proxies these include the upgrade and admin logic in the proxy contract itself. This type of proxy is more expensive to deploy than other types of proxies, but it is straightforward to use.
  • UUPS Proxies these are lightweight and versatile, and they do not include upgrade and admin logic. Instead, the upgrade is handled by the implementation contract, which includes all the necessary code to update the implementation’s address stored at a specific slot in the proxy’s storage space.

To make an implementation contract upgradeable with a UUPS proxy, developers can use the OpenZeppelin UUPSUpgradeable contract, which provides a template for the implementation contract. Developers need to inherit from this contract and override the _authorizeUpgrade function with the relevant access control mechanism to make their contract UUPS compliant.

It is important to note that using upgradeable proxies correctly and securely requires deep knowledge of the proxy pattern, Solidity, and the EVM. Some of the beauty of immutable decentralized code is lost through the necessitation to have upgradeability.


Security

The two main contracts I use in the security sub-directory are:

  • Pausable.sol – enables the owner of the contract to pause execution on functions with the whenNotPaused modifier. The library doesn’t have public pause function so you need to create one using the _pause() and _unpause() internal functions with some kind of access control.
  • ReentrancyGuard.sol – helps prevent reentrancy attacks when a malicious contract calls a vulnerable contract multiple times in a single transaction, before the vulnerable contract has finished executing the previous call. This can lead to unexpected and potentially harmful behavior, as the state of the vulnerable contract may be modified in unexpected ways. The ReentrancyGuard contract provides a modifier called nonReentrant which can be applied to functions to make sure there are no nested (reentrant) calls to them. This modifier prevents a contract from calling itself, directly or indirectly, by checking a internal _status variable.

Utilities

There is a whole host of util functions in the OpenZeppelin frame (see screenshot below) but I’m just going to go through the ones I am most familiar with.

OpenZeppelin Util Libraries
  • Strings.sol – provides functions for working with strings in Solidity. It includes functions for concatenating strings, comparing strings, and converting strings to other data types.
  • Base64.sol – provides a set of functions to encode and decode data in Base64 format. Base64 encoding is a binary-to-text encoding scheme that represents binary data in an ASCII string format by translating it into a radix-64 representation. It is often used to represent data in situations where the underlying information is binary in nature, such as when transmitting images or other binary data over email or other text-based channels. Base64 is also compatible with NFT URIs such as in this example: https://github.com/jamesbachini/Solidity-Snippets/blob/main/contracts/ERC721.sol
  • Multicall.sol – used to batch together multiple external function calls in a single external call. This can save gas costs and simplify the interaction with smart contracts that have multiple external functions that need to be called. The function executes a batch of function calls on the contract and returns an array of the results of each function call.
  • cryptography/MerkleProof.sol – Merkle proofs allow devs to take a massive list of addresses and verify a user is part of that list in a efficient manner without storing the whole list on-chain.

Conclusion

Using OpenZeppelin libraries in Solidity is a great way to build more secure smart contracts with ease. The framework provides an expanding number of reusable components that can save you time and effort. The code has been used across the DeFi ecosystem and much of it has been security audited. OpenZeppelin code is widely used and trusted by the Ethereum community.



Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

Thank you.

James Bachini

Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.