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 variants32.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.