At Golem Foundation, we believe in creating maintainable, high-quality code through thoughtful collaboration. This guide outlines our expectations and best practices for contributing to the project.
- Each commit should represent a single logical change
- Use clear, descriptive commit messages that explain why the change was made
- Follow the conventional commits format:
type(scope): message(e.g.,fix(rewards): correct calculation for staking rewards) - Avoid mixing unrelated changes in a single commit
As PRs get bigger, time to review them scales super-linearly. And long-standing PRs create lots of merge conflict and duplicate work. It's much cheaper for the team to make small and prompt changes to codebase than big and belated changes.
- Keep PRs focused on a single feature, bug fix, or improvement
- Aim for PRs under 300 lines of code when possible
- Split large features into smaller, sequential PRs
- Include relevant tests and documentation with your changes
- Mark unfinished or work-in-progress PRs as Draft until they are ready for review
- Review existing PRs (if requested) before creating new ones
- Help merge ready PRs to prevent accumulation
- Set aside a time for PR reviews daily
- Rebase your branch before requesting review to ensure it's up-to-date
Main time cost of developing smart-contract is audits, not development itself. By solving things in simplest, clearest way we can reduce this cost dramatically.
- Implement the simplest solution that meets requirements
- Avoid premature optimization or over-engineering
- Write self-documenting code with clear variable and function names, with clear intent
- Use comments as a last resort when things are not clear enough
- Aim for high coherence and loose coupling
- Adhere to SOLID Principles so we can ensure solidity (pun intended) of our code
- Consider future (maintenance, or other) costs in your design decisions
- Apply the "Boy Scout Rule": Leave the code better than you found it
- Address small issues before they become big problems
- Document what you stumble upon and can't address now
- Self-review your code before requesting reviews
- Write concise PR descriptions - Focus on what changed and why, not implementation details
- Keep under 15 lines when possible
- Link to detailed docs if needed (don't paste them in description)
- Use bullet points for clarity
- Be open to feedback and willing to make changes
- Use the PR description to highlight areas where you'd like specific feedback
- Be respectful and constructive in your feedback
- Focus on the code, not the person
- Stay in the scope of the PR during your review
- Be specific, leave no room for confusion
- Try to approve ASAP when concerns are addressed
- We use Gitflow as Git branching model.
- We use a pre-commit hook to automatically format, lint and test, ensuring no surprises on continuous integration run.
- Use clear, verbose language in all communications
- Ask questions when something isn't clear
- Share progress and blockers with the team
- Be mindful of others' time and priorities
- Always prioritize security and only then consider gas optimizations
- Follow established security patterns for smart contracts
- Consider edge cases and potential attack vectors
- Document security assumptions and considerations
All tests MUST follow the AAA pattern for clarity and maintainability:
function test_deposit_MintsCorrectShares() public {
// Arrange - Set up test state
uint256 depositAmount = 1000e18;
deal(address(asset), user, depositAmount);
vm.startPrank(user);
asset.approve(address(vault), depositAmount);
// Act - Execute the operation being tested
uint256 shares = vault.deposit(depositAmount, user);
// Assert - Verify expected outcomes
assertEq(shares, expectedShares, "Should mint correct shares");
assertEq(vault.balanceOf(user), shares, "User should receive shares");
assertEq(asset.balanceOf(address(vault)), depositAmount, "Vault should hold assets");
vm.stopPrank();
}Why AAA?
- Arrange: Makes test setup explicit and reproducible
- Act: Isolates the exact behavior being tested
- Assert: Clearly documents expected outcomes
Follow these patterns for discoverability:
- Files:
ContractName.t.sol,ContractNameIntegration.t.sol,ContractNameInvariants.t.sol - Unit Tests:
test_functionName_Scenario()(e.g.,test_deposit_RevertsWhenPaused()) - Fuzz Tests:
testFuzz_functionName_Scenario(uint256 x)(e.g.,testFuzz_deposit_AlwaysMintsShares(uint256 amount)) - Revert Tests:
test_RevertWhen_condition()(e.g.,test_RevertWhen_DepositExceedsLimit())
- Run
yarn coveragebefore submitting PRs - Check uncovered branches and add tests for critical paths
- View detailed reports with
yarn coverage:genhtml - Focus on security-critical functions (access control, asset handling, state transitions)
For detailed security and quality requirements (what edge cases to test, anti-patterns to avoid), see .github/code-review.md.
The Four Pillars: Every NatSpec contribution must adhere to:
- Syntax - Proper tag usage and formatting
- Semantics - Accurate descriptions matching implementation
- License - Correct SPDX identifiers and copyleft compliance
- Attribution - Proper credit to original authors and sources
| Tag | Required | Example |
|---|---|---|
@title |
YES | @title YourContract |
@author |
YES | @author [Golem Foundation](https://golem.foundation) |
@notice |
YES (functions/events) | @notice Deposits assets and mints shares |
@dev |
When needed | @dev Uses ERC4626 with ROUND_DOWN |
@param |
YES | @param assets Amount of assets to deposit |
@return |
If returns | @return Minted shares |
@inheritdoc |
For overrides | @inheritdoc IYourInterface |
@custom:security-contact |
YES | @custom:security-contact security@golem.foundation |
@custom:security |
For privileged | @custom:security Only callable by management |
@custom:origin |
For adapted code | @custom:origin https://github.com/org/repo/blob/hash/file.sol |
@custom:ported-from |
For ported code | @custom:ported-from https://github.com/org/repo/blob/hash/file.sol |
Note on Errors: Custom errors should be self-documenting through descriptive names. Do NOT add @notice tags to errors.
ALWAYS use @inheritdoc when overriding functions from parent contracts or interfaces to avoid duplicating documentation.
When a function overrides a parent implementation, use @inheritdoc ParentContract instead of duplicating the documentation. Only add additional context specific to your implementation using @dev tags.
Good - Using @inheritdoc:
/// @inheritdoc IERC4626Payable
/// @dev Requires operator role when dragon mode is enabled
function deposit(uint256 assets, address receiver)
external payable override returns (uint256 shares) {
// implementation
}Bad - Duplicating parent documentation:
/// @notice Deposits assets into the vault and mints shares to receiver
/// @param assets Amount of assets to deposit in asset base units
/// @param receiver Address to receive minted shares
/// @return shares Amount of shares minted in share base units
/// @dev Requires operator role when dragon mode is enabled
function deposit(uint256 assets, address receiver)
external payable override returns (uint256 shares) {
// implementation
}When to use @inheritdoc:
- ✅ Function overrides parent/interface function
- ✅ Documentation would be identical or nearly identical to parent
- ✅ You want to reference the canonical documentation in the interface/parent
When NOT to use @inheritdoc:
- ❌ Signature or behavior differs significantly from parent
- ❌ Parent contract has no documentation (document it properly instead)
- ❌ Implementation-specific nuances require full documentation
Original Golem Code:
// SPDX-License-Identifier: AGPL-3.0-or-later
/**
* @title YourContract
* @author [Golem Foundation](https://golem.foundation)
* @custom:security-contact security@golem.foundation
*/Modified External Code:
// SPDX-License-Identifier: GPL-3.0 // ← Preserve original license
/**
* @author Yearn.finance; modified by [Golem Foundation](https://golem.foundation)
* @custom:security-contact security@golem.foundation
* @custom:origin https://github.com/yearn/[repo]/blob/[COMMIT_HASH]/path/to/file.sol
* @notice Your description here
*/Design-Inspired (Not Derivative):
// SPDX-License-Identifier: AGPL-3.0-or-later // ← Use AGPL-3.0-or-later
/**
* @author [Golem Foundation](https://golem.foundation)
* @dev Design inspired by:
* - OpenZeppelin Governor: https://github.com/OpenZeppelin/[...]
* - Yearn Strategy: https://github.com/yearn/[...]
*/Core Principles:
- Attention is scarce: Document what's necessary and nothing more. Make it count.
- Document WHY, not WHAT (code shows what)
- Specify units ONLY when genuinely ambiguous (follow OpenZeppelin standard)
- No "The..." anti-patterns that restate names
- Keep it concise - every word must earn its place
Common Mistakes:
| ❌ Don't | ✅ Do |
|---|---|
@param amount The amount |
@param amount Amount of assets to deposit |
@param assets Amount of assets in asset base units |
@param assets Amount of assets to deposit |
@return The shares |
@return Minted shares |
@param deployer The deployer address |
@param deployer Address that deployed contract |
Units Reference (Only when genuinely ambiguous):
| Type | When to Specify | Format |
|---|---|---|
| ERC4626 assets/shares | ❌ Never (standard convention) | @param assets Amount of assets |
| Multiple share types | ✅ Always | @param shares Proportional allocation shares (unitless) |
| Basis points | ✅ Always | @param fee Fee in basis points (10000 = 100%) |
| Time (ambiguous) | ✅ When unclear | @param duration Duration in seconds (not blocks) |
| Timestamps | ❌ Never (Unix standard) | @param timestamp Timestamp of event |
| Exchange rates | ✅ Non-standard only | @param rate Rate in RAY precision (1e27) |
Golden Rule: Follow OpenZeppelin conventions - only add unit qualifiers when there's genuine ambiguity.
Ported Code = Implementation PRESERVED (we maintain but don't modify core logic)
- Tag:
@custom:ported-from <URL> - Example: Direct Solidity port of Vyper contract, extracted library, unmodified interface
Adapted Code = Implementation MODIFIED (forked, adapted, or heavily changed from upstream)
- Tag:
@custom:origin <URL> - Example: Forked contract with Octant-specific changes, adapted interface
IMPORTANT: These tags are mutually exclusive - use one or the other, never both.
For Ported Code (use @custom:ported-from):
- ✅ Keep original NatSpec documentation style
- ✅ Preserve original "The..." patterns (don't fix them)
- ✅ Only update: SPDX license, @author attribution, @custom:security-contact
- ✅ Add
@custom:ported-from <URL>with link to original source - ❌ Don't apply our strict NatSpec standards
For Adapted Code (use @custom:origin):
- ✅ Apply full NatSpec standards from this guide
- ✅ Fix "The..." patterns
- ✅ Add proper @author attribution noting modification (forked/adapted/modified)
- ✅ Add
@custom:origin <URL>with link to original source
Ported (Implementation Unmodified):
/**
* @title MultistrategyVault
* @author yearn.finance; port maintained by [Golem Foundation]
* @custom:security-contact security@golem.foundation
* @custom:ported-from https://github.com/yearn/yearn-vaults-v3/blob/master/contracts/VaultV3.vy
* @notice ... (preserve original documentation)
*/Extracted Library (Still Ported):
/**
* @title DebtManagementLib
* @author yearn.finance; extracted as library by [Golem Foundation]
* @custom:security-contact security@golem.foundation
* @custom:ported-from https://github.com/yearn/yearn-vaults-v3/blob/master/contracts/VaultV3.vy
* @notice ... (preserve original documentation)
*/Adapted Code (Implementation Modified):
/**
* @title TokenizedStrategy
* @author yearn.finance; forked and modified by [Golem Foundation]
* @custom:security-contact security@golem.foundation
* @custom:origin https://github.com/yearn/tokenized-strategy/blob/master/src/TokenizedStrategy.sol
* @notice Tokenized strategy with Octant-specific enhancements
* @dev Apply our NatSpec standards - fix "The..." patterns, add units, etc.
*/- All public/external functions have
@notice,@param,@return - All events have
@noticeand@paramfor each parameter - Used
@inheritdocfor overridden functions instead of duplicating documentation - Ambiguous numeric params/returns specify units (follow OpenZeppelin - don't over-specify)
- Contract has
@title,@author,@custom:security-contact - Privileged functions have
@custom:security - Custom errors use self-documenting names (NO @notice tags)
- License adheres to copyleft if modifying GPL/AGPL code
- Attribution credits original authors with source links
- No TODO/FIXME in production
- No "The..." patterns that merely restate names
-
forge docgenerates clean output
Thank you for contributing to Octant V2 Core!