OpenZeppelin Contracts

OpenZeppelin Contracts is the most widely used library of audited Solidity building blocks.

32.1 Overview

OpenZeppelin Contracts is the most widely used library of audited Solidity building blocks. Version 5.x (2023+) requires Solidity 0.8.20+, uses custom errors, namespaced storage for upgradeable variants, and an explicit initialOwner for Ownable.

Ownable, Ownable2Step, AccessControl, AccessControlDefaultAdminRules, AccessManager

ERC20 (+ ERC20Permit, ERC20Votes, ERC20Burnable, ERC20Capped, ERC20Pausable, ERC4626), ERC721 (+ ERC721Enumerable, ERC721URIStorage, ERC721Royalty), ERC1155, SafeERC20

ReentrancyGuard, ReentrancyGuardTransient (5.1+), Pausable

Governor + extensions, TimelockController, Votes ERC1967Proxy, TransparentUpgradeableProxy, ProxyAdmin, UUPSUpgradeable, BeaconProxy, UpgradeableBeacon, Clones

ECDSA, MessageHashUtils, EIP712, SignatureChecker, MerkleProof, P256

Address, Strings, Math, SafeCast, Multicall, Nonces, StorageSlot, EnumerableSet, EnumerableMap, BitMaps, Checkpoints

forge install OpenZeppelin/openzeppelin-contracts            # Foundry
npm install @openzeppelin/contracts                          # Hardhat / npm
npm install @openzeppelin/contracts-upgradeable              # upgradeable variants

32.2 A production-style token

In 5.x, _update(from, to, value) replaces the 4.x _beforeTokenTransfer/_afterTokenTransfer hooks. OpenZeppelin Wizard (wizard.openzeppelin.com) generates such contracts interactively.

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

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

contract GovToken is ERC20, ERC20Burnable, ERC20Pausable, ERC20Permit, AccessControl {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    uint256 public constant CAP = 100_000_000e18;

    error CapExceeded();

    constructor(address admin) ERC20("Gov Token", "GOV") ERC20Permit("Gov Token") {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(MINTER_ROLE, admin);
        _grantRole(PAUSER_ROLE, admin);
    }

    function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
        if (totalSupply() + amount > CAP) revert CapExceeded();
        _mint(to, amount);
    }

    function 
…

32.3 Using SafeERC20 and ReentrancyGuard

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

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract TokenEscrow is ReentrancyGuard {
    using SafeERC20 for IERC20;

    IERC20 public immutable token;
    mapping(address => uint256) public deposited;

    constructor(IERC20 _token) { token = _token; }

    function deposit(uint256 amount) external nonReentrant {
        uint256 before = token.balanceOf(address(this));
        token.safeTransferFrom(msg.sender, address(this), amount);
        uint256 received = token.balanceOf(address(this)) - before;   // handles fee-on-transfer tokens
        deposited[msg.sender] += received;
    }

    function withdraw(uint256 amount) external nonReentrant {
        deposited[msg.sender] -= amount;            // reverts on underflow (0.8 checked math)
        token.safeTransfer(msg.sender, amount);
    }
}

32.4 Merkle allowlist

Generate trees off-chain with @openzeppelin/merkle-tree (StandardMerkleTree.of(values, ["uint256", "address", "uint256"])).

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

import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol";

contract Airdrop {
    using BitMaps for BitMaps.BitMap;

    bytes32 public immutable merkleRoot;
    BitMaps.BitMap private claimed;

    event Claimed(uint256 index, address account, uint256 amount);

    constructor(bytes32 root) { merkleRoot = root; }

    function claim(uint256 index, address account, uint256 amount, bytes32[] calldata proof) external {
        require(!claimed.get(index), "already claimed");
        // Double-hash leaves (OZ StandardMerkleTree format) to avoid second-preimage attacks
        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(index, account, amount))));
        require(MerkleProof.verify(proof, merkleRoot, leaf), "invalid proof");
        claimed.set(index);
        emit Claimed(index, account, amount);
        // transfer tokens to `account` here
    }
}

32.5 Other libraries

Solady (gas-optimized, assembly-heavy), Solmate (minimal, gas-efficient; less maintained), forge-std (testing), PRBMath (fixed-point math), Uniswap v4-core / periphery, Chainlink contracts, Safe contracts.