This file provides guidance to LLM tools when working with code in this repository.
This is Olympus V3 (aka Bophades), a complete rewrite of the Olympus protocol using the Default Framework. It's a modular DeFi protocol built on Solidity that separates concerns through a Kernel-Module-Policy architecture.
Installation and Build:
- Node.js >=24 is required for local development and CI
pnpm install- Install all dependencies (runs postinstall script)pnpm buildorforge build- Build all filesforge build --contracts path/to/contract.sol- Build a specific contract
Testing:
pnpm run test- Run all tests (runs./shell/test_all.sh)pnpm run test:unit- Run unit tests only (excludes fork tests and proposals)pnpm run test:fork- Run fork tests (requiresALCHEMY_API_KEYenv var)pnpm run test:proposal- Run OCG proposal testspnpm run test:coverage- Generate test coverage reportforge test -vvv --match-contract ContractTest- Run a specific test contract
For detailed test debugging guidance (verbosity levels, setUp() issues, trace output), use the /test-debug skill.
Linting:
pnpm run lint- Format and lint code (prettier + solhint + markdownlint)pnpm run lint:check- Check formatting and linting without fixingpnpm run prettier- Format code (runs quicker than linting)
For detailed linter note resolution guidance (deployed vs in-development contracts, suppression templates), use the /lint-fix skill.
Note: Always build, test and lint updated files. Use project-wide build and test commands sparingly.
Allowed without prompt:
- Read files, list files
- Build single file
- Formatting and linting
- Test single file
Ask first:
- Installing dependencies
- Full build
- Git push
- Deleting files
- Changing file permissions
- Full test suites (
test,test:unit,test:forkortest:proposal)
Use the repo's existing commands and tooling for commit, push, and PR work. Do not add scripts for this workflow.
- Before every commit, run the repo's existing lint/format command:
pnpm run lint. - Before pushing, run the repo's existing full validation commands:
pnpm buildorforge buildpnpm run lint:checkpnpm run testwhen the change is ready for full-suite validation or touches protocol behavior, tests, deployment, dependencies, or shared tooling- Include other existing focused validation commands when they are directly relevant to the changed files
- Keep focused tests and single-file builds available during implementation, but do not substitute them for the push gate when the change is ready to publish.
- CodeRabbit is a sparse pre-push review tool. Run it before pushing or when explicitly requested, not before every commit.
- When using CodeRabbit, run
coderabbit review --agent --base <PR base>. Verify the PR base and diff scope before trusting the result. Treat an instant "0 issues" response as suspicious until the base and reviewed diff have been confirmed.
Batch approval into two explicit gates. If the user approves a gate, do not ask separately for lint, git add, git commit, git push, or PR creation steps covered by that gate.
-
Commit approval:
- Show the current branch.
- Show the exact files that will be staged.
- Show the validation command that will run before commit.
- Show the exact commit message.
- After approval, run validation, stage only the listed files, and commit.
-
Publish approval:
- Show the current branch.
- Show the remote and PR base.
- Show the push validation commands, including full tests when appropriate.
- Show the CodeRabbit command when it applies.
- Show the PR action: create or update.
- Show the PR title and a summary of the PR body.
- After approval, validate, push, and create or update the PR.
Never stage unrelated files. If the worktree has unrelated modifications, leave them unstaged and call them out in the approval summary.
- For worktrees, check whether
.envexists before running fork tests or full validation that needs RPC credentials. - If
.envis missing, bootstrap it from the documented shared/local env source. If the source is unknown, ask once for the source and reuse that answer for the current worktree. - Do not repeatedly rediscover or re-report the same missing env issue after the source has been identified.
- For audit issues or manually pasted CodeRabbit comments, first verify applicability against the current code.
- State whether each issue applies, does not apply, or partially applies.
- Identify the red test or smallest validation that demonstrates the issue before implementing, when a test is practical.
- Implement only applicable fixes, then run focused validation before the normal commit or publish gate.
- Include audit issue IDs in PR bodies when relevant.
- Be direct and concise
- Avoid conversational fillers ("Let me...", "I'm going to...", "Now I'll...").
- State what you're doing, not what you're about to do. Example: Instead of "Let me read the file to understand what's happening", simply read the file and report findings.
- Make reasonable assumptions based on codebase conventions. Only ask when:
- Multiple valid approaches exist with significant trade-offs
- Security implications are unclear
- The choice affects external integrations
- Document assumptions in comments when you proceed without asking
- When stuck, ask a clarifying question
- Fix the specific issue, not the surrounding code unless requested
- Don't refactor "just because" - prefer ugly-but-working over clean-but-risky
- Exception: If you spot a critical security bug, flag it immediately
- Read the actual implementation before making changes. Never guess.
- For cross-cutting concerns (e.g., "all places that use X"), use grep/glob to find all instances first
- When exploring, cast a wide net initially, then narrow down
- If a build/test fails, report the actual error, not a summary
- Include file:line references for all errors
- Attempt to fix before reporting, but explain what you tried
- Remember user preferences stated earlier in the conversation
- When a pattern is established (e.g., "always use 18 decimals"), apply it consistently
- If the user corrects you, update your mental model and acknowledge
- Prefer specialized tools (Read, Grep, Glob) over bash equivalents
- Use Bash only for: git, npm/pnpm, forge commands, or actual terminal operations
- Never use bash for file operations (cat, sed, awk, echo redirections)
- For multi-step tasks, use TodoWrite proactively
- Mark tasks in_progress immediately when starting
- Mark completed after finishing - not in batches
- One task in_progress at a time
- Write or update tests before implementation
- If tests don't exist for a function you're modifying, create them first
- Run the specific test file after changes, not the full suite
- Only run full test suites when explicitly requested or at major milestones
- Use the Repo Workflow approval gates for commits, pushes, and PRs. Do not commit or push outside those gates unless the user explicitly requests a different flow.
- The git commit message should follow the format of:
<type>(<scope>): <description>typemay be one of:- feat: Introduces a new feature.
- fix: Patches a bug.
- docs: Documentation-only changes.
- style: Changes that do not affect the meaning of the code (white-space, formatting, etc).
- refactor: A code change that neither fixes a bug nor adds a feature.
- perf: Improves performance.
- test: Adds missing tests or corrects existing tests.
- chore: Changes to the build process or auxiliary tools and libraries such as documentation generation.
scopecan refer to the area of code (such as the feature) where the change has taken placedescriptionis a concise summary of the changes
- When starting work on a new branch or feature, use git worktree instead of switching branches
- List existing worktrees before creating new ones:
git worktree list - Create a worktree for a branch:
git worktree add ../olympus-v3-<feature-name> <branch-name> - When done with a worktree, remove it:
git worktree remove ../olympus-v3-<feature-name> - Be aware of worktree locations when running commands—use absolute paths if the worktree is outside the main repo
Claude skills are located in .claude/skills/ and can be invoked by name:
| Skill | Purpose |
|---|---|
/test-write |
Test writing guidance (file structure, modifiers, naming, error handling) |
/test-debug |
Test debugging guidance (verbosity levels, setUp() issues, trace output) |
/lint-fix |
Linter note resolution (deployed vs in-development contracts, suppression templates) |
Invoke a skill by name, e.g., /test-write for test writing guidance.
The project includes Model Context Protocol (MCP) servers for enhanced AI capabilities:
| Server | Purpose |
|---|---|
| CodeRabbit | Automated code review via coderabbitai-mcp |
| Context7 | Context-aware assistance via context7.com |
| Etherscan | On-chain contract verification via etherscan.io |
| GitHub | Repository integration via githubcopilot.com |
Configuration is in .mcp.json. API keys are set via environment variables:
MCP_GITHUB_PAT- GitHub personal access token (for CodeRabbit and GitHub)MCP_CONTEXT7_API_KEY- Context7 API keyMCP_ETHERSCAN_API_KEY- Etherscan API key
The protocol follows the Default Framework pattern with a three major components:
-
Kernel (
src/Kernel.sol) - Central governance and access control system- Manages installation/upgrading of modules and activation/deactivation of policies
- Implements role-based access control with 5-byte keycodes
- Executes governance actions: InstallModule, UpgradeModule, ActivatePolicy, DeactivatePolicy, ChangeExecutor, MigrateKernel
-
Modules (
src/modules/) - Shared state storage with minimal dependencies- Each module has a 5-byte keycode identifier (e.g., TRSRY, MINTR, PRICE)
- Define roles for policies to access module functions
- Can be upgraded by installing new versions with same keycode
- Key modules: TRSRY (Treasury), MINTR (Minter), PRICE (Price Oracle), RANGE (Range-Bound Stability), ROLES (Access Control)
-
Policies (
src/policies/) - User-facing contracts with business logic- Request permissions from kernel to call module functions
- Contain isolated state and handle user interactions
- Examples: Operator (RBS), Heart (Auction), BondCallback, Cooler lending
- Core Protocol: Operator, Heart, Distributor, Emergency
- Lending: Cooler V2 (MonoCooler), LoanConsolidator
- Cross-Chain: CCIPBurnMintTokenPool, CrossChainBridge
- Deposits: ConvertibleDepositFacility, YieldDepositFacility
- Boosted Liquidity: BLVault implementations for Lido and LUSD
src/
├── Kernel.sol # Central control system
├── modules/ # Shared state storage
│ ├── TRSRY/ # Treasury management
│ ├── MINTR/ # Token minting
│ ├── PRICE/ # Price oracles
│ ├── RANGE/ # Range-bound stability
│ └── ROLES/ # Access control
├── policies/ # Business logic contracts
│ ├── cooler/ # Lending protocols
│ ├── deposits/ # Deposit facilities
│ ├── BoostedLiquidity/ # Liquidity mining
│ └── bridge/ # Cross-chain functionality
├── external/ # External contract dependencies
├── interfaces/ # Standard interfaces
├── libraries/ # Utility libraries
└── test/ # Test files and mocks
└── scripts/ # Deployment and configuration scripts
└── proposals/ # On-Chain Governance (OCG) proposalsTest Commands:
pnpm run test- Run all tests (runs./shell/test_all.sh)pnpm run test:unit- Run unit tests only (excludes fork tests and proposals)pnpm run test:fork- Run fork tests (requiresALCHEMY_API_KEYenv var)pnpm run test:proposal- Run OCG proposal testsforge test -vvv --match-contract <Contract>- Run a specific test contract
Test Organization:
- Unit tests exclude fork tests and proposals:
--no-match-contract '(Fork)' --no-match-path 'src/test/proposals/*.t.sol' - Proposal tests are in
src/test/proposals/and require fork environment - Coverage reports generated in
coverage/directory
For detailed test writing guidance (file structure, modifiers, naming, error handling), use the /test-write skill.
Key standards summary:
- One test file per contract external function (internal functions are tested indirectly via their callers)
- Use
given*modifiers for state setup - Follow branching tree naming:
test_given<Condition>_<Action>_<ExpectedResult>() - Always use error selectors, never string messages:
abi.encodeWithSelector(Error.selector) - All assertions must have descriptive messages
- Chain-specific env files:
.env.[chain](copy from.env.deploy.example) - Deployment scripts in
src/scripts/deploy/ - Saved deployments in
src/scripts/deploy/savedDeployments/ - See
src/scripts/DEPLOY.mdandsrc/scripts/DEPLOY_L2.mdfor detailed steps
- Solidity version: >= 0.8.24 (with some on 0.8.15 for historical reasons)
- Optimizer runs: 10,000 (except for some contracts that require specific runs to meet bytecode limits, see foundry.toml)
- Follow existing patterns for module/policy development
- Use Default Framework conventions for access control and state management
- Dependencies are installed using soldeer (
forge soldeer) and kept independencies/ - Follow best-case practices for writing Solidity code, e.g. https://dev.to/truongpx396/solidity-limitations-solutions-best-practices-and-gas-optimization-27cb
- Running
forge buildwill output theforgetool's linting output. For linter note resolution, use the/lint-fixskill for guidance on deployed vs in-development contracts. - Internal state variables MUST use underscore prefix:
uint256 internal _counter; - For commit and push validation, follow the Repo Workflow section.
- When completing a major milestone, the unit tests should pass:
pnpm run test:unit - Between milestones, run a build (
forge build) and prettier (pnpm run prettier) - Do not use
require()for assertions. Instead, preference custom errors. Custom errors should be defined in the contract's parent interface (where available), or else in the contract itself. - Do not revert with a blank message, use a custom error instead.
- Contracts should have a separate interface that is defined in a separate file, to allow for easy integration. All interfaces are MIT-licensed, and should avoid using internal types. Interfaces should also use NatSpec to define functions and types, and any expectations for implementation contracts.
- Contracts that implement interfaces should use the
@inheritdocNatSpec tag in function documentation to reference the parent interface's function. - Function documentation should outline the behaviour of the function, including any conditions that would result in a revert.
- Policies request permissions via
requestPermissions()function - Kernel grants/revokes roles based on governance actions
- Use
onlyRole()modifiers (oronlyAdminRole()etc if using the PolicyAdmin mix-in) for function access control
Kernel.sol- Core governance and module/policy managementpolicies/Operator.sol- Range-Bound Stability mechanismpolicies/Heart.sol- Auction system for stability operationsmodules/TRSRY/- Treasury operations and asset managementpolicies/cooler/MonoCooler.sol- Primary lending protocol
- Modules inherit from
Modulebase contract with keycode and version - Policies inherit from
Policybase contract with dependency/permission requests - Use the
IVersionedinterface to standardise versioning of Modules and Policies - Contracts that implement interfaces should implement the
supportsInterface()function from ERC165 - Policies can use the
PolicyEnablermix-in to inherit common functionality around enabling/disabling contracts. Periphery contracts can usePeripheryEnabler. - Error handling with custom errors following naming conventions
- When planning a new feature, to write the plan to disk in Markdown format, and always include a TODO list that can be checked off. When working on that new feature, regularly update the status in the task list of that feature plan.
- When importing dependencies, use a versioned import path, e.g.
@solmate-6.2.0instead ofsolmate. Refer to remappings.txt for the aliases. - Imports must be at the top of the file, below the license and pragma.
- Imports should be grouped under headings of: interface, libraries, contracts
- Within each grouping, keep the imports sorted by the dependency path
- Do NOT do global imports,
import "src/Kernel.sol" - Instead, import individual contracts from a file, e.g.
import {Kernel} from "src/Kernel.sol" - The codebase has different approaches to imports. Ignore those and implement the prescribed approach.
When working with Solidity code involving mathematical operations, follow these principles:
-
No Floating-Point: Solidity has no floating-point numbers - all numbers are integers.
-
Decimal Representation:
- Decimal numbers are represented as integers with an associated decimal scale
- Example: 1.0 with 18 decimals = 1000000000000000000 (1e18)
- Always track and document the decimal scale of each variable
-
Multiplication & Division Order:
- When multiplying/dividing numbers with different scales, order matters
- General pattern: multiply first, then divide to maintain precision
- Example:
result = a * scaleB / scaleCwhere result has scaleB decimals - Always calculate and verify the resulting decimal scale
- Phantom overflows can occur, where
a * scaleB(from the example above) overflows the maximum value ofuint256. For that reason, it is advisable to use the FullMath library insrc/libraries/FullMath.sol.
-
Rounding Behavior:
- Solidity rounds DOWN by default (floor division)
- Use
mulDiv()for standard rounding down - Use
mulDivUp()when rounding up is needed - Be explicit about rounding direction in comments
- Suggested approach: when involving values going to an external user, round down. This favours the protocol.
- Ask the developer for desired behaviour
-
Precision Loss:
- Be aware of precision loss in division operations
- Consider the order of operations to maximize precision
- Document any intentional precision trade-offs
- Always comment the decimal scale of variables involved in calculations
- Show the arithmetic reasoning: input scales → operation → output scale
- Use descriptive variable names that hint at their scale when possible
- Explicitly state rounding behavior when it matters
-
Document the mathematical working in comments above each assertion
-
Show step-by-step calculation with decimal scales
-
Include the expected value derivation
-
Example format:
// deposit = 5e18 (18 decimals) // ohmScale = 1e9 (9 decimals) // price = 2e18 (18 decimals) // Expected: (5e18 * 1e9) / 2e18 = 5e27 / 2e18 = 2.5e9 (9 decimals) // Rounds down to 2e9 assertEq(convertibleAmount, 2e9, "Convertible amount does not equal 2e9");
- Verify decimal scale consistency across operations
- Check for potential overflow/underflow
- Validate that the order of operations preserves precision
- Confirm rounding behavior matches requirements
- Look for off-by-one errors due to rounding
Converting between scales:
// Convert from 18 decimals to 9 decimals
value9 = value18 / 1e9;
// Convert from 9 decimals to 18 decimals
value18 = value9 * 1e9;Price calculations:
// amount (18 dec) * price (18 dec) / 1e18 = value (18 dec)
value = amount.mulDiv(price, 1e18);Proportion calculations:
// part (X dec) * total (Y dec) / denominator (Z dec) = result (X+Y-Z dec)
result = part.mulDiv(total, denominator);Always think through the decimal arithmetic step-by-step and make the reasoning explicit in your responses.
Use CodeRabbit through the Repo Workflow section, which defines timing, base verification, and the standard review command. Run coderabbit -h for CLI details if the installed command syntax is unclear.
IMPORTANT: When running CodeRabbit to review code changes, don't run it more than 3 times in a given set of changes.
For detailed linter note resolution guidance (deployed vs in-development contracts, suppression templates, common fixes), use the /lint-fix skill.
Check for linting issues:
pnpm run lint:check # Check all linting rules
forge build # Output forge-lint notesThe following commands show detailed linting rule breakdown:
# List all linting rules with notes/warnings
forge lint 2>&1 | grep -E "^warning\[|^note\[" | grep -v "src/test" | sed 's/^.*\[\([^]]*\)\].*/\1/' | sort | uniq | cat
# Show linting rules grouped by file
forge lint 2>&1 | sed -n '
/^note\[/ { s/^note\[\([^]]*\)\].*/NOTE:\1/p; h; d; }
/^warning\[/ { s/^warning\[\([^]]*\)\].*/WARNING:\1/p; h; d; }
/^error\[/ { s/^error\[\([^]]*\)\].*/ERROR:\1/p; h; d; }
/^ --> / { s/^ --> \([^:]*\):.*/\1/p; }
' | awk '
/^(NOTE|WARNING|ERROR):/ { rule = $0; next }
rule { print rule "|" $0 }
' | sort -u | awk -F'|' '
{
if ($1 != last) {
if (last) print "";
print $1;
last = $1
}
print " " $2
}'Git worktrees allow multiple branches to be checked out simultaneously without stashing or committing changes. This is useful for:
- Working on multiple features concurrently
- Testing code on different branches
- Reviewing PRs while preserving current work
Common commands:
git worktree list- Show all worktreesgit worktree add <path> <branch>- Create new worktree for a branchgit worktree remove <path>- Delete a worktree