AgentSkillsCN

web3-solidity-patterns

Solidity设计模式与最佳实践。适用于设计合约架构、实现代理、治理、访问控制,或任何结构性Solidity模式的场景。涵盖Factory、Proxy、Diamond、Governor等多种模式。

SKILL.md
--- frontmatter
name: web3-solidity-patterns
description: Solidity design patterns and best practices. Use when designing contract architecture, implementing proxies, governance, access control, or any structural Solidity patterns. Covers Factory, Proxy, Diamond, Governor, and more.

Solidity Design Patterns

Contract Architecture

Interface-First Development

Always define the interface before implementation:

solidity
interface IVault {
    function deposit(uint256 amount) external;
    function withdraw(uint256 amount) external;
    function balanceOf(address user) external view returns (uint256);

    event Deposited(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);

    error InsufficientBalance(uint256 available, uint256 requested);
}

Contract Structure Order

solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

contract MyContract {
    // 1. Type declarations (structs, enums)
    // 2. State variables
    // 3. Events
    // 4. Errors
    // 5. Modifiers
    // 6. Constructor
    // 7. External functions
    // 8. Public functions
    // 9. Internal functions
    // 10. Private functions
    // 11. View/pure functions
}

Design Patterns

Factory Pattern

Deploy new contract instances from a factory:

solidity
contract VaultFactory {
    address[] public vaults;

    event VaultCreated(address indexed vault, address indexed owner);

    function createVault(address token) external returns (address) {
        Vault vault = new Vault(token, msg.sender);
        vaults.push(address(vault));
        emit VaultCreated(address(vault), msg.sender);
        return address(vault);
    }
}

Use when: deploying multiple instances of the same contract with different params.

Minimal Proxy (EIP-1167 Clone)

Deploy cheap copies sharing the same implementation:

solidity
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";

contract VaultFactory {
    address public immutable implementation;

    constructor() {
        implementation = address(new Vault());
    }

    function createVault(address token) external returns (address) {
        address clone = Clones.clone(implementation);
        Vault(clone).initialize(token, msg.sender);
        return clone;
    }
}

Use when: deploying many identical contracts (saves 90%+ gas on deployment).

Proxy Patterns

PatternUpgrade Logic InGas CostComplexity
TransparentProxy contractHigher (admin check)Medium
UUPS (EIP-1822)ImplementationLowerMedium
BeaconBeacon contractMediumHigh
Diamond (EIP-2535)Diamond contractVariableVery High

Access Control

PatternUse When
OwnableSingle admin
Ownable2StepSingle admin with safe transfer
AccessControlMultiple roles
AccessControlDefaultAdminRulesMultiple roles with admin safety
Multi-sig (Gnosis Safe)High-value operations
Timelock + GovernorDAO governance

Pull-Over-Push (Payments)

solidity
// BAD: Push pattern — can fail/DoS
function distribute(address[] calldata recipients) external {
    for (uint i; i < recipients.length;) {
        payable(recipients[i]).transfer(amount);  // Can fail
        unchecked { ++i; }
    }
}

// GOOD: Pull pattern — users withdraw
mapping(address => uint256) public pendingWithdrawals;

function withdraw() external {
    uint256 amount = pendingWithdrawals[msg.sender];
    require(amount > 0, NothingToWithdraw());
    pendingWithdrawals[msg.sender] = 0;
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, TransferFailed());
}

Event-Driven Architecture

Design events for off-chain indexing (The Graph, custom indexers):

solidity
// Index-friendly events
event Transfer(address indexed from, address indexed to, uint256 value);
event Swap(
    address indexed sender,
    address indexed tokenIn,
    address indexed tokenOut,
    uint256 amountIn,
    uint256 amountOut
);
  • Up to 3 indexed params (for filtering)
  • Non-indexed params for data
  • Emit events for EVERY state change

Library Usage

solidity
// Use libraries for reusable logic without inheritance
using SafeERC20 for IERC20;
using Math for uint256;

// Prefer libraries over inheritance when:
// - Logic is stateless
// - Multiple unrelated contracts need the same utility
// - You want to avoid the diamond inheritance problem

Testing Patterns

TypePurposeTool
UnitSingle function correctnessforge test
IntegrationMulti-contract interactionforge test (with setup)
ForkAgainst real mainnet stateforge test --fork-url
FuzzRandom inputs for propertiesforge test (fuzz)
InvariantProtocol-wide propertiesforge test (invariant)

Code Examples

See examples.md for complete code examples:

  • Minimal Proxy (EIP-1167)
  • UUPS Proxy setup
  • Diamond pattern skeleton
  • Governor setup
  • ERC-20 with Permit