agentleFS
OpenZeppelinOpenZeppelin/openzeppelin-contracts-upgradeable/.claude/skills/solidity-style/SKILL.md

solidity-style

Skill1.2k starsChanged 24 days agoMITView on GitHub

Solidity style and safety conventions for contract source files. Use when writing or editing any .sol file under contracts/. Covers errors, events, NatSpec, imports, file structure, assembly, unchecked, casting, numeric literal formatting, and inheritance ordering.

1 other project uses a version of this.

Trending

More talk lately.about the whole repository

Dec 2025: 0 mentionsJan 2026: 0 mentionsFeb 2026: 0 mentionsMar 2026: 2 mentionsApr 2026: 2 mentionsMay 2026: 0 mentionsJun 2026: 3 mentionsJul 2026: 1 mentionAug 2026: 0 mentionsSep 2026: 2 mentionsOct 2026: 4 mentionsNov 2026: 6 mentions
Dec 2025Jun 2026Nov 2026
Mentions a month of the OpenZeppelin/openzeppelin-contracts-upgradeable repository that our weekly crawl found. There is no adoption data for this file yet.

Most recently taken up by

Who's talking about OpenZeppelin/openzeppelin-contracts-upgradeable

The biggest 5 of 51 mentions linking to the repository that our crawl found. Each line is their own headline.

See all 51

Talked about on GitHub and Stack Overflow (51 times).

On GitHub · 50

45 more on GitHub
  • Other versions1 project uses a version of this.

    Word for word · 1

    1. OpenZeppelin/openzeppelin-contracts★ 27k100% same

    Edited · 0

    None found.

    Wrote their own version · 0

    None found.

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository4 other files it gives its agents.

The file

We split this skill into the rules it gives an agent

10 rules. About 0 are common, 3 are distinctive, and 7 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Prefer require(condition, CustomError(args)).

    line 12
  2. Distinctive:

    Document public and internal functions, events, errors, and constructors.

    line 43
  3. Very specific:

    Leave a blank line between the SPDX/last updated header and pragma.

    line 71
  4. Very specific:

    Don't name return values unless their meaning isn't obvious or there are multiple.

    line 79
  5. Very specific:

    Only mandatory ERC elements go in the base interface.

    line 86
  6. Distinctive:

    Don't pick the pragma floor by hand.

    line 92
  7. Very specific:

    Always annotate assembly ("memory-safe").

    line 106
  8. Very specific:

    Only use unchecked with an inline invariant comment that names the bound.

    line 117
  9. Distinctive:

    When reviewing, re-derive the bound for every code path that can reach the block.

    line 126
  10. Very specific:

    If the file header says // This file was procedurally generated from …, do not edit it.

    line 142

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Solidity style and safety

The conventions below are not enforced by solhint. For the lint-enforced rules (private state vars, underscore prefixes, library visibility, etc.), see the project root CLAUDE.md.

Errors

Prefer require(condition, CustomError(args)); in files whose pragma is ^0.8.27 or later (the first version supporting it in the legacy pipeline). Keep if (!condition) revert CustomError(args); when:

  • The file's pragma is lower. Do not bump pragma just to use require.
  • An error argument is anything other than a variable, constant, literal or cast of those. require evaluates its arguments even when the condition holds, so a function call, storage or member read costs gas on the success path, and checked arithmetic (e.g. revert Exceeded(value - max)) can panic instead of passing.
  • The check is part of an if/else if chain.

When a condition compares against a literal or constant bound, prefer the strict operator: require(x < 0x5fd4, ...) rather than require(x <= 0x5fd3, ...). The EVM has LT/GT but no LTE/GTE, so <= compiles to ISZERO(GT(...)). Don't rewrite a comparison against a variable bound this way: x < max + 1 adds arithmetic that overflows at type(uint256).max.

Custom errors follow ERC-6093:

  • Domain prefix: ERC<number> if the error violates an ERC spec; otherwise the component name (Governor, ECDSA, Timelock).
  • Include offending values as arguments so callers can decode the failure context.
  • Never declare the same error name twice across the library — duplicates cause identifier conflicts when inheriting from multiple contracts.

Location priority:

  1. Reuse the error from the underlying ERC if already defined there.
  2. Otherwise declare in the interface or library that owns the concept.
  3. Otherwise declare in the implementation (when the interface/library is already pinned by an ERC).
  4. Otherwise declare in the extension where it actually fires.

Events

Emit after the state change. Past-tense CapWords names (OwnershipTransferred, RoleGranted). ERC-standard events use the tense specified in the ERC (e.g. ERC-20's Transfer is present tense — follow the spec). Declare events in the interface or at the top of the emitting contract.

NatSpec

@dev is the only tag rendered by the OZ documentation engine. Use it as the primary tag everywhere. @notice, @param, @return appear in interface files where ERC specs are kept verbatim — do not add @notice to implementation contracts; it will not appear in generated docs.

  • Single-line: /// @dev …
  • Multi-line: /** @dev … */
  • Document public and internal functions, events, errors, and constructors. Private functions are optional — they don't appear on the docs site.

Overrides:

  • Pure relay with nothing to add: /// @inheritdoc InterfaceName
  • Override with added requirements or caveats: /** @dev See {Interface-functionName}. \n\n Requirements: … */

Cross-references: {ContractName-functionName} — e.g. {IERC20-transfer}, {_update}.

Section headers inside @dev blocks: Requirements:, NOTE:, WARNING:, IMPORTANT:. Frame caveats as conserved properties of the contract, not as alarms about safe behaviors.

Imports and file structure

100% named curly-brace imports. Relative paths within contracts/. No wildcards:

import { IERC20, IERC20Metadata } from '../../interfaces/IERC20.sol';

File order:

  1. // SPDX-License-Identifier: MIT
  2. // OpenZeppelin Contracts (last updated vX.Y.Z) (path/to/file.sol) — release-managed; do not add or edit by hand. scripts/release/update-comment.js rewrites this line at release time based on which contracts/**/*.sol files changed since the previous tag. New files: write only the SPDX line; the release script appends the second line on first inclusion.
  3. pragma solidity …
  4. Imports
  5. Contract-level NatSpec
  6. Contract declaration

Leave a blank line between the SPDX/last updated header and pragma.

Inside the body: using → structs/enums → constants → state vars → events → errors → modifiers → constructor → external/public functions → internal → private.

Contracts not meant to be deployed on their own (bases, extensions, mixins) are declared abstract so they can only be used by inheriting from them.

Return values

Don't name return values unless their meaning isn't obvious or there are multiple. Prefer returns (uint256) over returns (uint256 amount); name them when it aids clarity, e.g. returns (bool isMember, uint32 currentDelay).

Interfaces

  • ERC-defined behavior MUST have an interface, and it SHOULD live in its own file. The name starts with I; add the ERC number when there's a risk of clash (IModule → IERC7579Module).
  • ERC interfaces match the spec as closely as possible (including parameter and return names) and MUST NOT declare anything not in the spec. Non-spec errors, structs, and events go in the implementing contract or library instead.
  • In interfaces, functions are external and non-value-type parameters are calldata — this is the spec form. Implementations use public/memory (see the library-api-design skill); the two intentionally differ.
  • Only mandatory ERC elements go in the base interface; optional ones go in an extension interface (name() is in IERC20Metadata, not IERC20).
  • Placement: contracts/interfaces/ for ERC interfaces (draft- filename prefix if the ERC is not yet final), next to the implementation otherwise.
  • Interface files SHOULD NOT import anything that is not itself an interface file.

Pragma

Don't pick the pragma floor by hand. Run:

npm run pragma

scripts/minimize-pragma.js builds a dependency graph from compiled artifacts, compiles each file against every candidate solc, and writes back the lowest version that compiles for that file and is compatible with all of its dependents. The script picks the syntax for you (>= for interfaces, ^ for implementations and libraries) and respects the minVersionForContracts floor (default 0.8.20).

When adding a new file, start with the same pragma as a similar existing file in the same directory; the minimizer will tighten it on the next run. When editing a file, run the minimizer if you've used a feature that may have raised the floor.

The CI test:pragma job (npm run test:pragma) verifies the floor is honest about the opcodes the contract emits. Raising the floor without checking opcode availability on target chains (mcopy from 0.8.24 is the example) is the failure mode this guards against.

Assembly

Always annotate assembly ("memory-safe"). Omitting the annotation disables the memory optimizer and increases gas. Include inline comments for any non-trivial operation.

Numeric literals inside assembly:

  • Memory-related (locations, offsets, lengths): hexadecimal. mload(0x40), mstore(add(ptr, 0x20), value), keccak256(ptr, 0x55).
  • Bit operations (shift amounts, bit counts): decimal. shl(128, value), sar(96, x).
  • Trivially small values like 1 or 2 may use decimal even in memory contexts: ptr := add(ptr, 1).
  • In call/staticcall/delegatecall, decimal zero is acceptable when both location and length are zero.

unchecked

Only use unchecked with an inline invariant comment that names the bound. The comment may be omitted only when the reason is immediately apparent from the line directly above:

unchecked {
    // Overflow not possible: value <= fromBalance <= totalSupply.
    _balances[from] = fromBalance - value;
}

When reviewing, re-derive the bound for every code path that can reach the block.

Casting

Any narrowing integer cast (uint256 → uint48, etc.) must use SafeCast.toUintXX() (or the appropriate toIntXX). Direct Solidity casts are only permitted after an explicit bounds check on a line you can point to.

Inheritance ordering

The relative order of bases in any is A, B, C list must be globally consistent. CI runs npm run test:inheritance against compiled artifacts to detect conflicts. After any change to an is list:

npm run compile && npm run test:inheritance

Procedurally generated contracts

If the file header says // This file was procedurally generated from …, do not edit it. Edit the template in scripts/generate/templates/ and run npm run generate. CI runs npm run test:generation to enforce this.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.