diff --git a/.claude/skills/choose-extension/COMPARISON.md b/.claude/skills/choose-extension/COMPARISON.md new file mode 100644 index 00000000..3dc1e824 --- /dev/null +++ b/.claude/skills/choose-extension/COMPARISON.md @@ -0,0 +1,122 @@ +# M Extension Comparison + +Detailed comparison of all M extension types to help you make an informed decision. + +## Feature Matrix + +| Feature | MYieldToOne | MYieldToOneForcedTransfer | MYieldFee | MSpokeYieldFee | MEarnerManager | JMIExtension | +|---------|:-----------:|:-------------------------:|:---------:|:--------------:|:--------------:|:------------:| +| **Yield Model** | Single | Single | All holders | All holders | Whitelisted | Single | +| **Freezable accounts** | Yes | Yes | Yes | Yes | No | Yes | +| **Pausable** | Yes | Yes | Yes | Yes | Yes | Yes | +| **Forced transfers** | No | Yes | No | No | No | No | +| **Whitelist required** | No | No | No | No | Yes | No | +| **Per-address fee rates** | No | No | No | No | Yes | No | +| **Global fee rate** | No | No | Yes | Yes | No | No | +| **Custom claim recipients** | No | No | Yes | Yes | No | No | +| **Multi-asset collateral** | No | No | No | No | No | Yes | +| **L2 compatible** | Yes | Yes | L1 only | L2 only | Yes | Yes | + +## Yield Models Explained + +### Single Recipient (Treasury Model) +All yield accumulates and is claimable by a single designated address. Best for protocols where yield should flow to a treasury, DAO, or specific entity. + +**Yield calculation:** `yield = M_balance - totalSupply` + +**Extensions:** MYieldToOne, MYieldToOneForcedTransfer, JMIExtension + +### All Holders (User Yield Model) +Every token holder earns yield proportionally to their balance. A global fee percentage can be deducted from all yield. + +**Yield calculation:** Uses continuous index-based accrual with adjustable fee rate. + +**Extensions:** MYieldFee (L1), MSpokeYieldFee (L2) + +### Whitelisted Holders (Institutional Model) +Only approved addresses can hold tokens. Each address can have a custom fee rate, enabling tiered arrangements. + +**Yield calculation:** Per-account principal tracking with individual fee deductions. + +**Extensions:** MEarnerManager + +## Role Requirements + +### MYieldToOne +| Role | Purpose | +|------|---------| +| `DEFAULT_ADMIN_ROLE` | Grant/revoke other roles | +| `YIELD_RECIPIENT_MANAGER_ROLE` | Change yield recipient address | +| `FREEZE_MANAGER_ROLE` | Freeze/unfreeze accounts | +| `PAUSER_ROLE` | Pause/unpause contract | + +### MYieldToOneForcedTransfer +All MYieldToOne roles plus: +| Role | Purpose | +|------|---------| +| `FORCED_TRANSFER_MANAGER_ROLE` | Transfer tokens from frozen accounts | + +### MYieldFee / MSpokeYieldFee +| Role | Purpose | +|------|---------| +| `DEFAULT_ADMIN_ROLE` | Grant/revoke other roles | +| `FEE_MANAGER_ROLE` | Set fee rate and fee recipient | +| `CLAIM_RECIPIENT_MANAGER_ROLE` | Set custom claim recipients per account | +| `FREEZE_MANAGER_ROLE` | Freeze/unfreeze accounts | +| `PAUSER_ROLE` | Pause/unpause contract | + +### MEarnerManager +| Role | Purpose | +|------|---------| +| `DEFAULT_ADMIN_ROLE` | Grant/revoke other roles | +| `EARNER_MANAGER_ROLE` | Whitelist accounts, set fee rates, manage fee recipient | +| `PAUSER_ROLE` | Pause/unpause contract | + +### JMIExtension +All MYieldToOne roles plus: +| Role | Purpose | +|------|---------| +| `ASSET_CAP_MANAGER_ROLE` | Set caps for non-M collateral assets | + +## Storage Patterns + +All extensions use ERC-7201 namespaced storage for upgrade safety. + +### Per-Account Storage +- **MYieldToOne/MYieldToOneForcedTransfer:** Balance, frozen status +- **MYieldFee/MSpokeYieldFee:** Balance, principal, custom claim recipient, frozen status +- **MEarnerManager:** Balance, principal, whitelisted status, fee rate +- **JMIExtension:** Balance, frozen status + +### Global Storage +- **MYieldToOne/MYieldToOneForcedTransfer:** Total supply, yield recipient +- **MYieldFee/MSpokeYieldFee:** Total supply, total principal, fee rate, fee recipient, latest index +- **MEarnerManager:** Total supply, total principal, fee recipient, earning enabled flag +- **JMIExtension:** Total supply, yield recipient, per-asset caps and balances, total assets + +## Gas Considerations + +| Operation | Lower Gas | Higher Gas | Notes | +|-----------|-----------|------------|-------| +| Transfer | MYieldToOne | MEarnerManager | Whitelist checks add overhead | +| Wrap | MYieldToOne | JMIExtension | Multi-asset logic adds cost | +| Claim yield | MYieldToOne | MYieldFee | Index updates more complex | + +## Upgrade Paths + +All extensions deploy behind TransparentUpgradeableProxy, allowing: +- Bug fixes without redeployment +- Feature additions (with care for storage layout) +- Role/parameter adjustments via admin functions + +**Important:** Changing yield models (e.g., MYieldToOne to MYieldFee) requires migration to a new contract. + +## Chain Deployment Guide + +| Chain Type | Recommended Extensions | +|------------|----------------------| +| Ethereum Mainnet | MYieldToOne, MYieldToOneForcedTransfer, MYieldFee, MEarnerManager, JMIExtension | +| Arbitrum | MSpokeYieldFee, MYieldToOne, MEarnerManager, JMIExtension | +| Optimism | MSpokeYieldFee, MYieldToOne, MEarnerManager, JMIExtension | +| Base | MSpokeYieldFee, MYieldToOne, MEarnerManager, JMIExtension | +| Other L2s | Check if Rate Oracle is available for MSpokeYieldFee | diff --git a/.claude/skills/choose-extension/EXAMPLES.md b/.claude/skills/choose-extension/EXAMPLES.md new file mode 100644 index 00000000..09f4ed5c --- /dev/null +++ b/.claude/skills/choose-extension/EXAMPLES.md @@ -0,0 +1,155 @@ +# M Extension Use Case Examples + +Real-world scenarios to help you identify which extension fits your needs. + +## Example 1: Protocol Treasury + +**Scenario:** A DeFi protocol wants to wrap M tokens for use in their ecosystem. All yield should go to the protocol treasury to fund development. + +**Requirements:** +- Single yield recipient (treasury multisig) +- Open access (anyone can hold tokens) +- No special compliance needs + +**Recommendation:** `MYieldToOne` + +**Why:** Simplest model for treasury yield collection. Anyone can call `claimYield()` to mint accumulated yield to the designated recipient. + +**Configuration:** +```solidity +yieldRecipient = 0x...treasury_multisig +``` + +--- + +## Example 2: Regulated Stablecoin + +**Scenario:** A regulated financial institution issuing a compliant stablecoin needs the ability to freeze accounts and recover funds from sanctioned addresses. + +**Requirements:** +- Single yield recipient +- Account freezing for sanctions compliance +- Ability to recover funds from frozen accounts +- OFAC compliance + +**Recommendation:** `MYieldToOneForcedTransfer` + +**Why:** Provides all MYieldToOne features plus forced transfer capability for regulatory compliance. Can seize tokens from frozen accounts when legally required. + +**Configuration:** +```solidity +yieldRecipient = 0x...compliance_treasury +freezeManager = 0x...compliance_team +forcedTransferManager = 0x...legal_team +``` + +--- + +## Example 3: Yield-Bearing Stablecoin for DeFi + +**Scenario:** A DeFi protocol wants to offer a yield-bearing stablecoin where all holders automatically earn yield. The protocol takes a 10% fee on yield. + +**Requirements:** +- All holders earn yield +- Protocol fee on yield +- Deploying on Ethereum mainnet + +**Recommendation:** `MYieldFee` + +**Why:** Automatically distributes yield to all holders with a configurable global fee. No claiming required by users - balances grow automatically. + +**Configuration:** +```solidity +feeRate = 1000 // 10% (in basis points) +feeRecipient = 0x...protocol_treasury +``` + +--- + +## Example 4: L2 Yield Token + +**Scenario:** Same as Example 3, but deploying on Arbitrum to reduce gas costs for users. + +**Requirements:** +- All holders earn yield +- Protocol fee on yield +- Deploying on Arbitrum + +**Recommendation:** `MSpokeYieldFee` + +**Why:** Same functionality as MYieldFee but designed for L2 chains. Uses bridged index updates from L1 instead of real-time calculation. + +**Configuration:** +```solidity +feeRate = 1000 // 10% +feeRecipient = 0x...protocol_treasury +rateOracle = 0x...arbitrum_rate_oracle +``` + +--- + +## Example 5: Institutional Platform + +**Scenario:** A fintech company serving multiple institutional clients. Each client has a different fee arrangement based on their AUM. Only approved institutions can hold tokens. + +**Requirements:** +- Whitelist-based access +- Different fee rates per client +- KYC/AML compliance (only approved entities) + +**Recommendation:** `MEarnerManager` + +**Why:** Provides granular control over who can hold tokens and individual fee rates per account. Perfect for B2B platforms with tiered client arrangements. + +**Configuration:** +```solidity +// Whitelist institution A with 5% fee +setAccountInfo(institutionA, true, 500) + +// Whitelist institution B with 2% fee (VIP rate) +setAccountInfo(institutionB, true, 200) + +feeRecipient = 0x...platform_treasury +``` + +--- + +## Example 6: Multi-Collateral Stablecoin + +**Scenario:** A protocol wants to bootstrap liquidity by accepting multiple stablecoins (USDC, DAI, USDT) as collateral in addition to M. Yield from M backing goes to the treasury. + +**Requirements:** +- Single yield recipient +- Accept multiple collateral types +- 1:1 peg between all accepted assets +- Caps on non-M collateral + +**Recommendation:** `JMIExtension` + +**Why:** "Just Mint It" model allows users to deposit M or any approved stablecoin to mint tokens. Simplifies onboarding for users who don't yet hold M. + +**Configuration:** +```solidity +yieldRecipient = 0x...treasury + +// Allow USDC with 10M cap +setAssetCap(USDC, 10_000_000e6) + +// Allow DAI with 5M cap +setAssetCap(DAI, 5_000_000e18) +``` + +**Important Note:** Yield only accrues on the M portion of backing. Non-M collateral should be periodically swapped to M using `replaceAssetWithM()` to maximize yield. + +--- + +## Decision Summary + +| If you need... | Choose... | +|----------------|-----------| +| Simple treasury yield | MYieldToOne | +| Treasury yield + compliance | MYieldToOneForcedTransfer | +| Yield for all holders (L1) | MYieldFee | +| Yield for all holders (L2) | MSpokeYieldFee | +| Per-client fee arrangements | MEarnerManager | +| Multiple collateral types | JMIExtension | diff --git a/.claude/skills/choose-extension/SKILL.md b/.claude/skills/choose-extension/SKILL.md new file mode 100644 index 00000000..4bd80a78 --- /dev/null +++ b/.claude/skills/choose-extension/SKILL.md @@ -0,0 +1,84 @@ +--- +name: choose-extension +description: Helps developers choose the right M extension type for their use case. Use when someone asks which extension to use, wants to evaluate extension options, needs help deciding between MYieldToOne, MYieldFee, MEarnerManager, or other M extension types, or mentions "choose extension", "which extension", "extension for my use case". +--- + +# Choose Extension + +Help developers select the appropriate M extension type through an interactive decision process. + +## Instructions + +When this skill is invoked, guide the developer through selecting the right M extension by asking questions about their requirements. Use the `AskUserQuestion` tool to ask questions step by step. + +### Step 1: Yield Distribution Model + +First, ask about yield distribution: + +**Question:** "How should yield from the wrapped M tokens be distributed?" + +**Options:** +1. **Single recipient** - All yield goes to one address (treasury, DAO, protocol) +2. **All token holders** - Every holder earns yield proportionally +3. **Whitelisted holders only** - Only approved addresses can hold tokens and earn yield + +### Step 2: Follow-up Questions + +Based on the answer to Step 1, ask relevant follow-up questions: + +#### If "Single recipient": + +Ask: "Do you need compliance features like forced transfers to recover tokens from frozen/sanctioned accounts?" + +- **Yes, need forced transfers** - Ask about multi-asset collateral next +- **No forced transfers needed** - Ask about multi-asset collateral next + +Then ask: "Do you need to accept multiple collateral types (e.g., USDC, DAI) in addition to M?" + +- **Yes, multi-asset** - Recommend **JMIExtension** +- **No, M only** - If forced transfers needed: **MYieldToOneForcedTransfer**, otherwise: **MYieldToOne** + +#### If "All token holders": + +Ask: "Which chain are you deploying on?" + +- **Ethereum mainnet (L1)** - Recommend **MYieldFee** +- **Layer 2 (Arbitrum, Optimism, Base, etc.)** - Recommend **MSpokeYieldFee** + +#### If "Whitelisted holders only": + +No further questions needed. Recommend **MEarnerManager**. + +### Step 3: Provide Recommendation + +After determining the appropriate extension, provide: + +1. **The recommended extension** with a brief explanation +2. **Key features** of the recommended extension +3. **Required roles** to configure after deployment +4. **Link to the source file** for reference + +### Extension Quick Reference + +| Extension | Yield Model | Key Feature | +|-----------|-------------|-------------| +| `MYieldToOne` | Single recipient | Simple treasury model | +| `MYieldToOneForcedTransfer` | Single recipient | + Compliance/recovery | +| `MYieldFee` | All holders | Global fee, L1 | +| `MSpokeYieldFee` | All holders | Global fee, L2 | +| `MEarnerManager` | Whitelisted | Per-address fees | +| `JMIExtension` | Single recipient | Multi-asset collateral | + +### Source File Locations + +- `MYieldToOne`: `src/projects/yieldToOne/MYieldToOne.sol` +- `MYieldToOneForcedTransfer`: `src/projects/yieldToOne/MYieldToOneForcedTransfer.sol` +- `MYieldFee`: `src/projects/yieldToAllWithFee/MYieldFee.sol` +- `MSpokeYieldFee`: `src/projects/yieldToAllWithFee/MSpokeYieldFee.sol` +- `MEarnerManager`: `src/projects/earnerManager/MEarnerManager.sol` +- `JMIExtension`: `src/projects/jmi/JMIExtension.sol` + +## Supporting Documentation + +For detailed comparisons, see [COMPARISON.md](./COMPARISON.md). +For real-world use case examples, see [EXAMPLES.md](./EXAMPLES.md). diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..609babe2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,109 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +M Extensions Framework - A modular system for creating ERC-20 stablecoin extensions that wrap the yield-bearing `$M` token into non-rebasing variants for DeFi composability. All contracts deploy behind transparent upgradeable proxies. + +## Build Commands + +```bash +# Build +make build # Production build with sizes + +# Test +npm test # Run all tests +make fuzz # Fuzz tests only (5,000 runs default) +make integration # Integration tests only +make invariant # Invariant tests +make coverage # Generate lcov coverage report + +# Run specific test +forge test --match-test "testFunctionName" -vvv +forge test --match-contract "ContractName" -vvv + +# Code quality +npm run prettier # Format Solidity files +npm run solhint # Lint src/ files +npm run solhint-fix # Auto-fix linting issues +npm run slither # Static analysis + +# CI profile (more thorough) +make tests profile=ci # 10,000 fuzz runs, 250 invariant depth +``` + +## Architecture + +### Core Contract Hierarchy + +``` +MExtension (abstract base) +├── MYieldToOne - All yield to single recipient, with blacklist +├── MYieldToOneForcedTransfer - Adds forced transfer recovery from frozen accounts +├── MEarnerManager - Yield to all holders minus per-address fee, with whitelist +├── MYieldFee - Same yield rate for all, global fee deduction +├── MSpokeYieldFee - For L2s, index updates via bridging not time +└── JMIExtension - "Just Mint It" model with collateral deposits +``` + +### Swap Infrastructure + +- **SwapFacility**: Exclusive router for wrap/unwrap operations. Only SwapFacility can call `wrap()`/`unwrap()` on extensions. +- **UniswapV3SwapAdapter**: Helper for token swaps via Uniswap V3 SwapRouter02. + +### Component Modules (src/components/) + +Reusable behaviors that extensions can compose: +- `freezable/` - Account freezing mechanism +- `pausable/` - Pause/unpause functionality +- `forcedTransferable/` - Recovery mechanism for frozen accounts + +### Storage Pattern + +Uses ERC-7201 namespaced storage for upgradeable contracts. Storage structs are accessed via `_getStorage()` internal functions. + +## Testing + +Tests are in `test/unit/` and `test/integration/`. Base test setup in `test/utils/BaseUnitTest.sol` provides: +- Mock contracts (MockM, MockRateOracle, MockRegistrar) +- SwapFacility deployment with transparent proxy +- Standard test accounts (alice, bob, carol, etc.) +- Role constants (PAUSER_ROLE, FREEZE_MANAGER_ROLE, etc.) + +Test files use `.t.sol` suffix. Deployment scripts use `.s.sol` suffix. + +## Deployment + +Deployments use Foundry scripts in `script/deploy/`. Configuration in `script/Config.sol` defines per-chain settings. + +```bash +# Dry run (simulation only) +DRY_RUN=true make deploy-swap-facility-sepolia + +# Actual deployment (broadcasts and verifies) +make deploy-swap-facility-sepolia + +# Available deployment targets +make deploy-yield-to-one-{sepolia|mainnet} +make deploy-yield-to-one-forced-transfer-{sepolia|mainnet} +make deploy-swap-facility-{sepolia|mainnet|arbitrum|optimism|base|...} +make deploy-swap-adapter-{sepolia|mainnet|arbitrum} +``` + +Deployed addresses are stored in `deployments/{chainId}.json`. + +## Key Configuration + +- Solidity 0.8.26, Cancun EVM +- Optimizer: 19,999 runs +- Max line length: 120 chars +- Private variables: prefix with `_` +- FFI enabled for proxy deployments + +## Pre-commit Hooks + +Husky runs on commit: +1. Prettier formats changed .sol/.json/.md/.yml files +2. Solhint lints changed src/ and test/ .sol files +3. Full test suite runs