Claude Code Plugins

Community-maintained marketplace

Feedback
0
0

NatSpec documentation standards and best practices for Solidity contracts. Use when documenting code or reviewing documentation quality.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name natspec-standards
description NatSpec documentation standards and best practices for Solidity contracts. Use when documenting code or reviewing documentation quality.

NatSpec Standards Skill

This skill provides standards and best practices for NatSpec (Ethereum Natural Specification) documentation in Solidity.

When to Use

Use this skill when:

  • Documenting smart contracts
  • Reviewing code documentation
  • Generating documentation
  • Preparing for audits
  • Creating public APIs

NatSpec Overview

NatSpec is Ethereum's standard for documenting smart contracts. It generates human-readable documentation and is displayed in wallets when users interact with contracts.

Benefits:

  • User-facing descriptions in wallets
  • Developer documentation
  • Automated doc generation
  • Audit trail
  • Function parameter explanation

Basic Tags

Contract-Level Tags

/// @title Brief contract title
/// @author Author name
/// @notice Explanation visible to end users
/// @dev Technical details for developers
/// @custom:security-contact security@example.com
contract MyContract {
    // ...
}

Function Tags

/// @notice Transfers tokens to a recipient
/// @dev Uses SafeERC20 for safe transfers
/// @param to The address receiving the tokens
/// @param amount The number of tokens to transfer
/// @return success True if the transfer succeeded
function transfer(address to, uint256 amount) public returns (bool success) {
    // ...
}

Event Tags

/// @notice Emitted when tokens are transferred
/// @dev Follows ERC20 standard
/// @param from The sender address
/// @param to The recipient address
/// @param value The amount transferred
event Transfer(address indexed from, address indexed to, uint256 value);

State Variable Tags

/// @notice The total supply of tokens
/// @dev Updated on mint and burn
uint256 public totalSupply;

/// @notice Mapping of account balances
mapping(address => uint256) public balances;

Complete Tag Reference

Tag Usage Visibility
@title Contract title External
@author Author name External
@notice User explanation External
@dev Developer notes Developer
@param Parameter description External
@return Return value description External
@inheritdoc Inherit documentation -
@custom:* Custom tags Both

Documentation Patterns

Comprehensive Contract Documentation

/**
 * @title ERC20 Token Implementation
 * @author YourName
 * @notice This contract implements a standard ERC20 token with additional features
 * @dev Inherits from OpenZeppelin's ERC20 implementation
 * @custom:security-contact security@example.com
 */
contract MyToken is ERC20, Ownable {
    /// @notice The maximum supply of tokens that can ever exist
    /// @dev Set to 1 million tokens with 18 decimals
    uint256 public constant MAX_SUPPLY = 1_000_000 * 10**18;

    /// @notice Tracks the total amount of tokens that have been minted
    /// @dev Incremented on mint, used to enforce MAX_SUPPLY
    uint256 public totalMinted;

    /**
     * @notice Creates the token contract and mints initial supply
     * @dev Sets the token name and symbol, grants DEFAULT_ADMIN_ROLE to deployer
     * @param initialOwner The address that will own the contract
     */
    constructor(address initialOwner)
        ERC20("MyToken", "MTK")
        Ownable(initialOwner)
    {}

    /**
     * @notice Mints new tokens to a specified address
     * @dev Only callable by owner, enforces MAX_SUPPLY limit
     * @param to The address that will receive the minted tokens
     * @param amount The number of tokens to mint (in wei, e.g., 1e18 = 1 token)
     *
     * Requirements:
     * - Caller must be the owner
     * - Total minted must not exceed MAX_SUPPLY
     * - `to` cannot be the zero address
     *
     * Emits a {Transfer} event from the zero address
     */
    function mint(address to, uint256 amount) public onlyOwner {
        require(totalMinted + amount <= MAX_SUPPLY, "Exceeds max supply");
        _mint(to, amount);
        totalMinted += amount;
    }

    /**
     * @notice Returns the remaining tokens that can be minted
     * @dev Calculates MAX_SUPPLY - totalMinted
     * @return remaining The number of tokens that can still be minted
     */
    function remainingSupply() public view returns (uint256 remaining) {
        return MAX_SUPPLY - totalMinted;
    }
}

Documenting Complex Functions

/**
 * @notice Swaps exact tokens for tokens through multiple pairs
 * @dev Supports multi-hop swaps through the Uniswap V2 router
 *
 * This function swaps an exact amount of input tokens for as many output
 * tokens as possible, following the path of token pairs defined in `path`.
 * The swap will revert if the output amount is less than `amountOutMin`.
 *
 * @param amountIn The exact amount of input tokens to swap
 * @param amountOutMin The minimum amount of output tokens to receive
 * @param path An array of token addresses defining the swap path
 * @param to The address that will receive the output tokens
 * @param deadline Unix timestamp after which the transaction will revert
 *
 * @return amounts An array of amounts for each step in the swap path
 *
 * Requirements:
 * - `path.length` must be >= 2
 * - `deadline` must be in the future
 * - Contract must have approval to spend `amountIn` of `path[0]` tokens
 * - Resulting amount must be >= `amountOutMin`
 *
 * Emits:
 * - {Swap} event for each pair in the path
 * - {Transfer} events for token movements
 *
 * Example:
 * ```
 * // Swap 1 ETH for at least 2000 USDC
 * uint256 amountIn = 1 ether;
 * uint256 amountOutMin = 2000 * 10**6;
 * address[] memory path = new address[](2);
 * path[0] = WETH;
 * path[1] = USDC;
 * swapExactTokensForTokens(amountIn, amountOutMin, path, msg.sender, block.timestamp + 300);
 * ```
 */
function swapExactTokensForTokens(
    uint256 amountIn,
    uint256 amountOutMin,
    address[] calldata path,
    address to,
    uint256 deadline
) external returns (uint256[] memory amounts) {
    // Implementation
}

Documenting Errors

/**
 * @notice Thrown when a user has insufficient balance for an operation
 * @dev Used instead of require() for gas efficiency
 * @param account The address with insufficient balance
 * @param balance The actual balance of the account
 * @param needed The amount needed for the operation
 */
error InsufficientBalance(address account, uint256 balance, uint256 needed);

/**
 * @notice Thrown when an unauthorized address attempts a restricted action
 * @param caller The address that attempted the action
 * @param required The role or address required
 */
error Unauthorized(address caller, address required);

Documenting Modifiers

/**
 * @notice Ensures the function can only be called when the contract is not paused
 * @dev Reverts with "Pausable: paused" if contract is paused
 */
modifier whenNotPaused() {
    require(!paused(), "Pausable: paused");
    _;
}

/**
 * @notice Prevents reentrant calls to a function
 * @dev Uses the ReentrancyGuard pattern from OpenZeppelin
 */
modifier nonReentrant() {
    require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
    _status = _ENTERED;
    _;
    _status = _NOT_ENTERED;
}

Documenting Structs and Enums

/**
 * @notice Represents a user's position in the vault
 * @dev Stored in the `positions` mapping
 * @param amount The amount of tokens deposited
 * @param shares The number of vault shares owned
 * @param lastUpdate The timestamp of the last position update
 * @param locked Whether the position is locked for withdrawals
 */
struct Position {
    uint256 amount;
    uint256 shares;
    uint256 lastUpdate;
    bool locked;
}

/**
 * @notice Possible states for a proposal
 * @dev Used in the governance system
 */
enum ProposalState {
    Pending,    /// @notice Proposal has been created but voting hasn't started
    Active,     /// @notice Voting is currently open
    Canceled,   /// @notice Proposal was canceled by the proposer
    Defeated,   /// @notice Proposal failed to reach quorum or majority
    Succeeded,  /// @notice Proposal passed and can be executed
    Executed    /// @notice Proposal has been executed
}

Documentation Best Practices

1. Always Document Public/External Functions

// ❌ Bad: No documentation
function transfer(address to, uint256 amount) public returns (bool) {}

// ✅ Good: Clear documentation
/**
 * @notice Transfers tokens to a recipient
 * @param to The recipient address
 * @param amount The amount to transfer
 * @return success True if transfer succeeded
 */
function transfer(address to, uint256 amount) public returns (bool success) {}

2. Explain Complex Logic

/**
 * @notice Calculates compound interest using the formula: A = P(1 + r)^t
 * @dev Uses fixed-point arithmetic with 18 decimals
 *
 * The calculation is broken into steps to avoid overflow:
 * 1. Calculate (1 + rate) with 18 decimal precision
 * 2. Raise to the power of time periods
 * 3. Multiply by principal and adjust for decimals
 *
 * @param principal The initial amount (with 18 decimals)
 * @param rate The interest rate per period (with 18 decimals, e.g., 0.05e18 = 5%)
 * @param periods The number of compounding periods
 * @return The final amount after compound interest
 */
function calculateCompoundInterest(
    uint256 principal,
    uint256 rate,
    uint256 periods
) public pure returns (uint256) {
    // Implementation
}

3. Document Requirements and Effects

/**
 * @notice Stakes tokens in the contract
 * @dev Transfers tokens from user and mints staking shares
 *
 * @param amount The amount of tokens to stake
 *
 * Requirements:
 * - `amount` must be greater than 0
 * - User must have approved this contract to spend `amount`
 * - User must have sufficient token balance
 * - Contract must not be paused
 *
 * Effects:
 * - Transfers `amount` tokens from user to contract
 * - Mints proportional shares to user
 * - Updates user's stake timestamp
 * - Increases total staked amount
 *
 * Emits:
 * - {Staked} event with user address and amount
 * - {Transfer} events from token transfers
 */
function stake(uint256 amount) external nonReentrant whenNotPaused {
    // Implementation
}

4. Document Security Considerations

/**
 * @notice Withdraws all staked tokens and rewards
 * @dev Uses pull-over-push pattern to avoid reentrancy
 *
 * Security Considerations:
 * - Protected by `nonReentrant` modifier
 * - Updates state before external calls (CEI pattern)
 * - Will revert if contract has insufficient balance
 *
 * @return stakedAmount The amount of staked tokens returned
 * @return rewardAmount The amount of reward tokens claimed
 */
function withdraw() external nonReentrant returns (uint256 stakedAmount, uint256 rewardAmount) {
    // Implementation
}

5. Use Custom Tags for Additional Info

/**
 * @title Vault Contract
 * @author YourName
 * @notice Deposit tokens to earn yield
 * @dev Implements ERC4626 tokenized vault standard
 *
 * @custom:security-contact security@example.com
 * @custom:audit Audited by Company X on 2024-01-15
 * @custom:version 1.0.0
 * @custom:license MIT
 */
contract Vault is ERC4626 {
    // ...
}

Documentation Generation

Foundry

# Generate documentation
forge doc

# Generate and serve
forge doc --serve --port 3000

# Generate for specific contract
forge doc --contract MyContract

Hardhat

# Install hardhat-dodoc
npm install --save-dev @primitivefi/hardhat-dodoc

# Generate documentation
npx hardhat dodoc

# Documentation generated in docs/

Inheritance Documentation

/**
 * @title Enhanced ERC20
 * @notice ERC20 with additional features
 * @inheritdoc ERC20
 */
contract EnhancedERC20 is ERC20 {
    /**
     * @notice Mints new tokens with additional validation
     * @inheritdoc ERC20
     * @dev Adds max supply check before minting
     */
    function _mint(address account, uint256 amount) internal override {
        require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
        super._mint(account, amount);
    }
}

Common Documentation Mistakes

❌ 1. Vague Descriptions

/// @notice Does stuff
/// @param x some value
function doSomething(uint256 x) public {}

✅ Better:

/**
 * @notice Updates the withdrawal fee percentage
 * @param newFee The new fee in basis points (e.g., 100 = 1%)
 */
function setFee(uint256 newFee) public {}

❌ 2. Outdated Documentation

/// @notice Transfers tokens (function has been modified but docs haven't)
function transfer(address to, uint256 amount, bool fast) public {}

❌ 3. Missing Parameter Descriptions

/// @notice Swaps tokens
function swap(uint256 a, uint256 b, address[] memory p) public {}

✅ Better:

/**
 * @notice Swaps tokens through specified path
 * @param amountIn The input amount
 * @param amountOutMin The minimum acceptable output
 * @param path The swap path (array of token addresses)
 */
function swap(uint256 amountIn, uint256 amountOutMin, address[] memory path) public {}

Documentation Checklist

  • Every public/external function documented
  • All parameters explained
  • Return values described
  • Requirements listed
  • Effects documented
  • Events documented
  • Errors documented with parameters
  • Complex logic explained
  • Security considerations noted
  • Examples provided for complex functions
  • Custom tags used where appropriate
  • Documentation matches implementation

Quick Reference

Minimal Documentation

/// @notice Brief user-facing description
/// @param param1 Description
/// @return Description
function myFunction(uint256 param1) external returns (uint256) {}

Complete Documentation

/**
 * @notice User-facing description
 * @dev Technical details
 * @param param1 Description
 * @param param2 Description
 * @return returnValue Description
 *
 * Requirements:
 * - List requirements
 *
 * Emits:
 * - {EventName}
 */
function myFunction(uint256 param1, address param2)
    external
    returns (uint256 returnValue)
{}

Remember: Good documentation is essential for security audits, user trust, and long-term maintainability. Document as you code, not as an afterthought.