This guide documents NatSpec conventions and formatting standards for Elata Protocol contracts.
Every Solidity file follows this order:
- SPDX license identifier (required, first line)
- Pragma statement
- Imports (explicit named imports preferred)
- Contract NatSpec block
- Contract declaration
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @title ContractName
* @author Elata Biosciences
* @notice One-sentence user-facing summary
* @dev Technical details and implementation notes
* @custom:security-contact security@elata.bio
*/
contract ContractName is ERC20, AccessControl {
// ...
}All protocol contracts include these NatSpec tags:
/**
* @title ContractName
* @author Elata Biosciences
* @notice One-sentence user-facing summary
* @dev Detailed notes covering:
* - Key invariants
* - Trust model / access control
* - External dependencies
* - Non-obvious assumptions
* @custom:security-contact security@elata.bio
*//**
* @title LibraryName
* @notice Brief description of purpose
* @dev Implementation notes if needed
* @custom:security-contact security@elata.bio
*/Document all public and external functions with:
@notice— User-facing description@dev— Implementation details (if non-obvious)@param— All parameters@return— All return values
/**
* @notice Creates a new staking position
* @dev Locks ELTA tokens for the specified duration to mint veELTA
* @param amount ELTA tokens to lock
* @param duration Lock duration in seconds (MIN_LOCK to MAX_LOCK)
* @return positionId The ID of the created position
*/
function createLock(uint256 amount, uint256 duration) external returns (uint256 positionId) {
// Implementation
}Use @inheritdoc for functions that implement an interface:
/// @inheritdoc IERC20
function transfer(address to, uint256 amount) public override returns (bool) {
// Implementation
}Use consistent section headers within contracts:
// =========== Errors ===========
error ZeroAddress();
error InvalidAmount();
// =========== Events ===========
event Deposited(address indexed user, uint256 amount);
// =========== State ===========
uint256 public totalDeposits;
// =========== Constructor ===========
constructor(address _token) { }
// =========== Core Functions ===========
function deposit(uint256 amount) external { }
// =========== Admin Functions ===========
function pause() external onlyAdmin { }
// =========== View Functions ===========
function getBalance(address user) external view returns (uint256) { }- State variables:
camelCase - Constants:
UPPER_SNAKE_CASE - Private variables:
_prefixedWithUnderscore - Immutables:
camelCaseorUPPER_SNAKE_CASEfor primitive types
uint256 public constant MAX_SUPPLY = 77_000_000e18;
uint256 public totalStaked;
address private _admin;
IERC20 public immutable ELTA;- Public/external:
camelCase - Internal/private:
_prefixedWithUnderscore
- Past tense for actions that occurred:
Deposited,Withdrawn,Updated - Or present for state changes:
Transfer,Approval
- Descriptive noun or adjective phrases:
ZeroAddress,InvalidAmount,Unauthorized - Can include context:
InsufficientBalance,LockNotExpired
Use explicit named imports rather than wildcard imports:
// Good
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20, IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// Avoid
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";Name tests descriptively:
function test_CreateLock_WithMinDuration() public { }
function test_CreateLock_RevertWhen_AmountIsZero() public { }
function testFuzz_CreateLock_AnyValidDuration(uint256 duration) public { }Use custom errors instead of require strings:
// In contract or shared Errors.sol
error AmountTooLow();
error LockExpired();
// Usage
if (amount < MIN_AMOUNT) revert AmountTooLow();- No ASCII art in Solidity files — Keep contract headers clean and professional
- No personal GitHub handles or ENS names in contract headers — Use organizational authorship ("Elata Biosciences")
- No HTML or markdown in NatSpec — These won't render in block explorers or documentation generators
- No wildcard imports — Always use explicit named imports
- No require strings for errors — Use custom errors for gas efficiency and clarity
Run forge fmt before committing. The project uses Foundry's default formatter.
# Format all files
make fmt
# Check formatting without changes
make fmt-check- Solidity Style Guide
- NatSpec Format
- OpenZeppelin Contracts for examples