$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

CLAUDE.md for Solidity and Web3 Projects

Smart contract bugs are permanent and expensive. AI rules for reentrancy prevention, gas optimization, access control, and audit-ready Solidity.

8 min read·April 11, 2025

Smart contract bugs are permanent — there's no hotfix on the blockchain

Reentrancy prevention, access control, gas optimization, and audit readiness

Why Solidity Rules Are Life-or-Death for Your Contract

Smart contracts are the only software where bugs are literally permanent. Once deployed to a blockchain, the code can't be patched. A reentrancy vulnerability, an integer overflow, or a missing access control check means funds are stolen — and there's no rollback, no hotfix, no incident response. The DAO hack lost $60 million from a single reentrancy bug.

AI assistants generate Solidity that compiles and passes basic tests but contains vulnerabilities that only an experienced auditor would catch. The AI doesn't think about: reentrancy across multiple functions, gas griefing attacks, front-running MEV, or the subtle differences between call, transfer, and send. Without rules, the AI generates code that works in testing and gets exploited in production.

These rules won't replace a professional audit — but they prevent the most common vulnerabilities that AI introduces, making your code audit-ready instead of audit-hostile.

Rule 1: Checks-Effects-Interactions Pattern

The rule: 'Every function that transfers value or calls external contracts must follow the Checks-Effects-Interactions (CEI) pattern: 1) Check conditions (require/if), 2) Update state (effects), 3) Make external calls (interactions). Never update state after an external call — this is the reentrancy vulnerability pattern. Use OpenZeppelin's ReentrancyGuard on all functions that transfer value.'

For reentrancy specifically: 'Use the nonReentrant modifier from OpenZeppelin on every function that: sends ETH, calls an external contract, or modifies balances. Use pull-over-push for payments: instead of sending ETH to recipients, let them withdraw. This eliminates the most common reentrancy attack vector.'

The AI frequently generates code that updates a balance after sending ETH — the exact pattern that enables reentrancy. CEI + nonReentrant eliminates this entire vulnerability class.

⚠️ The DAO Lost $60M

The DAO hack exploited a single reentrancy bug — state updated after an external call. CEI pattern (checks → effects → interactions) + nonReentrant modifier eliminates this entire vulnerability class.

Rule 2: Access Control

The rule: 'Every external/public function that modifies state must have explicit access control. Use OpenZeppelin's AccessControl or Ownable — never roll custom access control. Mark functions that should only be called by the owner with onlyOwner. Mark admin functions with role-based access: onlyRole(ADMIN_ROLE). Functions without access control are callable by anyone — including attackers.'

For visibility: 'Use the most restrictive visibility possible. Internal functions are private by default — only make them public if external access is needed. Use private for helper functions. Use internal for functions used by derived contracts. Use external instead of public for functions only called from outside the contract (saves gas).'

AI assistants frequently generate public functions without access control — meaning anyone can call them. A mint function without onlyOwner means anyone can mint tokens. A withdraw function without access control means anyone can drain the contract.

  • OpenZeppelin AccessControl or Ownable — never custom access control
  • Every state-modifying function has explicit access control
  • external over public for functions called from outside (gas savings)
  • private by default — internal for inheritance, public only when necessary
  • Declare roles as constants: bytes32 public constant ADMIN_ROLE = keccak256('ADMIN');

Rule 3: Gas Optimization

The rule: 'Optimize for gas — every operation costs real money. Use uint256 instead of smaller integers (EVM operates on 256-bit words). Use calldata instead of memory for read-only function parameters. Use events for data that doesn't need on-chain access. Use mappings instead of arrays for lookups. Pack struct variables to minimize storage slots.'

For storage: 'Storage writes are the most expensive operation (20,000 gas for a new slot). Minimize storage writes: use memory for intermediate calculations, write to storage once at the end. Use immutable for values set in the constructor. Use constant for compile-time constants. Cache storage reads in local variables: uint256 bal = balances[msg.sender].'

For loops: 'Never iterate over unbounded arrays in transactions — gas limit will be exceeded. Use mappings for O(1) lookups. If iteration is necessary, limit batch sizes. Use unchecked { i++; } in for loops where overflow is impossible (Solidity 0.8+ checks overflow by default, which costs gas in loops).'

💡 Storage Is Expensive

A storage write costs 20,000 gas. Cache storage reads in local variables, compute in memory, write once at the end. The difference between an expensive and a cheap contract is often just caching strategy.

Rule 4: Use OpenZeppelin, Don't Reinvent

The rule: 'Use OpenZeppelin contracts for all standard patterns: ERC20 (token), ERC721 (NFT), ERC1155 (multi-token), AccessControl (roles), ReentrancyGuard, Pausable, and Upgradeable proxies. Never implement these standards from scratch — OpenZeppelin's implementations are audited, gas-optimized, and battle-tested. Pin the OpenZeppelin version in your dependencies.'

For upgradeable contracts: 'Use OpenZeppelin's upgradeable contract variants (Initializable, UUPSUpgradeable). Use initializer instead of constructor. Never use selfdestruct (deprecated). Store upgrade logic in a separate proxy admin contract. Test upgrade paths: deploy v1, upgrade to v2, verify state preservation.'

AI assistants sometimes generate custom implementations of ERC20 or access control — this is always wrong. Custom implementations of audited standards introduce vulnerabilities that the audited versions have already eliminated.

ℹ️ Never Reinvent Standards

OpenZeppelin's ERC20, ERC721, and AccessControl are audited and battle-tested. Custom implementations of audited standards introduce vulnerabilities the audited versions already eliminated.

Rule 5: Testing and Audit Readiness

The rule: 'Use Foundry (forge) for testing — it's the fastest and most expressive Solidity testing framework. Write fuzz tests for all functions that accept user input. Write invariant tests for properties that must always hold (total supply equals sum of balances). Test all access control: verify that unauthorized callers revert. Test edge cases: zero amounts, max uint256, empty arrays, reentrancy attempts.'

For coverage: 'Aim for 100% line coverage on all contract code. Use forge coverage to measure. Uncovered lines are untested attack surface. For complex contracts, use symbolic execution (Halmos, Kontrol) to verify properties mathematically.'

For audit readiness: 'Document every function with NatSpec comments: @notice for user-facing description, @dev for developer notes, @param for parameters, @return for return values. Write a SECURITY.md describing the threat model, trust assumptions, and known limitations. Run Slither static analysis in CI — zero findings before audit.'

  • Foundry (forge) for testing — fuzz tests for all user inputs
  • Invariant tests: total supply = sum of balances, access control holds
  • 100% line coverage — uncovered lines are untested attack surface
  • Slither static analysis in CI — zero findings before audit
  • NatSpec on all functions — SECURITY.md with threat model
  • Test reentrancy, access control, overflow, and zero-value edge cases

Complete Solidity Rules Template

Consolidated rules for Solidity smart contract projects.

  • Checks-Effects-Interactions pattern on all functions with external calls
  • ReentrancyGuard on all value-transferring functions — pull-over-push payments
  • OpenZeppelin for all standards: ERC20, ERC721, AccessControl, Pausable
  • Access control on every state-modifying function — never public without restriction
  • Gas: uint256, calldata for reads, events for off-chain data, pack structs
  • Cache storage reads — minimize storage writes — unchecked loops when safe
  • Foundry: fuzz tests, invariant tests, 100% coverage, Slither in CI
  • NatSpec on all functions — pin Solidity and OpenZeppelin versions