Skillshub solidity-coding
[AUTO-INVOKE] MUST be invoked BEFORE writing or modifying any Solidity contract (.sol files). Covers pragma version, naming conventions, project layout, OpenZeppelin library selection standards, oracle integration, and anti-patterns. Trigger: any task involving creating, editing, or reviewing .sol source files.
git clone https://github.com/ComeOnOliver/skillshub
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/0xlayerghost/solidity-agent-kit/solidity-coding" ~/.claude/skills/comeonoliver-skillshub-solidity-coding && rm -rf "$T"
skills/0xlayerghost/solidity-agent-kit/solidity-coding/SKILL.mdSolidity 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
— keep consistent across all files in the projectpragma solidity ^0.8.19; - Dependencies: OpenZeppelin Contracts 4.9.x, manage imports via
remappings.txt - Error Handling: Prefer custom errors over
strings — saves gas and is more expressiverequire- Define:
error InsufficientBalance(uint256 available, uint256 required); - Use:
if (balance < amount) revert InsufficientBalance(balance, amount);
- Define:
- Documentation: All
/public
functions must have NatSpec (external
,@notice
,@param
)@return - Event Indexing: Only add
toindexed
type parameters — add comment if indexing other typesaddress - Special Keywords:
/immutable
/constant
/unchecked
must have inline comment explaining whyassembly
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);
适用场景:
- 资金去向分叉:同一笔资金因不同条件流向不同地址
- 操作被拒绝/降级:用户请求未完全满足,附带原因
- 状态转换:同一个状态变更可能由不同触发条件引起
- 管理员操作:记录 why(如
,"security incident"
)"parameter tuning"
不适用场景:
- 事件只有一种触发路径(无分支)→ 不需要 reason
- 高频事件(每笔 swap/transfer)→ string 消耗额外 gas,用
代替uint8 reasonCode
Reason 实现方式选择
| 场景 | 方式 | Gas 成本 |
|---|---|---|
| 原因种类少且固定(≤5种) | 字面量 | 低(编译器优化短字符串) |
| 原因种类多或动态 | + 前端映射表 | 最低 |
| 需要附带动态数据 | 拼接 | 较高,慎用 |
// 方式 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
- 每个改变状态的
/external
函数必须 emit 至少一个事件public - 事件参数应包含足够信息让前端/indexer 无需额外 RPC 调用就能重建完整上下文
- NatSpec
必须为 reason 参数列出所有可能的值@param
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Contract / Library | PascalCase | , |
| Interface | + PascalCase | , |
| State variable / Function | lowerCamelCase | , |
| Constant / Immutable | UPPER_SNAKE_CASE | , |
| Event | PascalCase (past tense) | , |
| Custom Error | PascalCase | , |
| Function parameter | prefix for setter | |
- Forbidden: Pinyin names, single-letter variables (except
in loops), excessive abbreviationsi/j/k
Code Organization Rules
| Situation | Rule |
|---|---|
| Cross-contract constants | Place in |
| Interface definitions | Place in , separate from 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: |
Project Directory Structure
src/ — Contract source code interfaces/ — Interface definitions (I*.sol) 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
— network RPC URLs, contract addresses, business parametersconfig/*.json
— latest deployed contract addresses, must update after each deploymentdeployments/latest.env
— compiler version, optimizer settings, remappingsfoundry.toml- 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 | | |
| Owner transfer needs safety | | |
| Multi-role permission (admin/operator/minter) | | |
| Need to enumerate role members | | |
| Governance with timelock delay | | |
Rule: Single owner →
Ownable2Step; 2+ roles → AccessControl; governance/DAO → TimelockController
Security Protection
| Scenario | Library | Usage |
|---|---|---|
| External call / token transfer | | Add modifier |
| Emergency pause needed | | Add to user-facing functions; keep admin functions unpaused |
| ERC20 token interaction | | Use / / instead of raw calls |
Rule: Any contract that transfers tokens or ETH MUST use
ReentrancyGuard + SafeERC20
Token Standards
| Scenario | Library | Notes |
|---|---|---|
| Fungible token | | Base standard |
| Token with burn mechanism | | Adds and |
| Token with max supply cap | | Enforces |
| Gasless approval (EIP-2612) | | Saves users approve tx gas |
| Governance voting token | | Snapshot-based voting power |
| NFT | | Base NFT standard |
| NFT with enumeration | | Supports queries |
| Multi-token (FT + NFT mixed) | | Game items, batch operations |
Utility Libraries
| Scenario | Library | Usage |
|---|---|---|
| Whitelist / airdrop verification | | Gas-efficient Merkle tree verification |
| Signature verification | + | Off-chain sign + on-chain verify |
| Auto-increment IDs | | Token ID, order ID generation |
| Batch function calls | | Multiple operations in one tx |
| Address set / uint set | | Iterable sets with O(1) add/remove/contains |
| Revenue sharing | | Split ETH/token payments by shares |
| Standardized yield vault | | DeFi vault standard |
Contract Upgrade
| Scenario | Library | Notes |
|---|---|---|
| Upgradeable contract (gas efficient) | | Upgrade logic in implementation contract |
| Upgradeable contract (admin separated) | | Upgrade logic in proxy, higher gas |
| Initializer (replace constructor) | | Use 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 | | Only for tokens with supported oracle data feeds |
| Verifiable randomness (lottery/NFT) | | On-chain provably fair random numbers |
| Automated execution (cron jobs) | | Replace centralized keepers |
| Cross-chain messaging | | 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 → 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
wrappers — usetransferSafeERC20 - Do NOT write custom access control modifiers — use
/OwnableAccessControl - Do NOT write custom pause logic — use
Pausable - Do NOT use
on Solidity >= 0.8.0 — overflow checks are built-inSafeMath - Do NOT use
— userequire(token.transfer(...))
viatoken.safeTransfer(...)SafeERC20 - Do NOT use
for auth — usetx.origin
withmsg.sender
/OwnableAccessControl
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 | | Any new ERC20 token contract |
| NFT | | Any new NFT contract |
| Multi-token | | Game items, batch operations |
| Stablecoin | | Stablecoin with ERC20 compliance |
| Real-world assets | | Asset tokenization |
| Smart account | | ERC-4337 account abstraction |
| Governance | | DAO voting and proposals |
| 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 | |
| Install dependency | |
| Build contracts | |
| Format code | |
| Update remappings | |