protocols/openzeppelin-patterns

OpenZeppelin Contract Patterns

ethereumguide🏛️ Officialconfidence highhealth 100%
v1.0.0·Updated 3/20/2026

Installation

npm install @openzeppelin/contracts
npm install @openzeppelin/contracts-upgradeable  # Upgradeable version

ERC-20 Token

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

contract MyToken is ERC20, Ownable {
  constructor(uint256 initialSupply) ERC20("MyToken", "MTK") Ownable(msg.sender) {
    _mint(msg.sender, initialSupply * 10 ** decimals());
  }
  function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); }
}

ERC-721 NFT

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract MyNFT is ERC721URIStorage, Ownable {
  using Counters for Counters.Counter;
  Counters.Counter private _tokenIds;
  
  function mint(address to, string memory tokenURI) public onlyOwner returns (uint256) {
    _tokenIds.increment();
    uint256 newId = _tokenIds.current();
    _safeMint(to, newId);
    _setTokenURI(newId, tokenURI);
    return newId;
  }
}

Access Control (Multi-Role)

import "@openzeppelin/contracts/access/AccessControl.sol";

contract MyContract is AccessControl {
  bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
  bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
  
  constructor() {
    _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    _grantRole(MINTER_ROLE, msg.sender);
  }
  
  function mint(address to) public onlyRole(MINTER_ROLE) { ... }
}

Upgradeable Contracts (UUPS)

import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract MyContractV1 is UUPSUpgradeable, OwnableUpgradeable {
  function initialize() public initializer {
    __Ownable_init(msg.sender);
    __UUPSUpgradeable_init();
  }
  function _authorizeUpgrade(address) internal override onlyOwner {}
}
// Deploy: upgrades.deployProxy(MyContractV1, [], { kind: 'uups' })
// Upgrade: upgrades.upgradeProxy(proxyAddress, MyContractV2)

ReentrancyGuard (Reentrancy Protection)

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract SafeVault is ReentrancyGuard {
  function withdraw(uint256 amount) external nonReentrant {
    // CEI pattern: Check → Effect → Interact
    require(balances[msg.sender] >= amount);
    balances[msg.sender] -= amount;  // Effect: modify state first
    payable(msg.sender).transfer(amount);  // Interact last
  }
}

Pausable

import "@openzeppelin/contracts/utils/Pausable.sol";

contract MyContract is Pausable, Ownable {
  function pause() public onlyOwner { _pause(); }
  function unpause() public onlyOwner { _unpause(); }
  function transfer() public whenNotPaused { ... }
}

Common Pitfalls

  • Upgradeable contracts cannot have a constructor, use initialize() instead with the initializer modifier
  • Upgradeable contracts cannot change storage layout, only append new variables
  • Use _disableInitializers() to prevent implementation contracts from being initialized directly
  • AccessControl is more flexible than Ownable, prefer it for multi-person team projects