nirholas

solidity-best-practices

Guide for writing secure, gas-efficient, and maintainable Solidity smart contracts following established patterns and avoiding common pitfalls.

nirholas 247 69 Actualitzat fa 5 mesos
GitHub

Instal·la

npx skillscat add nirholas/three-ws/solidity-best-practices

Instal·la des del registre de SkillsCat.

SKILL.md

Solidity Best Practices

When to use this skill

Use when the user asks about:

  • Writing or reviewing Solidity code
  • Smart contract design patterns
  • Gas optimization techniques in Solidity
  • Security best practices for contract development
  • Structuring a Solidity project

Development Guidelines

1. Project Structure

Recommended contract organization:

  • contracts/ — main contract source files
  • contracts/interfaces/ — interface definitions (IProtocol.sol)
  • contracts/libraries/ — shared library code
  • contracts/utils/ — utility contracts (access control, pausable, etc.)
  • test/ — test files mirroring the contracts directory structure
  • script/ — deployment and interaction scripts

2. Coding Standards

Follow these Solidity style conventions:

  • Use Solidity 0.8.x or later (built-in overflow checks)
  • Specify exact compiler version with pragma solidity 0.8.24; (not floating ^)
  • Order contract elements: state variables, events, errors, modifiers, constructor, external, public, internal, private functions
  • Use custom errors instead of require strings (saves gas): error InsufficientBalance(uint256 available, uint256 required);
  • Use immutable for variables set once in the constructor
  • Use constant for compile-time known values
  • Prefix internal/private functions with underscore: _validateInput()
  • Use NatSpec comments for all public/external functions

3. Gas Optimization Patterns

High-impact optimizations:

  • Storage packing: Group variables smaller than 32 bytes together in a single storage slot (e.g., uint128 a; uint128 b; uses one slot vs two for uint256)
  • Calldata over memory: Use calldata for external function array/struct parameters that are read-only
  • Caching storage reads: Read a storage variable into a local variable if accessed multiple times in a function
  • Short-circuit conditions: Place cheap checks before expensive ones in require chains
  • Unchecked math: Use unchecked {} for arithmetic that provably cannot overflow (e.g., loop counters)
  • Avoid zero-to-nonzero writes: Initializing a storage slot from 0 costs 20,000 gas vs 5,000 for nonzero-to-nonzero
  • Use mappings over arrays when you don't need iteration — mappings have O(1) access
  • Batch operations: Combine multiple operations into single transactions where possible

4. Security Patterns

Essential security practices:

  • Checks-Effects-Interactions (CEI): Validate inputs, update state, then make external calls — prevents reentrancy
  • Reentrancy guard: Use OpenZeppelin's ReentrancyGuard for functions making external calls with value
  • Access control: Use role-based access (OpenZeppelin AccessControl) rather than single-owner patterns
  • Pull over push: Let users withdraw funds rather than pushing payments — avoids DoS via revert
  • Timelocks: Add delay to sensitive admin operations so users can react
  • Input validation: Validate all external inputs — check for zero addresses, bounds, array lengths
  • Safe token transfers: Use OpenZeppelin's SafeERC20 for token interactions
  • Event emission: Emit events for all state changes — essential for off-chain monitoring

5. Upgrade Patterns

When contracts need to be upgradeable:

  • Prefer UUPS over Transparent Proxy (lower gas for users)
  • Always initialize via initialize() function (not constructor) with initializer modifier
  • Reserve storage gaps: uint256[50] private __gap; in base contracts
  • Never change storage variable order or types in upgrades
  • Disable initializers on implementation: add _disableInitializers() in constructor
  • Test upgrade compatibility before deploying

6. Testing Requirements

Every contract should have:

  • Unit tests for each function covering happy path and revert cases
  • Integration tests for multi-contract interactions
  • Fuzz tests for functions with numerical inputs (Foundry's forge test)
  • Invariant tests for protocol-wide properties that must always hold
  • Fork tests against mainnet state for DeFi integrations
  • Gas snapshots to track gas regression across changes

7. Deployment Checklist

Before deploying to mainnet:

  • All tests passing with 100% line coverage on critical paths
  • Static analysis run (Slither, Aderyn) with no high/critical findings
  • Formal verification on core invariants if applicable
  • Professional audit completed for high-value contracts
  • Deployment script tested on testnet fork
  • Constructor/initializer arguments double-checked
  • Verify source code on block explorer immediately after deployment
  • Admin roles transferred to multisig/governance
  • Emergency pause mechanism tested