openzeppelin-contracts

June 30, 2026 · View on GitHub

This repository is a Solidity smart contract library: reusable abstract contract and library implementations meant to be inherited and extended by downstream projects. It is not a deployed product. Most conventions exist because code here is a base for others: functions must be overridable, argument types must not constrain inheritors, and the public API must not force a particular call context on child contracts.

Read GUIDELINES.md first — it is the human contributor spec and authoritative. This file is for what is specific to Claude-assisted work, plus the conventions not fully documented elsewhere.

Before preparing a contribution, also read CONTRIBUTING.md thoroughly. Non-trivial changes must be discussed in an issue before a PR is opened.

Repo map

PathPurpose
contracts/Library source. Subdirectory layout (token/ERC20/, access/, utils/, …) mirrors the public API.
contracts/mocks/Mocks used by tests. Mirrors contracts/. Manual mocks only — see the testing skill.
contracts/interfaces/ERC interfaces in their pristine spec form.
test/Hardhat + Chai tests (.test.js), shared behaviors (.behavior.js), and Foundry tests (.t.sol).
contracts-exposed/Auto-generated $ContractName wrappers from hardhat-exposed. Gitignored. Do not edit.
fv/Certora specs (fv/specs/*.{conf,spec}), harnesses, and make-applied patches.
scripts/generate/Procedurally generated .sol files (Arrays variants, Checkpoints, EnumerableSet, …). Source of truth is the template, not the generated file.
scripts/checks/CI checks: inheritance-ordering.js, pragma-validity.js, generation diff, storage layout.
docs/AsciiDoc sources used by the OZ documentation site. Per-module README.adoc files in contracts/**/ are also rendered.
.changeset/Per-PR changelog entries consumed by the release workflow.
audits/Historical third-party audit reports, one per release. Reference only.
.claude/skills/Skills specific to working in this repository, loaded by AI assistants per task. See CONTRIBUTING.md.

Commands

TaskCommand
Compilenpm run compile
Hardhat testsnpm test
Foundry tests (includes Halmos symbolic)forge test -vvv
Lint (JS + Sol)npm run lint / npm run lint:fix
Coveragenpm run coverage
Inheritance order checknpm run test:inheritance
Pragma validity checknpm run test:pragma
Generated-file freshness checknpm run test:generation
Regenerate procedural contractsnpm run generate
Add a changelog entrynpx changeset add
Run a single Certora specnode fv/run.js <SpecName> (apply harnesses first: make -C fv apply)

What linting via solhint already enforces (don't re-state in code review)

solhint-plugin-openzeppelin + scripts/solhint-custom/index.js enforce, on contracts/**/*.sol (mocks and tests are exempt):

  • State variables are private (constant/immutable are the only exceptions).
  • Underscore prefix matches visibility: private/internal state vars and functions get _; public/external do not. Library internal functions do NOT get _.
  • No external virtual (except fallback). The public default makes this redundant; flagged by no-external-virtual.
  • Libraries expose only internal/private.
  • Interfaces start with I, contracts use CapWords, events use CapWords, modifiers/params mixedCase.

If a lint rule already catches it, don't waste tokens explaining it — fix and move on.

Always-on conventions

These apply to every change and are not lint-enforced:

  • File header version line: don't add or edit the // OpenZeppelin Contracts (last updated vX.Y.Z) (path) line — release tooling (scripts/release/update-comment.js) maintains it automatically based on git diff between the previous tag and HEAD. The SPDX line is your only responsibility.
  • Pragma: don't pick the floor by hand. Run npm run pragma and let scripts/minimize-pragma.js walk the dependency graph, compile against every candidate solc, and write back the lowest version that works for each file (>= prefix for interfaces, ^ for implementations and libraries). Bumping the floor casually can introduce opcodes (mcopy in 0.8.24, etc.) not supported on all target chains — the script's output is the safe answer.
  • Imports: 100% named, curly-brace, relative paths within contracts/. No wildcards.
  • Inheritance order is globally consistent: if you change an is A, B, C list, run npm run compile && npm run test:inheritance before pushing.
  • Changesets: every PR that changes contract behavior needs one. Skip for NatSpec-only, internal refactors with no user-visible effect, or pure repo plumbing.
  • Procedurally generated files: never hand-edit a file whose header says "procedurally generated from <template>". Edit the template under scripts/generate/templates/ and run npm run generate. CI runs test:generation to enforce.
  • Backward compatibility: released contracts are inherited downstream and transpiled into the -upgradeable package. Don't change existing public/external signatures, event or error shapes, or the storage layout of existing contracts. Changes are additive; deprecate rather than delete. Breaking changes are a maintainer decision tied to a major release.
  • Dependencies: this library has no third-party dependencies that translate to the user, and nothing that could change behavior out from under it. Don't add an npm or Solidity dependency or a new external import without maintainer sign-off; reuse existing contracts/utils/ helpers first. Code that needs third-party dependencies belongs in openzeppelin-community-contracts, not here.

When to load which skill

Claude Code surfaces these from .claude/skills/ based on the task. You can also reference them directly:

  • library-api-design — when adding or modifying any contract in contracts/ (visibility, virtual, memory vs calldata, _update pattern, internal/external split, no-ops over reverts, hooks).
  • solidity-style — when writing or editing Solidity (errors, events, NatSpec, ERC-7201, assembly, unchecked, casting, immutables).
  • testing — when writing tests or mocks (hardhat-exposed $ wrappers, when manual mocks are warranted, .behavior.js reuse, multi-target test loops, Foundry fuzz, Halmos symbolic, Certora).
  • add-changeset — at PR close, or when the user asks for one.

Things to always verify manually

AI suggestions in these areas are often plausible-but-wrong. Read the surrounding code before accepting:

  • Cryptography — anything in contracts/utils/cryptography/, hashing, ECDSA, EIP-712 domains, BLS, signatures.
  • ERC-7201 slot derivations — recompute with SlotDerivation.erc7201Slot() and compare to the constant. The formula in the inline comment is mandatory.
  • Storage layout for transpiler-processed contracts — adding/removing/reordering fields breaks proxied deployments. CI runs storage-layout diff but read it.
  • delegatecall, inline assembly, memory safety — verify the memory-safe annotation is honest and stack/scratch usage is bounded.
  • unchecked invariants — re-derive the overflow-impossible claim for every code path. The inline comment is the contract.
  • _authorizeUpgrade access control — the base is empty by design; every override must restrict.
  • _disableInitializers() in implementation constructors for UUPS/Transparent proxy targets.
  • Pragma bumps — check that any new opcode the compiler will emit is supported on all target chains.

Out of scope for AI

  • Adding/renaming public API or deciding whether a feature belongs in the library — open an issue first.
  • Security assessments or audit opinions.
  • New ERC interfaces or standards without explicit direction.
  • Restructuring tests without a corresponding contract change.
  • Fabricated bug bounties and security rewards. Never claim or imply a reward, amount, or eligibility. The only existing program is Immunefi (See SECURITY.md).