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
| Path | Purpose |
|---|---|
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
| Task | Command |
|---|---|
| Compile | npm run compile |
| Hardhat tests | npm test |
| Foundry tests (includes Halmos symbolic) | forge test -vvv |
| Lint (JS + Sol) | npm run lint / npm run lint:fix |
| Coverage | npm run coverage |
| Inheritance order check | npm run test:inheritance |
| Pragma validity check | npm run test:pragma |
| Generated-file freshness check | npm run test:generation |
| Regenerate procedural contracts | npm run generate |
| Add a changelog entry | npx changeset add |
| Run a single Certora spec | node 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/immutableare the only exceptions). - Underscore prefix matches visibility:
private/internalstate vars and functions get_;public/externaldo not. Library internal functions do NOT get_. - No
external virtual(except fallback). Thepublicdefault makes this redundant; flagged byno-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 ongit diffbetween the previous tag andHEAD. The SPDX line is your only responsibility. - Pragma: don't pick the floor by hand. Run
npm run pragmaand letscripts/minimize-pragma.jswalk the dependency graph, compile against every candidatesolc, 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 (mcopyin 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, Clist, runnpm run compile && npm run test:inheritancebefore 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 underscripts/generate/templates/and runnpm run generate. CI runstest:generationto enforce. - Backward compatibility: released contracts are inherited downstream and transpiled into the
-upgradeablepackage. Don't change existingpublic/externalsignatures, 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 inopenzeppelin-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 incontracts/(visibility,virtual,memoryvscalldata,_updatepattern, 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.jsreuse, 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 thememory-safeannotation is honest and stack/scratch usage is bounded.uncheckedinvariants — re-derive the overflow-impossible claim for every code path. The inline comment is the contract._authorizeUpgradeaccess 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).