Skip to main content

Documenting Smart Contracts with NatSpec

This guide explains how to properly document Solidity smart contracts using NatSpec (Natural Specification) format and generate API documentation automatically.

Overview​

The ATS contracts use NatSpec (Ethereum Natural Language Specification Format) for inline documentation. This documentation is automatically extracted and published to the API Documentation section.

NatSpec Basics​

NatSpec uses special comment formats that are processed by documentation generators:

  • /// - Single-line NatSpec comment
  • /** ... */ - Multi-line NatSpec comment

NatSpec Tags​

TagPurposeWhere to Use
@titleContract nameContract definition
@authorContract authorContract definition
@noticeUser-facing descriptionContract, functions, events
@devDeveloper notesContract, functions, modifiers
@paramParameter descriptionFunctions with parameters
@returnReturn value descriptionFunctions that return values
@inheritdocInherit documentationOverride functions
@custom:*Custom tagsAny context-specific info

Documentation Structure​

Contract Documentation​

Every contract should have a title and notice explaining its purpose:

/**
* @title BondFacet
* @author Hedera Hashgraph
* @notice Manages bond-specific operations including coupon payments and maturity redemption
* @dev Implements bond lifecycle management using Diamond storage pattern
* @custom:security-contact security@hedera.com
*/
contract BondFacet is IBond, AccessControlStorageWrapper {
// Contract implementation
}

Function Documentation​

Document all public and external functions with clear descriptions:

/**
* @notice Pay a coupon to all bond holders based on a snapshot
* @dev Executes the coupon payment distribution and updates payment history
* @param security The security token address for which to pay coupons
* @param couponDate The date identifier for this coupon payment
* @param paymentTokenAddress The ERC20 token used for payment
* @param totalAmount The total amount to be distributed
* @return success True if the payment was successfully initiated
*/
function payCoupon(
address security,
uint256 couponDate,
address paymentTokenAddress,
uint256 totalAmount
) external override returns (bool success) {
// Implementation
}

Internal Functions​

Internal functions should use @dev for implementation details:

/**
* @dev Calculates the pro-rata coupon amount for a specific holder
* @param holderBalance The holder's token balance at snapshot time
* @param totalSupply The total supply at snapshot time
* @param totalCoupon The total coupon amount to distribute
* @return amount The calculated coupon amount for the holder
*/
function _calculateCouponAmount(
uint256 holderBalance,
uint256 totalSupply,
uint256 totalCoupon
) internal pure returns (uint256 amount) {
// Implementation
}

Events Documentation​

Document all events with clear descriptions of when they're emitted:

/**
* @notice Emitted when a coupon payment is successfully completed
* @param security The security token address
* @param couponDate The coupon payment date identifier
* @param totalAmount The total amount paid
* @param recipientCount The number of recipients who received payment
*/
event CouponPaid(address indexed security, uint256 indexed couponDate, uint256 totalAmount, uint256 recipientCount);

Modifiers Documentation​

Document modifiers explaining their validation logic:

/**
* @notice Ensures the caller has the BOND_MANAGER role
* @dev Reverts with AccessDenied if caller lacks the required role
*/
modifier onlyBondManager() {
require(hasRole(BOND_MANAGER_ROLE, msg.sender), "AccessDenied");
_;
}

Storage Documentation​

Storage structs live at file scope in *StorageWrapper.sol (never inside a contract or interface) and carry an ERC-7201 @custom:storage-location erc7201:security.token.standard.storage.<PascalName> annotation. The annotation argument shares the same PascalCase suffix as the paired STORAGE_LOCATION_* bytes32 constant — see .claude/rules/20-solidity/conventions.md §6 and hash-constants-naming.md §3 for the full 5-region layout convention. All four region banners are always present, in canonical order, even when a region has no fields (empty-region banners are mandatory scaffolding); the region number ↔ meaning mapping is fixed and must never be renumbered.

/// @custom:hash storage Bond
bytes32 constant STORAGE_LOCATION_BOND = 0x0000000000000000000000000000000000000000000000000000000000000000;

/// @custom:storage-location erc7201:security.token.standard.storage.Bond
struct BondDataStorage {
// ─── R1 Lifecycle (bool flags) ───────────────────────────
bool initialized;
// ─── R2 Packed scalars (uint8, bytes3, address, enum) ────
address defaultPaymentToken;
// ─── R3 Single-slot scalars (uint256, bytes32, string) ───
// ─── R4 Aggregates (mapping, array, EnumerableSet) ───────
mapping(address => BondConfig) bondConfigs;
mapping(address => mapping(uint256 => CouponPayment)) couponPayments;
// ─── APPEND-ONLY ZONE BELOW ───
}

Errors Documentation​

Document custom errors with clear descriptions:

/**
* @notice Thrown when attempting to pay a coupon for an invalid date
* @param security The security token address
* @param couponDate The invalid coupon date provided
*/
error InvalidCouponDate(address security, uint256 couponDate);

/**
* @notice Thrown when insufficient funds are available for payment
* @param required The amount required for the payment
* @param available The amount currently available
*/
error InsufficientFunds(uint256 required, uint256 available);

Best Practices​

1. Be Clear and Concise​

Write documentation for developers who are unfamiliar with the code:

// ❌ Bad - too vague
/// @notice Does stuff with bonds
function processBond() external;

// ✅ Good - specific and clear
/// @notice Matures a bond and releases principal to token holders
/// @dev Transfers the bond's face value to all holders proportionally
function processBond() external;

2. Document the "Why", Not Just the "What"​

// ❌ Bad - only describes what
/// @dev Sets the maturity date
function setMaturityDate(uint256 date) external;

// ✅ Good - explains why and constraints
/// @notice Sets the bond maturity date when principal becomes due
/// @dev Can only be called before the bond is issued. Once set, cannot be changed
/// to ensure investor certainty. Maturity date must be in the future.
function setMaturityDate(uint256 date) external;

3. Document Edge Cases and Constraints​

/**
* @notice Transfers tokens between accounts with compliance checks
* @dev Validates the transfer against KYC, freeze, and partition restrictions
* @param from The sender address (must have sufficient balance)
* @param to The recipient address (must pass KYC and not be frozen)
* @param amount The amount to transfer (must be > 0 and <= sender balance)
* @return success True if transfer was successful
*
* Requirements:
* - `from` must have at least `amount` tokens
* - `to` must be KYC verified
* - Neither `from` nor `to` can be frozen
* - Transfer must comply with all active compliance modules
*/
function transfer(address from, address to, uint256 amount) external returns (bool success);
/**
* @notice Creates a snapshot of current token holder balances
* @dev The snapshot ID can be used in {payCoupon} to execute payments
* @param security The security token to snapshot
* @return snapshotId The unique identifier for this snapshot
* @see payCoupon
*/
function createSnapshot(address security) external returns (uint256 snapshotId);

5. Document Security Considerations​

/**
* @notice Updates the bond's interest rate
* @dev ⚠️ SECURITY: This is a privileged operation that affects all bond holders
* Only BOND_ADMIN role can call this function. Rate changes do not apply
* retroactively to already-accrued interest.
* @param security The bond token address
* @param newRate The new annual interest rate in basis points (e.g., 500 = 5%)
* @custom:security Critical - requires BOND_ADMIN role
* @custom:emits InterestRateUpdated
*/
function updateInterestRate(address security, uint256 newRate) external;

Generating Documentation​

Prerequisites​

Ensure you're in the contracts directory:

cd packages/ats/contracts

Generate API Documentation​

Run the documentation generator:

npm run doc

This command:

  1. Extracts NatSpec comments from all contracts
  2. Generates markdown files in /docs/references/api/ats-contracts/
  3. Organizes documentation by contract hierarchy

Configuration​

The documentation generator is configured in hardhat.config.ts:

dodoc: {
runOnCompile: false, // Don't auto-generate on every compile
outputDir: "../../../docs/references/api/ats-contracts",
freshOutput: true, // Clear old docs before generating
include: ["contracts"], // Include all contracts
exclude: [
"contracts/test", // Exclude test contracts
"contracts/mocks", // Exclude mock contracts
"node_modules" // Exclude dependencies
],
},

Viewing Generated Documentation​

After generation, the documentation is available at:

  • Local: http://localhost:3000/docs/references/api
  • Production: https://hashgraph.github.io/asset-tokenization-studio/docs/references/api

Documentation Workflow​

During Development​

  1. Write the contract with inline NatSpec comments
  2. Document as you code - don't leave it for later
  3. Review your documentation before submitting PR

Before Committing​

  1. Generate docs to verify NatSpec is valid:

    npm run doc
  2. Check for warnings - fix any NatSpec syntax errors

  3. Review generated output in /docs/references/api/

In Pull Requests​

  • Documentation changes are automatically reviewed
  • Generated API docs should be committed with code changes
  • CI/CD pipeline will verify documentation builds successfully

Common Patterns in ATS​

Facet Pattern Documentation​

Document facets with their layer and purpose:

/**
* @title BondFacet
* @author Hedera Hashgraph
* @notice Layer 2 facet implementing bond-specific corporate actions
* @dev Part of the Diamond pattern - uses BondStorageWrapper for data access
* @custom:layer Layer 2 - Domain Features
* @custom:security-contact security@hedera.com
*/
contract BondFacet is IBond, BondStorageWrapper {
// Implementation
}

Storage Wrapper Documentation​

Document storage access patterns:

/**
* @title BondStorageWrapper
* @notice Provides type-safe access to bond storage in Diamond pattern
* @dev Implements the storage access layer for bond data
* @custom:layer Layer 0 - Storage
*/
abstract contract BondStorageWrapper {
/**
* @dev Returns the bond storage pointer
* @return s Storage pointer to BondStorage struct
* @custom:storage-location erc1967:bondFacet.storage
*/
function _bondStorage() internal pure returns (BondStorage storage s);
}

Interface Documentation​

Document interfaces with implementation requirements:

/**
* @title IBond
* @notice Interface for bond token operations
* @dev Implementers must support coupon payments and maturity redemption
*/
interface IBond {
/**
* @notice Pay a coupon to bond holders
* @dev Must validate caller permissions and check payment token allowance
* @param security The bond token address
* @param amount The total coupon amount
* @return success True if payment initiated successfully
*/
function payCoupon(address security, uint256 amount) external returns (bool success);
}

Troubleshooting​

Common Issues​

Issue: Documentation generator fails

Error: Unable to find contract sources

Solution: Ensure contracts are compiled first:

npm run compile
npm run doc

Issue: Missing documentation in generated output

Solution: Check that your NatSpec uses proper syntax:

  • Use /// or /** */ format
  • Place comments directly above the documented element
  • Include required tags (@notice for public functions)

Issue: Documentation not showing in Docusaurus

Solution: Rebuild the documentation site:

cd ../../../apps/docs
npm run build

Additional Resources​

Summary​

  • ✅ Use NatSpec format for all public interfaces
  • ✅ Document functions with @notice, @dev, @param, @return
  • ✅ Include security considerations and constraints
  • ✅ Generate docs with npm run doc before committing
  • ✅ Review generated documentation for clarity

Well-documented contracts make the codebase more maintainable and help developers understand the system architecture and business logic.