OpenZeppelin
OpenZeppelin Contracts is the audited Solidity library every serious dapp builds on. Use its ERC-20, ERC-721, AccessControl, Pausable, ReentrancyGuard, and proxy patterns instead of hand-rolling primitives. Hand-rolled crypto economics are bug factories; OpenZeppelin is the floor.
A safe ERC-20 with roles, pause, and upgradeable proxy
EXAMPLE
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
// ============================================================
// 1) A safe ERC-20 with mint, burn, pause, and role gating
// ============================================================
import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import { ERC20Burnable } from '@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol';
import { ERC20Pausable } from '@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol';
import { AccessControl } from '@openzeppelin/contracts/access/AccessControl.sol';
contract ShopToken is ERC20, ERC20Burnable, ERC20Pausable, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE');
bytes32 public constant PAUSER_ROLE = keccak256('PAUSER_ROLE');
constructor(address admin) ERC20('Shop Token', 'SHOP') {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MINTER_ROLE, admin);
_grantRole(PAUSER_ROLE, admin);
}
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
function pause() external onlyRole(PAUSER_ROLE) { _pause(); }
function unpause() external onlyRole(PAUSER_ROLE) { _unpause(); }
// Required override — both bases hook _update
function _update(address from, address to, uint256 value)
internal override(ERC20, ERC20Pausable) {
super._update(from, to, value);
}
}
// ============================================================
// 2) Reentrancy guard — for ANY function that calls external
// ============================================================
import { ReentrancyGuard } from '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
contract Vault is ReentrancyGuard {
mapping(address => uint256) public balanceOf;
function deposit() external payable {
balanceOf[msg.sender] += msg.value;
}
// nonReentrant blocks the classic re-entrancy attack
function withdraw(uint256 amount) external nonReentrant {
require(balanceOf[msg.sender] >= amount, 'insufficient');
balanceOf[msg.sender] -= amount; // EFFECTS before INTERACTION
(bool ok, ) = msg.sender.call{ value: amount }('');
require(ok, 'send failed');
}
}
// ============================================================
// 3) Upgradeable contract via the UUPS proxy pattern
// ============================================================
// npm i @openzeppelin/contracts-upgradeable
import { Initializable } from '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
import { UUPSUpgradeable } from '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol';
import { OwnableUpgradeable } from '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
contract Registry is Initializable, OwnableUpgradeable, UUPSUpgradeable {
mapping(bytes32 => address) public addrs;
// Use initialize instead of constructor — proxies have no constructor state.
function initialize(address admin) public initializer {
__Ownable_init(admin);
__UUPSUpgradeable_init();
}
function set(bytes32 key, address value) external onlyOwner {
addrs[key] = value;
}
function _authorizeUpgrade(address) internal override onlyOwner {}
}
// Deploy with the OpenZeppelin Upgrades plugin (Hardhat / Foundry):
// const r = await upgrades.deployProxy(Registry, [admin.address], { kind: 'uups' });
// later:
// const r2 = await upgrades.upgradeProxy(r.address, RegistryV2);
// ============================================================
// 4) Patterns that come with OZ for free
// ============================================================
// SafeERC20 wraps non-conforming ERC-20s (USDT, etc.) so safeTransfer
// reverts on failure instead of returning false
// MerkleProof Merkle airdrop / allowlist verification
// EIP712 structured signatures (gasless approvals, permit)
// AccessControl role-based admin, optional admin-of-admin chains
// TimelockController N-of-M signers + min delay for sensitive ops
// ============================================================
// 5) Audit + governance pattern
// ============================================================
// - Deploy via a multisig (Gnosis Safe) as admin
// - Wrap admin actions in a Timelock with > 24 hour delay
// - Public bounty program before adding new roles to the contract
// - Verify source on Etherscan / Sourcify so users can read it
Why it matters
Default to OpenZeppelin for every primitive — ERC-20, ERC-721, AccessControl, ReentrancyGuard, UUPS proxies. They have been audited, fuzzed, and battle-tested across billions of dollars of TVL. The mythical performance or "elegance" win of a hand-rolled version is never worth the audit budget you skip; ship boring, audited primitives and put creativity into the unique parts of your protocol.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Battle-tested implementations: ERC-20/721/1155, AccessControl, Ownable, // ReentrancyGuard, PausableUpgradeable, ERC-2535 diamonds. // Default to these instead of writing your own primitives.Try it Yourself »
Discussion
Loading…