SuperAPI AI Directory

solidity-coding

Coding & development

View repository

Updated 10-15 08:48

About this skill

Solidity Coding Standards

Language Rule

  • Always respond in the same language the user is using. If the user asks in Chinese, respond in Chinese. If in English, respond in English.

Coding Principles

  • Pragma: Use pragma solidity ^0.8.19; — keep consistent across all files in the project
  • Dependencies: OpenZeppelin Contracts 4.9.x, manage imports via remappings.txt
  • Error Handling: Prefer custom errors over require strings — saves gas and is more expressive
    • Define: error InsufficientBalance(uint256 available, uint256 required);
    • Use: if (balance < amount) revert InsufficientBalance(balance, amount);
  • Documentation: All public / external functions must have NatSpec (@notice, @param, @return)
  • Event Indexing: Only add indexed to address type parameters — add comment if indexing other types
  • Special Keywords: immutable / constant / unchecked / assembly must have inline comment explaining why

Initialization Strategy

When a contract involves state initialization (external addresses, business parameters, role assignments), DO NOT default to any single approach. Follow this workflow:

Workflow

  1. Evaluate: Is the contract upgradeable? Are all dependencies known at deploy time? Are there cross-contract deployment order constraints?
  2. Present options: Show the developer the three initialization schemes below with their trade-offs
  3. Let the developer choose: Do not assume a scheme — different projects and contracts have different needs
  4. Implement: Apply the chosen scheme with the safety rules listed below

Scheme Comparison

Scheme A: Constructor Only Scheme B: Initializer Only Scheme C: Hybrid
Approach All init in constructor params Deploy empty, call initialize() after Constructor handles known params; initialize() handles post-deploy dependencies
Atomicity Deploy fails = nothing exists, no dangerous half-init state Risk of "deployed but not initialized" window Partially atomic — constructor part is safe, initializer part has the window
Front-run risk None — constructor cannot be front-run initialize() can be front-run if unprotected Only the initializer portion has risk
Gas One tx for deploy + init Deploy tx + N init txs Deploy tx + 1 init tx
Flexibility Low — all params must be known at deploy time High — can set dependencies after other contracts deploy Medium — split between deploy-time and post-deploy
Upgradeable Not compatible with proxy patterns Required for UUPS / Transparent Proxy Partial — constructor sets proxy-safe params, initializer for the rest

When to Suggest Each Scheme

Scenario Suggested Scheme
Non-upgradeable, all dependencies known at deploy A
Upgradeable contract (UUPS / Transparent Proxy) B
Non-upgradeable, but some dependencies deploy later (e.g., LP pair created after token deploy) C
Multi-contract system with circular references C
Simple utility / library-style contract A
Contract needs post-deploy configuration by multisig / DAO B or C

Safety Rules (Apply Regardless of Scheme)

  • One-shot lock: Any setter that should only be called once must enforce require(currentValue == address(0)) or a bool initialized flag — prevent re-initialization
  • No unprotected initializers: initialize() functions must have access control (onlyOwner, initializer modifier, or deployer-only check)
  • Immutable preference: Values known at deploy time should use immutable over regular storage — saves gas on every read
  • No complex external calls in constructor: Constructor should not call external contracts for state-changing operations (swap, addLiquidity) — the contract is not fully constructed yet
  • Deploy script responsibility: When using Scheme C, the deploy script must call initialize() in the same transaction batch or immediately after — document the required call sequence

Event Design Patterns

Reason-Annotated Events

When an event represents a conditional/branching outcome (the same action can happen for different reasons), add a string reason parameter to make on-chain data self-documenting:

// GOOD — on-chain data is self-explanatory
event WithdrawalFailed(address indexed user, uint256 amount, string reason);
emit WithdrawalFailed(user, amount, "insufficient balance");
emit WithdrawalFailed(user, amount, "cooldown not expired");

event ProposalRejected(uint256 indexed proposalId, string reason);
emit ProposalRejected(id, "quorum not reached");
emit ProposalRejected(id, "voting period ended");

// BAD — must read contract source to understand why
event WithdrawalFailed(address indexed user, uint256 amount);

Applicable scenarios:

  • Fund destination branching: the same funds flow to different addresses under different conditions
  • Operation rejected/downgraded: the user's request is not fully satisfied, with a reason attached
  • State transitions: the same state change may be caused by different trigger conditions
  • Admin operations: record why (e.g. "security incident", "parameter tuning")

Non-applicable scenarios:

  • The event has only one trigger path (no branching) → no reason needed
  • High-frequency events (every swap/transfer) → a string consumes extra gas; use uint8 reasonCode instead

Choosing a Reason implementation approach

Scenario Approach Gas cost
Few and fixed reason types (≤5) string reason literal Low (compiler optimizes short strings)
Many or dynamic reason types uint8 reasonCode + frontend mapping table Lowest
Dynamic data needs to be attached string reason concatenation Relatively high, use with caution
// 方式 A:string literal(推荐,可读性最好)
emit WithdrawalFailed(user, amount, "cooldown not expired");

// 方式 B:uint8 code(高频场景省 gas)
event SwapRejected(address indexed user, uint256 amount, uint8 reasonCode);
// 0 = slippage, 1 = insufficient liquidity, 2 = paused
emit SwapRejected(user, amount, 0);

General Event Design Rules

  • Every state-changing external / public functionmust emit at least one event
  • Event parameters should contain enough information for the frontend/indexer without additional RPC callsto reconstruct the complete context
  • NatSpec @param must list all possible values for the reason parameter

Naming Conventions

Element Convention Example
Contract / Library PascalCase MyToken, StakingPool
Interface I + PascalCase IMyToken, IStakingPool
State variable / Function lowerCamelCase totalSupply, claimDividend
Constant / Immutable UPPER_SNAKE_CASE MAX_SUPPLY, ROUTER_ADDRESS
Event PascalCase (past tense) TokenTransferred, PoolCreated
Custom Error PascalCase InsufficientBalance, Unauthorized
Function parameter prefix _ for setter function setFee(uint256 _fee)
  • Forbidden: Pinyin names, single-letter variables (except i/j/k in loops), excessive abbreviations
  • Return value naming: When a return variable name conflicts with a state variable, use a semantic prefix (is / can / has / current / total) instead of appending _ — keeps ABI clean for frontend consumers

Code Organization Rules

Situation Rule
Cross-contract constants Place in src/common/Const.sol
Interface definitions Place in src/interfaces/I<Name>.sol, separate from implementation
Shared stateful behavior Place in src/abstract/<Name>.sol and inherit it instead of copying the implementation
Simple on-chain queries Use Foundry cast CLI (call / send)
Complex multi-step operations Use Foundry script (*.s.sol)
Import style Use named imports: import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

Shared Administrative Functions

When the same administrative operation is needed by multiple contracts, keep one canonical function name, parameter order, event, errors, NatSpec, and implementation. Search the repository before adding a new variant.

Use a shared abstract contract for stateful behavior or modifiers, an interface when external consumers need a stable ABI, and a library only for stateless logic. Business contracts MUST inherit the shared implementation instead of copying it.

For the project's canonical owner-controlled asset recovery function and inheritance rules, read references/shared-contract-behaviors.md. The project policy intentionally treats the owner as the highest authority and permits recovery of every ERC20 token and native currency held by the contract.

Complex Function Refactoring

When a function coordinates multiple business stages, mixes validation with several state changes or external calls, or approaches compiler stack limits, read references/complex-function-refactoring.md before modifying it.

Do not split small functions merely to increase function count. Preserve the external ABI, execution order, revert behavior, state-transition ordering, emitted events, and fund-flow invariants unless the user explicitly requests a behavioral change.

Project Directory Structure

src/              — Contract source code
  interfaces/     — Interface definitions (I*.sol)
  abstract/       — Shared stateful base contracts (AssetRescue.sol, etc.)
  common/         — Shared constants, types, errors (Const.sol, Types.sol)
test/             — Test files (*.t.sol)
script/           — Deployment & interaction scripts (*.s.sol)
config/           — Network config, parameters (*.json)
deployments/      — Deployment records (latest.env)
docs/             — Documentation, changelogs
lib/              — Dependencies (managed by Foundry)

Configuration Management

  • config/*.json — network RPC URLs, contract addresses, business parameters
  • deployments/latest.env — latest deployed contract addresses, must update after each deployment
  • foundry.toml — compiler version, optimizer settings, remappings
  • Important config changes must be documented in the PR description

OpenZeppelin Library Selection Standards

When writing Solidity contracts, prioritize using battle-tested OpenZeppelin libraries over custom implementations. Select the appropriate library based on the scenario:

Access Control

Scenario Library Import Path
Single owner management Ownable @openzeppelin/contracts/access/Ownable.sol
Owner transfer needs safety Ownable2Step @openzeppelin/contracts/access/Ownable2Step.sol
Multi-role permission (admin/operator/minter) AccessControl @openzeppelin/contracts/access/AccessControl.sol
Need to enumerate role members AccessControlEnumerable @openzeppelin/contracts/access/AccessControlEnumerable.sol
Governance with timelock delay TimelockController @openzeppelin/contracts/governance/TimelockController.sol

Ownership Model Selection

For a single-owner contract, the developer MUST choose the ownership model before code generation:

Choice Behavior Use When
Ownable Ownership transfer completes in one transaction Simplicity and immediate transfer are explicitly preferred
Ownable2Step New owner must call acceptOwnership() Protection against transferring ownership to the wrong address is preferred

Do not silently force Ownable2Step. If the project has no recorded choice, present both options and let the developer choose. Once selected, use that model consistently across the project, including shared base contracts, deployment scripts, tests, and ownership-transfer commands. Generate the selected inheritance and imports from the start; do not generate Ownable2Step and leave the developer to replace it manually.

Rule: 1 role → developer chooses Ownable or Ownable2Step; 2+ roles → AccessControl; governance/DAO → TimelockController

Security Protection

Scenario Library Usage
External call / token transfer ReentrancyGuard Add nonReentrant modifier
Emergency pause needed Pausable Add whenNotPaused to user-facing functions; keep admin functions unpaused
ERC20 token interaction SafeERC20 Use safeTransfer / safeTransferFrom / safeApprove instead of raw calls

Rule: Any contract that transfers tokens or ETH MUST use ReentrancyGuard + SafeERC20

Token Standards

Scenario Library Notes
Fungible token ERC20 Base standard
Token with burn mechanism ERC20Burnable Adds burn() and burnFrom()
Token with max supply cap ERC20Capped Enforces totalSupply <= cap
Gasless approval (EIP-2612) ERC20Permit Saves users approve tx gas
Governance voting token ERC20Votes Snapshot-based voting power
NFT ERC721 Base NFT standard
NFT with enumeration ERC721Enumerable Supports tokenOfOwnerByIndex queries
Multi-token (FT + NFT mixed) ERC1155 Game items, batch operations

Utility Libraries

Scenario Library Usage
Whitelist / airdrop verification MerkleProof Gas-efficient Merkle tree verification
Signature verification ECDSA + EIP712 Off-chain sign + on-chain verify
Auto-increment IDs Counters Token ID, order ID generation
Batch function calls Multicall Multiple operations in one tx
Address set / uint set EnumerableSet Iterable sets with O(1) add/remove/contains
Revenue sharing PaymentSplitter Split ETH/token payments by shares
Standardized yield vault ERC4626 DeFi vault standard

Contract Upgrade

Scenario Library Notes
Upgradeable contract (gas efficient) UUPSUpgradeable Upgrade logic in implementation contract
Upgradeable contract (admin separated) TransparentUpgradeableProxy Upgrade logic in proxy, higher gas
Initializer (replace constructor) Initializable Use initializer modifier instead of constructor

Rule: New projects prefer UUPSUpgradeable; always use Initializable for upgradeable contracts

Oracle and Off-Chain Services

Scenario Library Notes
Token price data AggregatorV3Interface Only for tokens with supported oracle data feeds
Verifiable randomness (lottery/NFT) VRFConsumerBaseV2 On-chain provably fair random numbers
Automated execution (cron jobs) AutomationCompatible Replace centralized keepers
Cross-chain messaging CCIP Cross-chain token/message transfer

Library Selection Decision Flow

Does contract handle user funds/tokens?
├── YES → Add ReentrancyGuard + SafeERC20
│         Does it need emergency stop?
│         ├── YES → Add Pausable
│         └── NO  → Skip
└── NO  → Skip

How many admin roles needed?
├── 1 role  → Ask developer to choose Ownable or Ownable2Step
├── 2+ roles → AccessControl
└── DAO/governance → TimelockController

Does contract need price data?
├── Token has oracle feed → AggregatorV3Interface
├── No oracle feed → Custom TWAP with min-liquidity check
└── No price needed → Skip

Will contract need upgrades?
├── YES → UUPSUpgradeable + Initializable
└── NO  → Standard deployment (immutable)

Anti-Patterns (Do NOT)

  • Do NOT write custom transfer wrappers — use SafeERC20
  • Do NOT write custom access control modifiers — use Ownable / AccessControl
  • Do NOT write custom pause logic — use Pausable
  • Do NOT use SafeMath on Solidity >= 0.8.0 — overflow checks are built-in
  • Do NOT use require(token.transfer(...)) — use token.safeTransfer(...) via SafeERC20
  • Do NOT use tx.origin for auth — use msg.sender with Ownable / AccessControl

MCP-Assisted Contract Generation (if available)

When OpenZeppelinContracts MCP is configured, prefer using it to generate base contracts instead of writing from scratch:

Contract Type MCP Tool When to Use
Fungible token solidity-erc20 Any new ERC20 token contract
NFT solidity-erc721 Any new NFT contract
Multi-token solidity-erc1155 Game items, batch operations
Stablecoin solidity-stablecoin Stablecoin with ERC20 compliance
Real-world assets solidity-rwa Asset tokenization
Smart account solidity-account ERC-4337 account abstraction
Governance solidity-governor DAO voting and proposals
Custom solidity-custom Non-standard contracts with OZ patterns

Workflow: MCP generates base → apply this skill's naming/structure rules → customize business logic → apply /solidity-security rules

Why MCP over manual: MCP output is validated against the same rule-set as OZ Contracts Wizard — imports, modifiers, security checks are guaranteed correct. Manual coding risks missing imports or using wrong OZ versions.

When NOT to use MCP: Heavily custom contracts with non-standard patterns, contracts that don't fit any OZ template, or when you need fine-grained control from line 1.

Graceful degradation: If MCP is not configured, fall back to the Library Selection Standards above and write contracts manually following all rules in this skill.

Foundry Quick Reference

Operation Command
Create new project forge init <project-name>
Install dependency forge install openzeppelin-contracts
Build contracts forge build
Format code forge fmt
Update remappings forge remappings