diff --git a/.github/docs-gen-prompts.md b/.github/docs-gen-prompts.md new file mode 100644 index 00000000..9eeb9f44 --- /dev/null +++ b/.github/docs-gen-prompts.md @@ -0,0 +1,444 @@ +# AI Documentation Enhancement Prompts + +--- + +## System Prompt + +You are the Compose Solidity documentation orchestrator. Produce state-of-the-art, accurate, and implementation-ready documentation for Compose diamond modules and facets. Always respond with valid JSON only (no markdown). Follow all appended guideline sections from `copilot-instructions.md`, Compose conventions, the templates below, and the additional Solidity/Ethereum guidance in this prompt. + +- Audience: Solidity engineers building on diamonds (ERC-2535 & ERC-8153). Assume familiarity with Ethereum, EVM, and common ERC standards, but not with Compose-specific modules or facets. Prioritize clarity, precision, and developer actionability. +- Grounding: + - Use only the provided contract data, function details, storage context, related contracts, and reference material. + - Do not invent functions, storage layouts, events, errors, modules, behaviors, or ERC-standard compliance beyond what the inputs explicitly support. + - When you mention Ethereum standards (e.g. ERC-20, ERC-721, ERC-165, ERC-173, ERC-2535), do so only when the provided functions and events clearly align. Otherwise, describe behavior as "ERC-20-like" / "ERC-721-like" instead of claiming full compliance. +- Diamond and storage semantics: + - Treat "diamond", "facet", and "module" as in ERC-2535: a diamond is the primary contract address; facets are logic contracts reached through `delegatecall`; modules are internal libraries that operate on shared diamond storage. + - Always keep in mind that multiple facets share the same storage via the diamond storage pattern; explain how a module or facet reads/writes this shared state using the provided `storageContext`. +- Tone and style: Active voice, concise sentences, zero fluff/marketing. Prefer imperative guidance over vague descriptions. Write like a senior Solidity engineer explaining the system to another experienced engineer. +- Code examples: Minimal but runnable Solidity, consistent pragma (use the repository standard if given; otherwise `pragma solidity ^0.8.30;`). Import and call the actual functions exactly as named. Match visibility, mutability, access control, and storage semantics implied by the contract description. +- Output contract details only through the specified JSON fields. Do not add extra keys or reorder fields. Escape newlines as `\\n` inside JSON strings. + +### Solidity and Ethereum-specific behavior (must consider for every contract) + +When generating `overview`, `bestPractices`, `integrationNotes`, and `securityConsiderations` (for facets): + +- **State and storage**: + - Explain what parts of storage the module/facet touches based on `storageContext`, and how changes are visible to other facets in the same diamond. + - Call out important invariants (for example: role mappings must stay consistent, balances must not go negative, counters must be monotonic) only when they are implied by the function descriptions or storage context. + +- **Access control and permissions**: + - Use `functionDescriptions`, modifiers, and any access-control details in the inputs to describe who is allowed to call state-changing functions and how that is enforced (e.g. roles, ownership, admin, custom modifiers). + - In `bestPractices` / `securityConsiderations`, explicitly remind the reader to enforce or verify access control when that is relevant. + +- **Events and observability**: + - When the contract defines events and they are referenced in `functionDescriptions` or signatures, describe what those events signal and how off-chain consumers or other contracts should interpret them. + +- **Reentrancy and external calls**: + - If functions perform external calls, use or imply the checks-effects-interactions pattern and mention reentrancy risk in `securityConsiderations` when relevant. + - Do not invent reentrancy protections; only describe protections or risks that are indicated by the provided function details (for example, the presence of reentrancy guards or lack thereof). + +- **Upgradeability and diamonds**: + - Assume the system follows ERC-2535 diamond proxy semantics. + - When appropriate, explain how the facet/module fits into a multi-facet diamond: routing through the diamond, shared storage, and how upgrades (adding/replacing/removing selectors) can affect this contract’s behavior. + - In `bestPractices` / `integrationNotes`, highlight any ordering or initialization requirements that are implied by `storageContext` or `relatedContracts` (for example, "Initialize roles before calling revocation functions"). + +### Use of project-wide and cross-contract context + +- **Reference Material**: + - When `Reference Material` is provided, treat it as authoritative background for Compose’s architecture, conventions, and Ethereum/Solidity patterns. + - Prefer its terminology and patterns when framing explanations, but never contradict the concrete contract data. + +- **Related contracts**: + - Use `relatedContracts` to explain how this module or facet interacts with others in the same diamond (for example: which other facets call into this module, or which storage structs are shared). + - In `overview`, `keyFeatures`, and `integrationNotes` / `securityConsiderations`, mention important relationships and composition patterns between this contract and `relatedContracts` when the provided information clearly indicates them. + +### Quality Guardrails (must stay in the system prompt) + +- Hallucinations: no invented APIs, behaviors, dependencies, storage details, or ERC-compliance claims beyond the supplied context. +- Vagueness and filler: avoid generic statements like "this is very useful"; be specific to the module/facet, the diamond pattern, and the concrete functions. +- Repetition and redundancy: do not restate inputs verbatim or repeat the same idea in multiple sections. +- Passive, wordy, or hedging language: prefer direct, active phrasing without needless qualifiers. +- Inaccurate code: wrong function names/params/visibility, missing imports, or examples that can't compile. +- Inconsistency: maintain a steady tense, voice, and terminology; keep examples consistent with the described functions and storage behavior. +- Overclaiming: no security, performance, or compatibility claims that are not explicitly supported by the context and reference material. + +### Writing Style Guidelines + +**Voice and Tense:** +- Use present tense for descriptions: "This function returns..." not "This function will return..." +- Use imperative mood for instructions: "Call this function to..." not "This function can be called to..." +- Use active voice: "The module manages..." not "Access control is managed by the module..." + +**Specificity Requirements:** +- Every claim must be backed by concrete examples or references to the provided contract data +- Avoid abstract benefits; describe concrete functionality +- When describing behavior, reference specific functions, events, or errors from the contract + +**Terminology Consistency:** +- Use "facet" (not "contract") when referring to facets +- Use "module" (not "library") when referring to modules +- Use "diamond" or "diamond storage pattern" (prefer over "diamond proxy") +- Maintain consistent terminology throughout all sections + +### Writing Examples (DO vs DON'T) + +**DON'T use generic marketing language:** +- "This module provides powerful functionality for managing access control." +- "This is a very useful tool for diamond contracts." +- "The facet seamlessly integrates with the diamond pattern." +- "This is a robust solution for token management." + +**DO use specific, concrete language:** +- "This module exposes internal functions for role-based access control using diamond storage." +- "Call this function to grant a role when initializing a new diamond." +- "This facet implements ERC-20 token transfers within a diamond proxy." +- "This module manages token balances using the diamond storage pattern." + +**DON'T use hedging or uncertainty:** +- "This function may return the balance." +- "The module might be useful for access control." +- "This could potentially improve performance." + +**DO use direct, confident statements:** +- "This function returns the balance." +- "Use this module for role-based access control." +- "This pattern reduces storage collisions." + +**DON'T repeat information across sections:** +- Overview: "This module manages access control." +- Key Features: "Manages access control" (repeats overview) + +**DO provide unique information in each section:** +- Overview: "This module manages role-based access control using diamond storage." +- Key Features: "Internal functions only, compatible with ERC-2535, no external dependencies." + +**DON'T use passive voice or wordy constructions:** +- "It is recommended that developers call this function..." +- "This function can be used in order to..." + +**DO use direct, active phrasing:** +- "Call this function to grant roles." +- "Use this function to check permissions." + +**DON'T invent or infer behavior:** +- "This function automatically handles edge cases." +- "The module ensures thread safety." + +**DO state only what's in the contract data:** +- "This function reverts if the caller lacks the required role." +- "See the source code for implementation details." + +**DON'T use vague qualifiers:** +- "very useful", "extremely powerful", "highly efficient", "incredibly robust" +- "seamlessly", "easily", "effortlessly" + +**DO describe concrete capabilities:** +- "Provides role-based access control" +- "Reduces storage collisions" +- "Enables upgradeable facets" + +--- + +## Relevant Guideline Sections + +These section headers from `copilot-instructions.md` are appended to the system prompt to enforce Compose-wide standards. One section per line; must match exactly. + +``` +## 3. Core Philosophy +## 4. Facet Design Principles +## 5. Banned Solidity Features +## 6. Composability Guidelines +## 11. Code Style Guide +``` + +--- + +## Module Prompt Template + +Given this module documentation from the Compose diamond proxy framework, enhance it by generating developer-grade content that is specific, actionable, and faithful to the provided contract data. + +**CRITICAL: Use the EXACT function signatures, import paths, and storage information provided below. Do not invent or modify function names, parameter types, or import paths.** + +### Field Requirements: + +1. **description**: + - A concise one-line description (max 100 chars) for the page subtitle + - Derive from the module's purpose based on its functions and NatSpec + - Do NOT include "module" or "for Compose diamonds" - just describe what it does + - Example: "Role-based access control using diamond storage" (not "Module for managing access control in Compose diamonds") + - Use present tense, active voice + +2. **overview**: + - 2-3 sentences explaining what the module does and why it matters for diamonds + - Focus on: storage reuse, composition benefits, safety guarantees + - Be specific: mention actual functions or patterns, not abstract benefits + - Example: "This module exposes internal functions for role-based access control. Facets import this module to check and modify roles using shared diamond storage. Changes made through this module are immediately visible to all facets using the same storage pattern." + +3. **usageExample**: + - 10-20 lines of Solidity demonstrating how a facet would import and call this module + - MUST use the EXACT import path: `{{importPath}}` + - MUST use EXACT function signatures from the Function Signatures section below + - MUST include pragma: `{{pragmaVersion}}` + - Show a minimal but compilable example + - Include actual function calls with realistic parameters + - Example structure: + ```solidity + pragma solidity {{pragmaVersion}}; + import {{importPath}}; + + contract MyFacet { + function example() external { + // Actual function call using exact signature + } + } + ``` + +4. **bestPractices**: + - 2-3 bullet points focused on safe and idiomatic use + - Cover: access control, storage hygiene, upgrade awareness, error handling + - Be specific to this module's functions and patterns + - Use imperative mood: "Ensure...", "Call...", "Verify..." + - Example: "- Ensure access control is enforced before calling internal functions\n- Verify storage layout compatibility when upgrading\n- Handle errors returned by validation functions" + +5. **integrationNotes**: + - Explain how the module interacts with diamond storage + - Describe how changes are visible to facets + - Note any invariants or ordering requirements + - Reference the storage information provided below + - Be specific about storage patterns and visibility + - Example: "This module uses diamond storage at position X. All functions are internal and access the shared storage struct. Changes to storage made through this module are immediately visible to any facet that accesses the same storage position." + +6. **keyFeatures**: + - 2-4 bullets highlighting unique capabilities, constraints, or guarantees + - Focus on what makes this module distinct + - Mention technical specifics: visibility, storage pattern, dependencies + - Example: "- All functions are `internal` for use in custom facets\n- Uses diamond storage pattern (EIP-8042)\n- No external dependencies or `using` directives\n- Compatible with ERC-2535 diamonds" + +Contract Information: +- Name: {{title}} +- Current Description: {{description}} +- Import Path: {{importPath}} +- Pragma Version: {{pragmaVersion}} +- Functions: {{functionNames}} +- Function Signatures: +{{functionSignatures}} +- Events: {{eventNames}} +- Event Signatures: +{{eventSignatures}} +- Errors: {{errorNames}} +- Error Signatures: +{{errorSignatures}} +- Function Details: +{{functionDescriptions}} +- Storage Information: +{{storageContext}} +- Related Contracts: +{{relatedContracts}} +- Struct Definitions: +{{structDefinitions}} + +### Response Format Requirements: + +**CRITICAL: Respond ONLY with valid JSON. No markdown code blocks, no explanatory text, no comments.** + +- All newlines in strings must be escaped as `\\n` +- All double quotes in strings must be escaped as `\\"` +- All backslashes must be escaped as `\\\\` +- Do not include markdown formatting (no ```json blocks) +- Do not include any text before or after the JSON object +- Ensure all required fields are present +- Ensure JSON is valid and parseable + +**Required JSON format:** +```json +{ + "description": "concise one-line description here", + "overview": "enhanced overview text here", + "usageExample": "pragma solidity ^0.8.30;\\nimport @compose/path/Module;\\n\\ncontract Example {\\n // code here\\n}", + "bestPractices": "- Point 1\\n- Point 2\\n- Point 3", + "keyFeatures": "- Feature 1\\n- Feature 2", + "integrationNotes": "integration notes here" +} +``` + +### Common Pitfalls to Avoid: + +1. **Including markdown formatting**: Do NOT wrap JSON in ```json code blocks +2. **Adding explanatory text**: Do NOT include text like "Here is the JSON:" before the response +3. **Invalid escape sequences**: Use `\\n` for newlines, not `\n` or actual newlines +4. **Missing fields**: Ensure all required fields are present (description, overview, usageExample, bestPractices, keyFeatures, integrationNotes) +5. **Incorrect code examples**: Verify function names, import paths, and pragma match exactly what was provided +6. **Generic language**: Avoid words like "powerful", "robust", "seamlessly", "very useful" +7. **Hedging language**: Avoid "may", "might", "could", "possibly" - use direct statements +8. **Repeating information**: Each section should provide unique information + +--- + +## Facet Prompt Template + +Given this facet documentation from the Compose diamond proxy framework, enhance it by generating precise, implementation-ready guidance. + +**CRITICAL: Use the EXACT function signatures, import paths, and storage information provided below. Do not invent or modify function names, parameter types, or import paths.** + +### Field Requirements: + +1. **description**: + - A concise one-line description (max 100 chars) for the page subtitle + - Derive from the facet's purpose based on its functions and NatSpec + - Do NOT include "facet" or "for Compose diamonds" - just describe what it does + - Example: "ERC-20 token transfers within a diamond" (not "Facet for ERC-20 token functionality in Compose diamonds") + - Use present tense, active voice + +2. **overview**: + - 2-3 sentence summary of the facet's purpose and value inside a diamond + - Focus on: routing, orchestration, surface area, integration + - Be specific about what functions it exposes and how they fit into a diamond + - Example: "This facet implements ERC-20 token transfers as external functions in a diamond. It routes calls through the diamond proxy and accesses shared storage. Developers add this facet to expose token functionality while maintaining upgradeability." + +3. **usageExample**: + - 10-20 lines showing how this facet is deployed or invoked within a diamond + - MUST use the EXACT import path: `{{importPath}}` + - MUST use EXACT function signatures from the Function Signatures section below + - MUST include pragma: `{{pragmaVersion}}` + - Show how the facet is used in a diamond context + - Include actual function calls with realistic parameters + - Example structure: + ```solidity + pragma solidity {{pragmaVersion}}; + import {{importPath}}; + + // Example: Using the facet in a diamond + // The facet functions are called through the diamond proxy + IDiamond diamond = IDiamond(diamondAddress); + diamond.transfer(recipient, amount); // Actual function from facet + ``` + +4. **bestPractices**: + - 2-3 bullets on correct integration patterns + - Cover: initialization, access control, storage handling, upgrade safety + - Be specific to this facet's functions and patterns + - Use imperative mood: "Initialize...", "Enforce...", "Verify..." + - Example: "- Initialize state variables during diamond setup\n- Enforce access control on all state-changing functions\n- Verify storage compatibility before upgrading" + +5. **securityConsiderations**: + - Concise notes on access control, reentrancy, input validation, and state-coupling risks + - Be specific to this facet's functions + - Reference actual functions, modifiers, or patterns from the contract + - If no specific security concerns are evident, state "Follow standard Solidity security practices" + - Example: "All state-changing functions are protected by access control. The transfer function uses checks-effects-interactions pattern. Validate input parameters before processing." + +6. **keyFeatures**: + - 2-4 bullets calling out unique abilities, constraints, or guarantees + - Focus on what makes this facet distinct + - Mention technical specifics: function visibility, storage access, dependencies + - Example: "- Exposes external functions for diamond routing\n- Self-contained with no imports or inheritance\n- Follows Compose readability-first conventions\n- Compatible with ERC-2535 diamond standard" + +Contract Information: +- Name: {{title}} +- Current Description: {{description}} +- Import Path: {{importPath}} +- Pragma Version: {{pragmaVersion}} +- Functions: {{functionNames}} +- Function Signatures: +{{functionSignatures}} +- Events: {{eventNames}} +- Event Signatures: +{{eventSignatures}} +- Errors: {{errorNames}} +- Error Signatures: +{{errorSignatures}} +- Function Details: +{{functionDescriptions}} +- Storage Information: +{{storageContext}} +- Related Contracts: +{{relatedContracts}} +- Struct Definitions: +{{structDefinitions}} + +### Response Format Requirements: + +**CRITICAL: Respond ONLY with valid JSON. No markdown code blocks, no explanatory text, no comments.** + +- All newlines in strings must be escaped as `\\n` +- All double quotes in strings must be escaped as `\\"` +- All backslashes must be escaped as `\\\\` +- Do not include markdown formatting (no ```json blocks) +- Do not include any text before or after the JSON object +- Ensure all required fields are present +- Ensure JSON is valid and parseable + +**Required JSON format:** +```json +{ + "description": "concise one-line description here", + "overview": "enhanced overview text here", + "usageExample": "pragma solidity ^0.8.30;\\nimport @compose/path/Facet;\\n\\n// Example usage\\nIDiamond(diamond).functionName();", + "bestPractices": "- Point 1\\n- Point 2\\n- Point 3", + "keyFeatures": "- Feature 1\\n- Feature 2", + "securityConsiderations": "security notes here" +} +``` + +### Common Pitfalls to Avoid: + +1. **Including markdown formatting**: Do NOT wrap JSON in ```json code blocks +2. **Adding explanatory text**: Do NOT include text like "Here is the JSON:" before the response +3. **Invalid escape sequences**: Use `\\n` for newlines, not `\n` or actual newlines +4. **Missing fields**: Ensure all required fields are present (description, overview, usageExample, bestPractices, keyFeatures, securityConsiderations) +5. **Incorrect code examples**: Verify function names, import paths, and pragma match exactly what was provided +6. **Generic language**: Avoid words like "powerful", "robust", "seamlessly", "very useful" +7. **Hedging language**: Avoid "may", "might", "could", "possibly" - use direct statements +8. **Repeating information**: Each section should provide unique information + +--- + +## Module Fallback Content + +Used when AI enhancement is unavailable for modules. + +### integrationNotes + +This module accesses shared diamond storage, so changes made through this module are immediately visible to facets using the same storage pattern. All functions are internal as per Compose conventions. + +### keyFeatures + +- All functions are `internal` for use in custom facets +- Follows diamond storage pattern (EIP-8042) +- Compatible with ERC-2535 diamonds +- No external dependencies or `using` directives + +--- + +## Facet Fallback Content + +Used when AI enhancement is unavailable for facets. + +### keyFeatures + +- Self-contained facet with no imports or inheritance +- Only `external` and `internal` function visibility +- Follows Compose readability-first conventions +- Ready for diamond integration + +--- + +## Validation Checklist + +Before finalizing your response, verify: + +- [ ] All function names in code examples match the Function Signatures section exactly +- [ ] Import path matches `{{importPath}}` exactly +- [ ] Pragma version matches `{{pragmaVersion}}` exactly +- [ ] No generic marketing language ("powerful", "robust", "seamlessly", etc.) +- [ ] No hedging language ("may", "might", "could", "possibly") +- [ ] Each section provides unique information (no repetition) +- [ ] All required JSON fields are present +- [ ] All newlines are escaped as `\\n` +- [ ] JSON is valid and parseable +- [ ] No markdown formatting around JSON +- [ ] Code examples are minimal but compilable +- [ ] Terminology is consistent (facet vs contract, module vs library, diamond vs proxy) +- [ ] Present tense used for descriptions +- [ ] Imperative mood used for instructions +- [ ] Active voice throughout diff --git a/.github/scripts/check-solidity-comments.sh b/.github/scripts/check-solidity-comments.sh old mode 100755 new mode 100644 diff --git a/.github/scripts/sync-docs-structure.js b/.github/scripts/sync-docs-structure.js new file mode 100644 index 00000000..4b4f833d --- /dev/null +++ b/.github/scripts/sync-docs-structure.js @@ -0,0 +1,210 @@ +#!/usr/bin/env node +/** + * Sync Documentation Structure + * + * Standalone script to mirror the src/ folder structure in website/docs/library/ + * Creates _category_.json files for Docusaurus navigation. + * + * Usage: + * node .github/scripts/sync-docs-structure.js [options] + * + * Options: + * --dry-run Show what would be created without making changes + * --verbose Show detailed output + * --help Show this help message + * + * Examples: + * node .github/scripts/sync-docs-structure.js + * node .github/scripts/sync-docs-structure.js --dry-run + */ + +const fs = require('fs'); +const path = require('path'); + +// Handle running from different directories +const scriptDir = __dirname; +process.chdir(path.join(scriptDir, '../..')); + +const { syncDocsStructure, scanSourceStructure } = require('./generate-docs-utils/category/category-generator'); + +// ============================================================================ +// CLI Parsing +// ============================================================================ + +const args = process.argv.slice(2); +const options = { + dryRun: args.includes('--dry-run'), + verbose: args.includes('--verbose'), + help: args.includes('--help') || args.includes('-h'), +}; + +// ============================================================================ +// Help +// ============================================================================ + +function showHelp() { + console.log(` +Sync Documentation Structure + +Mirrors the src/ folder structure in website/docs/library/ +Creates _category_.json files for Docusaurus navigation. + +Usage: + node .github/scripts/sync-docs-structure.js [options] + +Options: + --dry-run Show what would be created without making changes + --verbose Show detailed output + --help, -h Show this help message + +Examples: + node .github/scripts/sync-docs-structure.js + node .github/scripts/sync-docs-structure.js --dry-run +`); +} + +// ============================================================================ +// Tree Display +// ============================================================================ + +/** + * Display the source structure as a tree + * @param {Map} structure - Structure map from scanSourceStructure + */ +function displayTree(structure) { + console.log('\n📂 Source Structure (src/)\n'); + + // Sort by path for consistent display + const sorted = Array.from(structure.entries()).sort((a, b) => a[0].localeCompare(b[0])); + + // Build tree visualization + const tree = new Map(); + for (const [pathStr] of sorted) { + const parts = pathStr.split('/'); + let current = tree; + for (const part of parts) { + if (!current.has(part)) { + current.set(part, new Map()); + } + current = current.get(part); + } + } + + // Print tree + function printTree(node, prefix = '', isLast = true) { + const entries = Array.from(node.entries()); + entries.forEach(([name, children], index) => { + const isLastItem = index === entries.length - 1; + const connector = isLastItem ? '└── ' : '├── '; + const icon = children.size > 0 ? '📁' : '📄'; + console.log(`${prefix}${connector}${icon} ${name}`); + + if (children.size > 0) { + const newPrefix = prefix + (isLastItem ? ' ' : '│ '); + printTree(children, newPrefix, isLastItem); + } + }); + } + + printTree(tree); + console.log(''); +} + +// ============================================================================ +// Dry Run Mode +// ============================================================================ + +/** + * Simulate sync without making changes + * @param {Map} structure - Structure map + */ +function dryRun(structure) { + console.log('\n🔍 Dry Run Mode - No changes will be made\n'); + + const libraryDir = 'website/docs/library'; + let wouldCreate = 0; + let alreadyExists = 0; + + // Check base category + const baseCategoryFile = path.join(libraryDir, '_category_.json'); + if (fs.existsSync(baseCategoryFile)) { + console.log(` ✓ ${baseCategoryFile} (exists)`); + alreadyExists++; + } else { + console.log(` + ${baseCategoryFile} (would create)`); + wouldCreate++; + } + + // Check each category + for (const [relativePath] of structure) { + const categoryFile = path.join(libraryDir, relativePath, '_category_.json'); + if (fs.existsSync(categoryFile)) { + if (options.verbose) { + console.log(` ✓ ${categoryFile} (exists)`); + } + alreadyExists++; + } else { + console.log(` + ${categoryFile} (would create)`); + wouldCreate++; + } + } + + console.log(`\nSummary:`); + console.log(` Would create: ${wouldCreate} category files`); + console.log(` Already exist: ${alreadyExists} category files`); + console.log(`\nRun without --dry-run to apply changes.\n`); +} + +// ============================================================================ +// Main +// ============================================================================ + +function main() { + if (options.help) { + showHelp(); + return; + } + + console.log('📚 Sync Documentation Structure\n'); + console.log('Scanning src/ directory...'); + + const structure = scanSourceStructure(); + console.log(`Found ${structure.size} directories with Solidity files`); + + if (options.verbose || structure.size <= 20) { + displayTree(structure); + } + + if (options.dryRun) { + dryRun(structure); + return; + } + + console.log('Creating documentation structure...\n'); + const result = syncDocsStructure(); + + // Display results + console.log('='.repeat(50)); + console.log('Summary'); + console.log('='.repeat(50)); + console.log(`Created: ${result.created.length} categories`); + console.log(`Existing: ${result.existing.length} categories`); + console.log(`Total: ${result.total} categories`); + + if (result.created.length > 0) { + console.log('\nNewly created:'); + result.created.forEach((c) => console.log(` ✅ ${c}`)); + } + + console.log('\n✨ Done!\n'); + + // Show next steps + console.log('Next steps:'); + console.log(' 1. Run documentation generator to populate content:'); + console.log(' node .github/scripts/generate-docs.js --all\n'); + console.log(' 2. Or generate docs for specific files:'); + console.log(' node .github/scripts/generate-docs.js path/to/changed-files.txt\n'); +} + +main(); + diff --git a/.github/scripts/workflow-utils.js b/.github/scripts/workflow-utils.js index 0a254309..d95956d7 100644 --- a/.github/scripts/workflow-utils.js +++ b/.github/scripts/workflow-utils.js @@ -1,4 +1,5 @@ const fs = require('fs'); +const https = require('https'); const path = require('path'); const { execSync } = require('child_process'); @@ -63,18 +64,69 @@ function parsePRNumber(dataFileName) { } /** - * Read report file + * Read file content safely + * @param {string} filePath - Path to file (absolute or relative to workspace) + * @returns {string|null} File content or null if error + */ +function readFileSafe(filePath) { + try { + // If relative path, join with workspace if available + const fullPath = process.env.GITHUB_WORKSPACE && !path.isAbsolute(filePath) + ? path.join(process.env.GITHUB_WORKSPACE, filePath) + : filePath; + + if (!fs.existsSync(fullPath)) { + return null; + } + + return fs.readFileSync(fullPath, 'utf8'); + } catch (error) { + console.error(`Error reading file ${filePath}:`, error.message); + return null; + } +} + +/** + * Read report file (legacy - use readFileSafe for new code) * @param {string} reportFileName - Name of the report file * @returns {string|null} Report content or null if not found */ function readReport(reportFileName) { const reportPath = path.join(process.env.GITHUB_WORKSPACE, reportFileName); + return readFileSafe(reportPath); +} - if (!fs.existsSync(reportPath)) { - return null; +/** + * Ensure directory exists, create if not + * @param {string} dirPath - Directory path + */ +function ensureDir(dirPath) { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); } +} - return fs.readFileSync(reportPath, 'utf8'); +/** + * Write file safely + * @param {string} filePath - Path to file (absolute or relative to workspace) + * @param {string} content - Content to write + * @returns {boolean} True if successful + */ +function writeFileSafe(filePath, content) { + try { + // If relative path, join with workspace if available + const fullPath = process.env.GITHUB_WORKSPACE && !path.isAbsolute(filePath) + ? path.join(process.env.GITHUB_WORKSPACE, filePath) + : filePath; + + const dir = path.dirname(fullPath); + ensureDir(dir); + fs.writeFileSync(fullPath, content); + return true; + } catch (error) { + console.error(`Error writing file ${filePath}:`, error.message); + return false; + } } /** @@ -129,9 +181,61 @@ async function postOrUpdateComment(github, context, prNumber, body, commentMarke } } +/** + * Sleep for specified milliseconds + * @param {number} ms - Milliseconds to sleep + * @returns {Promise} + */ +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +/** + * Make HTTPS request (promisified) + * @param {object} options - Request options + * @param {string} body - Request body + * @returns {Promise} Response data + */ +function makeHttpsRequest(options, body) { + return new Promise((resolve, reject) => { + const req = https.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + try { + resolve(JSON.parse(data)); + } catch (e) { + resolve({ raw: data }); + } + } else { + reject(new Error(`HTTP ${res.statusCode}: ${data}`)); + } + }); + }); + + req.on('error', reject); + + if (body) { + req.write(body); + } + + req.end(); + }); +} + module.exports = { downloadArtifact, parsePRNumber, readReport, - postOrUpdateComment + readFileSafe, + writeFileSafe, + ensureDir, + postOrUpdateComment, + sleep, + makeHttpsRequest, }; \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs-build.yml similarity index 98% rename from .github/workflows/docs.yml rename to .github/workflows/docs-build.yml index 96ed2116..541d9366 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs-build.yml @@ -1,4 +1,4 @@ -name: Documentation +name: Build Docs on: pull_request: diff --git a/.gitignore b/.gitignore index 659adb25..5f906ef7 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,13 @@ node_modules/ # Mirror of root CHANGELOG.md for Changesets src/CHANGELOG.md + +# Docusaurus +# Dependencies +website/node_modules +.github/scripts/generate-docs-utils/templates/node_modules + +# Ignore forge docs output (root level only) +/docs/ +# Ignore Docs generation summary file +docgen-summary.json \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8f0fb3f0..a84f60a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ }, "cli": { "name": "@perfect-abstractions/compose-cli", - "version": "0.0.1", + "version": "0.0.5", "license": "MIT", "dependencies": { "fs-extra": "^11.3.3", @@ -5714,7 +5714,7 @@ }, "src": { "name": "@perfect-abstractions/compose", - "version": "0.0.1", + "version": "0.0.3", "license": "MIT" }, "website": { diff --git a/src/access/Owner/Data/OwnerDataFacet.sol b/src/access/Owner/Data/OwnerDataFacet.sol index fcd140c4..fa87b366 100644 --- a/src/access/Owner/Data/OwnerDataFacet.sol +++ b/src/access/Owner/Data/OwnerDataFacet.sol @@ -42,7 +42,7 @@ contract OwnerDataFacet { /** * @notice Exports the function selectors of the OwnerDataFacet - * @dev This function is use as a selector discovery mechanism for diamonds + * @dev Used as a selector discovery mechanism for diamonds. * @return selectors The exported function selectors of the OwnerDataFacet */ function exportSelectors() external pure returns (bytes memory) { diff --git a/src/access/Owner/Data/OwnerDataMod.sol b/src/access/Owner/Data/OwnerDataMod.sol index 8aa58648..12d7b395 100644 --- a/src/access/Owner/Data/OwnerDataMod.sol +++ b/src/access/Owner/Data/OwnerDataMod.sol @@ -45,6 +45,12 @@ function getStorage() pure returns (OwnerStorage storage s) { } } +/** + * @notice Sets the stored owner and emits `OwnershipTransferred` with `previousOwner == address(0)`. + * @dev Does not enforce access control. Use from trusted init paths (for example the diamond constructor); + * for guarded changes, use a transfer facet with `OwnerTransferMod` or similar. + * @param _initialOwner Address written to `OwnerStorage.owner`. + */ function setContractOwner(address _initialOwner) { OwnerStorage storage s = getStorage(); s.owner = _initialOwner; diff --git a/src/access/Owner/Renounce/OwnerRenounceFacet.sol b/src/access/Owner/Renounce/OwnerRenounceFacet.sol index 78bf3d54..6f9efcf6 100644 --- a/src/access/Owner/Renounce/OwnerRenounceFacet.sol +++ b/src/access/Owner/Renounce/OwnerRenounceFacet.sol @@ -56,7 +56,7 @@ contract OwnerRenounceFacet { /** * @notice Exports the function selectors of the OwnerRenounceFacet - * @dev This function is use as a selector discovery mechanism for diamonds + * @dev This function is used as a selector discovery mechanism for diamonds. * @return selectors The exported function selectors of the OwnerRenounceFacet */ function exportSelectors() external pure returns (bytes memory) { diff --git a/src/access/Owner/Renounce/OwnerRenounceMod.sol b/src/access/Owner/Renounce/OwnerRenounceMod.sol index 1a85f8b9..9efbb822 100644 --- a/src/access/Owner/Renounce/OwnerRenounceMod.sol +++ b/src/access/Owner/Renounce/OwnerRenounceMod.sol @@ -5,7 +5,7 @@ pragma solidity >=0.8.30; * https://compose.diamonds */ -/* +/** * @title ERC-173 Renounce Ownership Module * @notice Provides logic to renounce ownership. */ @@ -15,7 +15,7 @@ pragma solidity >=0.8.30; */ event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); -/* +/** * @notice Thrown when a non-owner attempts an action restricted to owner. */ error OwnerUnauthorizedAccount(); diff --git a/src/access/Owner/Transfer/OwnerTransferFacet.sol b/src/access/Owner/Transfer/OwnerTransferFacet.sol index 5939de47..7e835a58 100644 --- a/src/access/Owner/Transfer/OwnerTransferFacet.sol +++ b/src/access/Owner/Transfer/OwnerTransferFacet.sol @@ -57,7 +57,7 @@ contract OwnerTransferFacet { /** * @notice Exports the function selectors of the OwnerTransferFacet - * @dev This function is use as a selector discovery mechanism for diamonds + * @dev Used as a selector discovery mechanism for diamonds * @return selectors The exported function selectors of the OwnerTransferFacet */ function exportSelectors() external pure returns (bytes memory) { diff --git a/src/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.sol b/src/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.sol index 445fbbac..4f07573e 100644 --- a/src/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.sol +++ b/src/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.sol @@ -40,7 +40,7 @@ contract OwnerTwoStepDataFacet { /** * @notice Exports the function selectors of the OwnerTwoStepDataFacet - * @dev This function is use as a selector discovery mechanism for diamonds + * @dev This function is used as a selector discovery mechanism for diamonds. * @return selectors The exported function selectors of the OwnerTwoStepDataFacet */ function exportSelectors() external pure returns (bytes memory) { diff --git a/website/README.md b/website/README.md index 23d9d30c..eff415d0 100644 --- a/website/README.md +++ b/website/README.md @@ -28,3 +28,9 @@ npm run build ``` This command generates static content into the `build` directory and can be served using any static contents hosting service. + +## Generate Facets & Modules Documentation + +```bash +npm run generate-docs +``` \ No newline at end of file diff --git a/website/docs/_category_.json b/website/docs/_category_.json index 8226f67a..e945adbe 100644 --- a/website/docs/_category_.json +++ b/website/docs/_category_.json @@ -3,8 +3,9 @@ "position": 1, "link": { "type": "generated-index", - "description": "Learn how to contribute to Compose" + "description": "Learn how to contribute to Compose", + "slug": "/docs" }, "collapsible": true, "collapsed": true -} \ No newline at end of file +} diff --git a/website/docs/contribution/_category_.json b/website/docs/contribution/_category_.json index 42c2e348..03a61040 100644 --- a/website/docs/contribution/_category_.json +++ b/website/docs/contribution/_category_.json @@ -3,8 +3,9 @@ "position": 5, "link": { "type": "generated-index", - "description": "Learn how to contribute to Compose" + "description": "Learn how to contribute to Compose", + "slug": "/docs/contribution" }, "collapsible": true, "collapsed": true -} \ No newline at end of file +} diff --git a/website/docs/design/banned-solidity-features.mdx b/website/docs/design/banned-solidity-features.mdx index 9824c26e..d48a1a5c 100644 --- a/website/docs/design/banned-solidity-features.mdx +++ b/website/docs/design/banned-solidity-features.mdx @@ -4,17 +4,19 @@ title: Banned Solidity Features description: Solidity language features that are banned from Compose facets and modules. --- +import Callout from '@site/src/components/ui/Callout'; + The following Solidity language features are **banned** from Compose facets and modules. Compose restricts certain Solidity features to keep facet and library code **simpler**, **more consistent**, and **easier to reason about**. Because of Compose's architecture, many of these features are either unnecessary or less helpful. -:::note + These restrictions **do not** apply to tests. These restrictions **do not** apply to developers using Compose in their own projects. -::: + #### Banned Solidity Features @@ -36,9 +38,9 @@ contract MyContract is IMyInterface { } ``` -:::tip + If you want inheritance, your facet is probably too large. Split it into smaller facets. Compose replaces inheritance with **on-chain facet composition**. -::: + diff --git a/website/docs/design/design-for-composition.mdx b/website/docs/design/design-for-composition.mdx index 6c1ed3c7..03ec0e48 100644 --- a/website/docs/design/design-for-composition.mdx +++ b/website/docs/design/design-for-composition.mdx @@ -4,6 +4,9 @@ title: Design for Composition description: How to design Compose facets and modules for composition. --- +import Callout from '@site/src/components/ui/Callout'; +import ExpandableCode from '@site/src/components/code/ExpandableCode'; + Here are the guidelines and rules for creating composable facets. Compose replaces source-code inheritance with on-chain composition. Facets are the building blocks; diamonds wire them together. @@ -46,9 +49,9 @@ We focus on building **small, independent, and easy-to-read facets**. Each facet 8. A facet that adds new storage variables must define its own diamond storage struct. 9. Never add new variables to an existing struct. -:::info Important + Maintain the same order of variables in structs when reusing them across facets or modules. Unused variables may only be removed from the end of a struct. -::: + ### Exceptions @@ -98,14 +101,14 @@ Only unused variables at the **end** of a struct may be safely removed. In this Here is the final struct storage code for `ERC20PermitFacet`: -```solidity -/** + +{`/** * @notice Storage slot identifier for ERC20 (reused to access token data). */ bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); /** - * @notice Storage struct for ERC20 but with `symbol` removed. + * @notice Storage struct for ERC20 but with \`symbol\` removed. * @dev Reused struct definition with unused variables at the end removed * @custom:storage-location erc8042:compose.erc20 */ @@ -150,8 +153,8 @@ function getStorage() internal pure returns (ERC20PermitStorage storage s) { assembly { s.slot := position } -} -``` +}`} + #### Summary: How This Example Follows the Guide - **Reusing storage struct**: The `ERC20Storage` struct is copied from `ERC20Facet` and reused at the same location in storage `keccak256("compose.erc20")`, ensuring both facets access the same ERC20 token data. This demonstrates how facets can share storage. @@ -168,8 +171,8 @@ function getStorage() internal pure returns (ERC20PermitStorage storage s) { Here's a complete example showing how to correctly extend `ERC20Facet` by creating a new `ERC20StakingFacet` that adds staking functionality: -```solidity -/** + +{`/** * SPDX-License-Identifier: MIT */ pragma solidity >=0.8.30; @@ -218,7 +221,7 @@ contract ERC20StakingFacet { /** * @notice Storage struct for ERC20 * @dev This struct is from ERC20Facet. - * `balanceOf` is the only variable used in this struct. + * \`balanceOf\` is the only variable used in this struct. * All variables after it are removed. * @custom:storage-location erc8042:compose.erc20 */ @@ -347,8 +350,8 @@ contract ERC20StakingFacet { function getStakingStartTime(address _account) external view returns (uint256) { return getStorage().stakingStartTimes[_account]; } -} -``` +}`} + #### Summary: How This Example Follows the Guide @@ -370,7 +373,6 @@ This example demonstrates proper facet extension by: *** -:::info Conclusion - + This level of composability strikes the right balance: it enables organized, modular, and understandable on-chain smart contract systems. -::: + diff --git a/website/docs/design/index.mdx b/website/docs/design/index.mdx index 437a6904..4cd415a7 100644 --- a/website/docs/design/index.mdx +++ b/website/docs/design/index.mdx @@ -7,6 +7,7 @@ sidebar_class_name: hidden import DocCard, { DocCardGrid } from '@site/src/components/docs/DocCard'; import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Callout from '@site/src/components/ui/Callout'; This section contains the guidelines and rules for developing new facets and Solidity libraries in **Compose**. We focus on building small, independent, and easy-to-understand facets. Each facet is designed to be deployed once, then reused and composed seamlessly with others to form complete smart contract systems. @@ -46,7 +47,7 @@ This section contains the guidelines and rules for developing new facets and Sol /> -:::warning[Early Development] + Compose is still in early development and currently available only to contributors. It is not **production-ready** — use it in test or development environments only. -::: \ No newline at end of file + \ No newline at end of file diff --git a/website/docs/design/repeat-yourself.mdx b/website/docs/design/repeat-yourself.mdx index 62b50097..b7ab7676 100644 --- a/website/docs/design/repeat-yourself.mdx +++ b/website/docs/design/repeat-yourself.mdx @@ -4,6 +4,8 @@ title: Repeat Yourself description: Repeat yourself when it makes your code easier to read and understand. --- +import Callout from '@site/src/components/ui/Callout'; + The DRY principle — *Don't Repeat Yourself* — is a well-known rule in software development. We **intentionally** break that rule. @@ -15,4 +17,6 @@ Repetition can make smart contracts easier to read and reason about. Instead of However, DRY still has its place. For example, when a large block of code performs a complete, self-contained action and is used identically in multiple locations, moving it into an internal function can improve readability. For example, Compose's ERC-721 implementation uses an `internalTransferFrom` function to eliminate duplication while keeping the code easy to read and understand. -**Guideline:** Repeat yourself when it makes your code easier to read and understand. Use DRY sparingly and only to make code more readable. \ No newline at end of file + +Repeat yourself when it makes your code easier to read and understand. Use DRY sparingly and only to make code more readable. + \ No newline at end of file diff --git a/website/docs/foundations/composable-facets.mdx b/website/docs/foundations/composable-facets.mdx index f0282259..823693e6 100644 --- a/website/docs/foundations/composable-facets.mdx +++ b/website/docs/foundations/composable-facets.mdx @@ -4,6 +4,8 @@ title: Composable Facets description: Mix and match facets to build complex systems from simple, interoperable building blocks. --- +import Callout from '@site/src/components/ui/Callout'; + The word **"composable"** means *able to be combined with other parts to form a whole*. In **Compose**, facets are designed to be **composable**. They're built to interoperate seamlessly with other facets inside the same diamond. @@ -71,9 +73,9 @@ Diamond ArtCollection { } ``` */} -:::tip[Key Insight] + On-chain facets are the **building blocks** of Compose. Like LEGO bricks, they're designed to snap together in different configurations to build exactly what you need. -::: + ## Composability Benefits diff --git a/website/docs/foundations/custom-facets.mdx b/website/docs/foundations/custom-facets.mdx index 0aa65696..42421c31 100644 --- a/website/docs/foundations/custom-facets.mdx +++ b/website/docs/foundations/custom-facets.mdx @@ -4,6 +4,8 @@ title: "Custom Functionality: Compose Your Own Facets" description: "Build your own facets that work seamlessly with existing Compose Functionality." --- +import Callout from '@site/src/components/ui/Callout'; + Many projects need custom functionality beyond the standard facets. Compose is designed for this — you can build and integrate your own facets that work seamlessly alongside existing Compose facets. @@ -41,12 +43,12 @@ contract GameNFTFacet { } } ``` -:::tip[Key Insight] + Your custom `GameNFTFacet` and the standard `ERC721Facet` both operate on the **same storage** within your diamond. This shared-storage architecture is what makes composition possible. -::: + -:::warning[Early State Development] + Compose is still in early development and currently available only to contributors. It is not **production-ready** — use it in test or development environments only. -::: + diff --git a/website/docs/foundations/diamond-contracts.mdx b/website/docs/foundations/diamond-contracts.mdx index fecef74c..fa112367 100644 --- a/website/docs/foundations/diamond-contracts.mdx +++ b/website/docs/foundations/diamond-contracts.mdx @@ -5,6 +5,7 @@ description: "Understand Diamonds from the ground up—facets, storage, delegati --- import SvgThemeRenderer from '@site/src/components/theme/SvgThemeRenderer'; +import Callout from '@site/src/components/ui/Callout'; A **diamond contract** is a smart contract that is made up of multiple parts instead of one large block of code. The diamond exists at **one address** and holds **all of the contract's storage**, but it uses separate smart contracts called **facets** to provide its functionality. @@ -12,9 +13,9 @@ Users interact only with the **diamond**, but the diamond's features come from i Because facets can be added, replaced, or removed, a diamond can grow and evolve over time **without changing its address** and without redeploying the entire system. -:::note[In Simple Terms] + A diamond contract is a smart contract made from multiple small building blocks (facets), allowing it to be flexible, organized, and able to grow over time. -::: + A diamond has: - One address diff --git a/website/docs/foundations/index.mdx b/website/docs/foundations/index.mdx index f4a41da1..45a8b489 100644 --- a/website/docs/foundations/index.mdx +++ b/website/docs/foundations/index.mdx @@ -8,6 +8,7 @@ import DocCard, { DocCardGrid } from '@site/src/components/docs/DocCard'; import DocSubtitle from '@site/src/components/docs/DocSubtitle'; import Icon from '@site/src/components/ui/Icon'; import CalloutBox from '@site/src/components/ui/CalloutBox'; +import Callout from '@site/src/components/ui/Callout'; Compose is a new approach to smart contract development that changes how developers build and deploy smart contract systems. This section introduces the core concepts that make Compose unique. @@ -54,7 +55,12 @@ import CalloutBox from '@site/src/components/ui/CalloutBox'; /> -:::warning[Early Development] + + +Don't rush through these concepts. Taking time to understand the foundations will make everything else much easier. + + + Compose is still in early development and currently available only to contributors. It is not **production-ready** — use it in test or development environments only. -::: \ No newline at end of file + \ No newline at end of file diff --git a/website/docs/foundations/onchain-contract-library.mdx b/website/docs/foundations/onchain-contract-library.mdx index 9379b0a4..53a6a971 100644 --- a/website/docs/foundations/onchain-contract-library.mdx +++ b/website/docs/foundations/onchain-contract-library.mdx @@ -5,6 +5,7 @@ description: Compose provides a set of reusable on-chain contracts that already --- import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Callout from '@site/src/components/ui/Callout'; **Compose takes a different approach.** @@ -31,11 +32,11 @@ This reduces duplication, improves upgradeability, and makes smart contract syst For your next project, instead of deploying new contracts, simply **use the existing on-chain contracts** provided by Compose. -:::tip[Key Insight] + Compose is a general purpose **on-chain** smart contract library. -::: + -:::info[In Development] + Compose is still in early development, and its smart contracts haven't been deployed yet. We're actively building—and if this vision excites you, we'd love for you to join us. -::: + diff --git a/website/docs/foundations/overview.mdx b/website/docs/foundations/overview.mdx deleted file mode 100644 index 733e1038..00000000 --- a/website/docs/foundations/overview.mdx +++ /dev/null @@ -1,85 +0,0 @@ ---- -sidebar_position: 10 -title: Overview -description: Overview of Compose foundations—core concepts, authentication, facets and modules, diamond standard, and storage patterns for diamond-based smart contract development. -draft: true ---- - -import DocHero from '@site/src/components/docs/DocHero'; -import DocCard, { DocCardGrid } from '@site/src/components/docs/DocCard'; -import Callout from '@site/src/components/ui/Callout'; - - - -## Core Concepts - -

- Understanding these fundamental concepts will help you build robust, scalable smart contract systems with Compose. -

- - - } - href="/docs/foundations/authentication" - /> - } - href="/docs/foundations/facets-and-modules" - /> - } - href="/docs/" - /> - } - href="/docs/" - /> - - -## Advanced Topics - - - } - href="/docs/" - /> - } - href="/docs/" - /> - - - -We recommend starting with **Facets & Modules** to understand the core architecture, then moving to **Storage Patterns** to see how it all works together. - - -## Why These Matter - -The concepts in this section form the foundation of everything you'll build with Compose: - -- **Authentication** ensures your contracts have proper access control -- **Facets & Modules** explain how to structure your code -- **Diamond Standard** provides the underlying architecture -- **Storage Patterns** enable the shared state that makes it all work - - -Don't rush through these concepts. Taking time to understand the foundations will make everything else much easier and prevent common mistakes. - - diff --git a/website/docs/foundations/reusable-facet-logic.mdx b/website/docs/foundations/reusable-facet-logic.mdx index 10fd0fdf..fc511550 100644 --- a/website/docs/foundations/reusable-facet-logic.mdx +++ b/website/docs/foundations/reusable-facet-logic.mdx @@ -5,6 +5,7 @@ description: Deploy once, reuse everywhere. Compose facets are shared across tho --- import DiamondFacetsSVG from '@site/static/img/svg/compose_diamond_facets.svg' +import Callout from '@site/src/components/ui/Callout'; You might be wondering: **How can I create a new project without deploying new smart contracts?** @@ -53,13 +54,13 @@ If 1,000 projects use the same `ERC20Facet`: - **Millions in gas costs avoided** - **1,000 projects** benefit from the same audited, battle-tested code -:::tip[Key Insight] + Many diamond contracts can be deployed that **reuse the same on-chain facets**. -::: + -:::tip[Key Insight] + Each diamond manages **its own storage data** by using the code from facets. -::: + diff --git a/website/docs/foundations/solidity-modules.mdx b/website/docs/foundations/solidity-modules.mdx index d8d40e16..4a6a18d4 100644 --- a/website/docs/foundations/solidity-modules.mdx +++ b/website/docs/foundations/solidity-modules.mdx @@ -4,6 +4,8 @@ title: Solidity Modules description: What Solidity modules are, how they differ from contracts and libraries, and how Compose uses them for reusable facet logic and shared storage. --- +import ExpandableCode from '@site/src/components/code/ExpandableCode'; + Solidity **modules** are Solidity files whose top-level code lives *outside* of contracts and Solidity libraries. They contain reusable logic that gets pulled into other contracts at compile time. @@ -33,8 +35,8 @@ Compose uses clear naming patterns to distinguish Solidity file types: Here is an example of a Solidity module that implements contract ownership functionality: -```solidity -// SPDX-License-Identifier: MIT + +{`// SPDX-License-Identifier: MIT pragma solidity >=0.8.30; /* @@ -87,13 +89,13 @@ function requireOwner() view { if (getStorage().owner != msg.sender) { revert OwnerUnauthorizedAccount(); } -} -``` +}`} + Here is an example of a diamond contract that uses Solidity modules to implement ERC-2535 Diamonds: -```solidity -// SPDX-License-Identifier: MIT + +{`// SPDX-License-Identifier: MIT pragma solidity >=0.8.30; import "../DiamondMod.sol" as DiamondMod; @@ -146,7 +148,7 @@ contract ExampleDiamond { } receive() external payable {} -} -``` +}`} + diff --git a/website/docs/getting-started/_category_.json b/website/docs/getting-started/_category_.json index 74b10c34..f17bd463 100644 --- a/website/docs/getting-started/_category_.json +++ b/website/docs/getting-started/_category_.json @@ -3,9 +3,9 @@ "position": 3, "link": { "type": "generated-index", - "description": "Learn how to install and configure Compose for your smart contract projects." + "description": "Learn how to install and configure Compose for your smart contract projects.", + "slug": "/docs/getting-started" }, "collapsible": true, "collapsed": true } - diff --git a/website/docs/getting-started/installation.md b/website/docs/getting-started/installation.md index c4f9dc3d..a844cffc 100644 --- a/website/docs/getting-started/installation.md +++ b/website/docs/getting-started/installation.md @@ -3,6 +3,7 @@ sidebar_position: 1 --- import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Callout from '@site/src/components/ui/Callout'; # Installation @@ -79,7 +80,7 @@ Having trouble with installation? - Ask in **[Discord](https://discord.gg/compose)** - Open an **[issue on GitHub](https://github.com/Perfect-Abstractions/Compose/issues)** -:::tip Development Environment -We recommend using VSCode with the [**Solidity** extension](https://github.com/juanfranblanco/vscode-solidity) by Juan Blanco for the best development experience. -::: + +We recommend using VSCode with the **Solidity** extension by Juan Blanco for the best development experience. + diff --git a/website/docs/getting-started/quick-start.md b/website/docs/getting-started/quick-start.md deleted file mode 100644 index 318fd0e6..00000000 --- a/website/docs/getting-started/quick-start.md +++ /dev/null @@ -1,264 +0,0 @@ ---- -sidebar_position: 2 -draft: true ---- - -# Quick Start - -Let's build your first diamond using Compose facets in under 5 minutes! 🚀 - -## What We'll Build - -We'll create a simple ERC-20 token diamond that demonstrates: -- How to use Compose facets -- How shared storage works -- How to deploy and interact with your diamond - -## Step 1: Set Up Your Project - -```bash -# Create a new Foundry project -forge init my-diamond -cd my-diamond - -# Install Compose (when available as dependency) -# For now, clone the repository -git clone https://github.com/Perfect-Abstractions/Compose.git lib/Compose -``` - -## Step 2: Create Your Diamond Contract - -Create `src/MyTokenDiamond.sol`: - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {Diamond} from "compose/Diamond.sol"; - -/// @title MyTokenDiamond -/// @notice A diamond that implements ERC-20 functionality -contract MyTokenDiamond is Diamond { - constructor( - address owner, - address diamondCutFacet - ) Diamond(owner, diamondCutFacet) { - // Diamond is initialized and ready to receive facets - } -} -``` - -## Step 3: Deploy Script - -Create `script/DeployMyDiamond.s.sol`: - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {Script} from "forge-std/Script.sol"; -import {MyTokenDiamond} from "../src/MyTokenDiamond.sol"; -import {DiamondCutFacet} from "compose/facets/DiamondCutFacet.sol"; -import {ERC20Facet} from "compose/facets/ERC20Facet.sol"; -import {IDiamondCut} from "compose/interfaces/IDiamondCut.sol"; - -contract DeployMyDiamond is Script { - function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address deployer = vm.addr(deployerPrivateKey); - - vm.startBroadcast(deployerPrivateKey); - - // 1. Deploy DiamondCutFacet - DiamondCutFacet diamondCutFacet = new DiamondCutFacet(); - - // 2. Deploy Diamond - MyTokenDiamond diamond = new MyTokenDiamond( - deployer, - address(diamondCutFacet) - ); - - // 3. Deploy ERC20Facet - ERC20Facet erc20Facet = new ERC20Facet(); - - // 4. Add ERC20Facet to diamond - IDiamondCut.FacetCut[] memory cuts = new IDiamondCut.FacetCut[](1); - - bytes4[] memory erc20Selectors = new bytes4[](9); - erc20Selectors[0] = ERC20Facet.name.selector; - erc20Selectors[1] = ERC20Facet.symbol.selector; - erc20Selectors[2] = ERC20Facet.decimals.selector; - erc20Selectors[3] = ERC20Facet.totalSupply.selector; - erc20Selectors[4] = ERC20Facet.balanceOf.selector; - erc20Selectors[5] = ERC20Facet.transfer.selector; - erc20Selectors[6] = ERC20Facet.allowance.selector; - erc20Selectors[7] = ERC20Facet.approve.selector; - erc20Selectors[8] = ERC20Facet.transferFrom.selector; - - cuts[0] = IDiamondCut.FacetCut({ - facetAddress: address(erc20Facet), - action: IDiamondCut.FacetCutAction.Add, - functionSelectors: erc20Selectors - }); - - IDiamondCut(address(diamond)).diamondCut(cuts, address(0), ""); - - vm.stopBroadcast(); - - console.log("Diamond deployed at:", address(diamond)); - console.log("ERC20Facet deployed at:", address(erc20Facet)); - } -} -``` - -## Step 4: Create Initialization Facet - -For initializing your token with name, symbol, and initial supply: - -```solidity -// src/facets/TokenInitFacet.sol -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {LibERC20} from "compose/libraries/LibERC20.sol"; - -contract TokenInitFacet { - function init( - string memory name, - string memory symbol, - uint8 decimals, - uint256 initialSupply, - address recipient - ) external { - LibERC20.ERC20Storage storage s = LibERC20.getStorage(); - - require(bytes(s.name).length == 0, "Already initialized"); - - s.name = name; - s.symbol = symbol; - s.decimals = decimals; - - if (initialSupply > 0) { - s.totalSupply = initialSupply; - s.balances[recipient] = initialSupply; - } - } -} -``` - -## Step 5: Test Your Diamond - -Create `test/MyTokenDiamond.t.sol`: - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {Test} from "forge-std/Test.sol"; -import {MyTokenDiamond} from "../src/MyTokenDiamond.sol"; -import {ERC20Facet} from "compose/facets/ERC20Facet.sol"; -import {IERC20} from "compose/interfaces/IERC20.sol"; - -contract MyTokenDiamondTest is Test { - MyTokenDiamond diamond; - IERC20 token; - - address owner = address(1); - address user1 = address(2); - address user2 = address(3); - - function setUp() public { - // Deploy and configure diamond - // (Diamond cut logic here - see deploy script) - - // Cast diamond to IERC20 interface - token = IERC20(address(diamond)); - } - - function test_Transfer() public { - vm.prank(owner); - token.transfer(user1, 100 ether); - - assertEq(token.balanceOf(user1), 100 ether); - } - - function test_Approve() public { - vm.prank(user1); - token.approve(user2, 50 ether); - - assertEq(token.allowance(user1, user2), 50 ether); - } - - function test_TransferFrom() public { - // Setup: owner has tokens, user1 is approved - vm.prank(owner); - token.approve(user1, 100 ether); - - // Act: user1 transfers from owner to user2 - vm.prank(user1); - token.transferFrom(owner, user2, 50 ether); - - // Assert - assertEq(token.balanceOf(user2), 50 ether); - } -} -``` - -## Step 6: Run and Deploy - -```bash -# Run tests -forge test - -# Deploy to local network -anvil # In another terminal - -# Deploy -forge script script/DeployMyDiamond.s.sol:DeployMyDiamond --rpc-url http://localhost:8545 --broadcast -``` - -## Understanding What Happened - -Let's break down what you just built: - -1. **Diamond Base**: Your `MyTokenDiamond` inherits from `Diamond`, which provides the core diamond functionality -2. **Facet Integration**: You added `ERC20Facet`, a complete ERC-20 implementation -3. **Shared Storage**: The facet and your initialization logic both use `LibERC20` to access the same storage -4. **Upgradeable**: You can add more facets anytime using `diamondCut()` - -### The Power of Composition - -Your diamond now has: -- ✅ Full ERC-20 functionality -- ✅ Upgradeability via diamond cuts -- ✅ Ability to add more facets (ERC-721, access control, etc.) -- ✅ Separation of concerns (each facet handles one responsibility) - -## Next Steps - -Congratulations! 🎉 You've created your first Compose diamond. Now: - -- **[Learn Core Concepts](/)** - Understand the architecture deeply -- **[Explore Available Facets](/)** - See what else you can add -- **[Custom Facets](/)** - Build your own facets -- **[Best Practices](/)** - Write production-ready code - -## Common Issues - -### "Cannot find Diamond.sol" - -Make sure Compose is properly installed in `lib/Compose/` and your remappings are configured. - -### "DiamondCut failed" - -Check that: -- Function selectors are correct -- Facet address is valid -- You have the right permissions - -### Need Help? - -- 💬 **[Join Discord](https://discord.gg/compose)** - Get immediate help -- 📚 **[Read FAQ](/)** - Common questions answered -- 🐛 **[Report Issues](https://github.com/Perfect-Abstractions/Compose/issues)** - Found a bug? - diff --git a/website/docs/getting-started/your-first-diamond.md b/website/docs/getting-started/your-first-diamond.md deleted file mode 100644 index 73b91dbb..00000000 --- a/website/docs/getting-started/your-first-diamond.md +++ /dev/null @@ -1,371 +0,0 @@ ---- -sidebar_position: 3 -draft: true ---- - -# Your First Diamond - -In this guide, you'll learn how to create a diamond from scratch and understand every piece of the architecture. - -## What is a Diamond? - -A **diamond** is a smart contract that follows the **ERC-2535 Diamond Standard**. Think of it as a modular smart contract system where you can: - -- **Add, replace, or remove functionality** after deployment -- **Combine multiple facets** (modules) into one address -- **Share storage** across all facets using a unified storage layout -- **Exceed the 24KB contract size limit** by splitting code across facets - -### The Diamond Architecture - -``` -┌─────────────────────────────────────┐ -│ Diamond Proxy │ -│ (Single Address, Delegatecalls) │ -└─────────────┬───────────────────────┘ - │ - ┌───────┴────────┬──────────────┐ - │ │ │ -┌─────▼─────┐ ┌──────▼──────┐ ┌───▼────┐ -│ Facet A │ │ Facet B │ │ Facet C│ -│ (ERC-20) │ │ (ERC-721) │ │ (Owner)│ -└───────────┘ └─────────────┘ └────────┘ - │ │ │ - └────────────────┴──────────────┘ - │ - ┌───────────▼────────────┐ - │ Shared Storage │ - │ (Diamond Storage) │ - └───────────────────────┘ -``` - -## Step-by-Step Guide - -### 1. Understanding the Components - -A complete diamond consists of: - -1. **Diamond Contract** - The main contract that users interact with -2. **DiamondCutFacet** - Manages adding/removing/replacing facets -3. **Functional Facets** - Your actual business logic (ERC-20, etc.) -4. **Libraries** - Helper functions for custom facets - -### 2. Create the Diamond Base - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {Diamond} from "compose/Diamond.sol"; - -/// @title MyDiamond -/// @notice A customizable diamond contract -/// @dev Inherits core diamond functionality from Compose -contract MyDiamond is Diamond { - /// @notice Creates a new diamond - /// @param _contractOwner The address that will own this diamond - /// @param _diamondCutFacet The address of the DiamondCutFacet - constructor( - address _contractOwner, - address _diamondCutFacet - ) Diamond(_contractOwner, _diamondCutFacet) { - // The diamond is now ready to receive facets - } -} -``` - -### 3. Deploy DiamondCutFacet - -The DiamondCutFacet is special—it's the only facet that must be added during construction: - -```solidity -import {DiamondCutFacet} from "compose/facets/DiamondCutFacet.sol"; - -// Deploy it once -DiamondCutFacet diamondCutFacet = new DiamondCutFacet(); - -// Pass its address to your diamond -MyDiamond diamond = new MyDiamond( - msg.sender, // owner - address(diamondCutFacet) -); -``` - -### 4. Add Your First Facet - -Let's add ERC-20 functionality: - -```solidity -import {ERC20Facet} from "compose/facets/ERC20Facet.sol"; -import {IDiamondCut} from "compose/interfaces/IDiamondCut.sol"; - -// 1. Deploy the facet -ERC20Facet erc20Facet = new ERC20Facet(); - -// 2. Prepare the function selectors -bytes4[] memory selectors = new bytes4[](9); -selectors[0] = ERC20Facet.name.selector; -selectors[1] = ERC20Facet.symbol.selector; -selectors[2] = ERC20Facet.decimals.selector; -selectors[3] = ERC20Facet.totalSupply.selector; -selectors[4] = ERC20Facet.balanceOf.selector; -selectors[5] = ERC20Facet.transfer.selector; -selectors[6] = ERC20Facet.allowance.selector; -selectors[7] = ERC20Facet.approve.selector; -selectors[8] = ERC20Facet.transferFrom.selector; - -// 3. Create the facet cut -IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); -cut[0] = IDiamondCut.FacetCut({ - facetAddress: address(erc20Facet), - action: IDiamondCut.FacetCutAction.Add, - functionSelectors: selectors -}); - -// 4. Execute the diamond cut -IDiamondCut(address(diamond)).diamondCut(cut, address(0), ""); -``` - -### 5. Initialize Your Token - -Create an init facet for one-time setup: - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {LibERC20} from "compose/libraries/LibERC20.sol"; - -contract ERC20InitFacet { - /// @notice Initialize the ERC-20 token - /// @dev Can only be called once - function initERC20( - string calldata _name, - string calldata _symbol, - uint8 _decimals, - uint256 _initialSupply - ) external { - LibERC20.ERC20Storage storage s = LibERC20.getStorage(); - - // Ensure we haven't initialized yet - require(bytes(s.name).length == 0, "Already initialized"); - - // Set token details - s.name = _name; - s.symbol = _symbol; - s.decimals = _decimals; - - // Mint initial supply to caller - if (_initialSupply > 0) { - s.totalSupply = _initialSupply; - s.balances[msg.sender] = _initialSupply; - - emit Transfer(address(0), msg.sender, _initialSupply); - } - } - - event Transfer(address indexed from, address indexed to, uint256 value); -} -``` - -Call it via `diamondCut` with initialization data: - -```solidity -// Deploy init facet -ERC20InitFacet initFacet = new ERC20InitFacet(); - -// Prepare init data -bytes memory initData = abi.encodeWithSelector( - ERC20InitFacet.initERC20.selector, - "My Token", - "MTK", - 18, - 1_000_000 ether -); - -// Execute diamond cut with initialization -IDiamondCut(address(diamond)).diamondCut( - cut, // empty or with more facets - address(initFacet), - initData -); -``` - -### 6. Interact with Your Diamond - -Now you can use your diamond like any ERC-20 token: - -```solidity -import {IERC20} from "compose/interfaces/IERC20.sol"; - -// Cast diamond to ERC-20 interface -IERC20 token = IERC20(address(diamond)); - -// Use standard ERC-20 functions -uint256 balance = token.balanceOf(msg.sender); -token.transfer(recipient, 100 ether); -token.approve(spender, 1000 ether); -``` - -## Key Concepts Explained - -### Delegatecall Magic - -When you call a function on the diamond: -1. Diamond receives the call -2. Diamond looks up which facet implements that function -3. Diamond `delegatecalls` to that facet -4. Facet executes using **diamond's storage** -5. Result is returned to caller - -``` -User → Diamond.transfer() - ↓ (delegatecall) - ERC20Facet.transfer() - ↓ (reads/writes) - Diamond's Storage -``` - -### Shared Storage - -All facets share the same storage in the diamond. This is why libraries like `LibERC20` are crucial—they ensure everyone accesses storage at the same location: - -```solidity -// Both the facet and your custom code access the same storage -LibERC20.ERC20Storage storage s = LibERC20.getStorage(); -// Always returns storage at keccak256("compose.erc20") -``` - -### Function Selectors - -Each function has a unique 4-byte signature (selector): - -```solidity -bytes4 selector = ERC20Facet.transfer.selector; -// selector = 0xa9059cbb (first 4 bytes of keccak256("transfer(address,uint256)")) -``` - -The diamond uses these selectors to route calls to the correct facet. - -## Complete Example - -Here's a complete deployment script: - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "forge-std/Script.sol"; -import {MyDiamond} from "../src/MyDiamond.sol"; -import {DiamondCutFacet} from "compose/facets/DiamondCutFacet.sol"; -import {ERC20Facet} from "compose/facets/ERC20Facet.sol"; -import {ERC20InitFacet} from "../src/facets/ERC20InitFacet.sol"; -import {IDiamondCut} from "compose/interfaces/IDiamondCut.sol"; - -contract DeployDiamond is Script { - function run() external { - vm.startBroadcast(); - - // Step 1: Deploy DiamondCutFacet - DiamondCutFacet cutFacet = new DiamondCutFacet(); - - // Step 2: Deploy Diamond - MyDiamond diamond = new MyDiamond(msg.sender, address(cutFacet)); - - // Step 3: Deploy ERC20Facet - ERC20Facet erc20 = new ERC20Facet(); - - // Step 4: Deploy InitFacet - ERC20InitFacet initFacet = new ERC20InitFacet(); - - // Step 5: Prepare facet cuts - bytes4[] memory selectors = getERC20Selectors(); - IDiamondCut.FacetCut[] memory cuts = new IDiamondCut.FacetCut[](1); - cuts[0] = IDiamondCut.FacetCut({ - facetAddress: address(erc20), - action: IDiamondCut.FacetCutAction.Add, - functionSelectors: selectors - }); - - // Step 6: Prepare init data - bytes memory initData = abi.encodeWithSelector( - ERC20InitFacet.initERC20.selector, - "Compose Token", - "COMP", - 18, - 1_000_000 ether - ); - - // Step 7: Execute diamond cut with initialization - IDiamondCut(address(diamond)).diamondCut( - cuts, - address(initFacet), - initData - ); - - vm.stopBroadcast(); - - console.log("Diamond deployed at:", address(diamond)); - } - - function getERC20Selectors() internal pure returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](9); - selectors[0] = ERC20Facet.name.selector; - selectors[1] = ERC20Facet.symbol.selector; - selectors[2] = ERC20Facet.decimals.selector; - selectors[3] = ERC20Facet.totalSupply.selector; - selectors[4] = ERC20Facet.balanceOf.selector; - selectors[5] = ERC20Facet.transfer.selector; - selectors[6] = ERC20Facet.allowance.selector; - selectors[7] = ERC20Facet.approve.selector; - selectors[8] = ERC20Facet.transferFrom.selector; - return selectors; - } -} -``` - -## Testing Your Diamond - -```solidity -// test/MyDiamond.t.sol -import {Test} from "forge-std/Test.sol"; -import {IERC20} from "compose/interfaces/IERC20.sol"; - -contract MyDiamondTest is Test { - address diamond; - IERC20 token; - - function setUp() public { - // Deploy your diamond (using the script above) - // ... - - token = IERC20(diamond); - } - - function testTokenName() public { - assertEq(token.name(), "Compose Token"); - } - - function testTransfer() public { - address recipient = address(0x123); - uint256 amount = 100 ether; - - token.transfer(recipient, amount); - assertEq(token.balanceOf(recipient), amount); - } -} -``` - -## Next Steps - -You now understand how to build a diamond from scratch! Continue learning: - -- **[Core Concepts: Facets and Libraries](/)** - Deep dive into the architecture -- **[Available Facets](/)** - Explore what Compose provides -- **[Creating Custom Facets](/)** - Build your own facets -- **[Upgrading Diamonds](/)** - Learn about diamond cuts - -:::tip Pro Tip -In production, consider using a multi-sig wallet or DAO for the diamond owner to ensure secure upgrades. -::: - diff --git a/website/docs/library/_category_.json b/website/docs/library/_category_.json new file mode 100644 index 00000000..04125e1e --- /dev/null +++ b/website/docs/library/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Library", + "position": 4, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/index" + } +} diff --git a/website/docs/library/diamond/DiamondInspectFacet.mdx b/website/docs/library/diamond/DiamondInspectFacet.mdx new file mode 100644 index 00000000..716c2006 --- /dev/null +++ b/website/docs/library/diamond/DiamondInspectFacet.mdx @@ -0,0 +1,250 @@ +--- +sidebar_position: 2 +title: "Diamond Inspect Facet" +description: "Inspect diamond facets and selectors" +sidebar_label: "Inspect Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/diamond/DiamondInspectFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Callout from '@site/src/components/ui/Callout'; +import PropertyTable from '@site/src/components/api/PropertyTable'; +import ExpandableCode from '@site/src/components/code/ExpandableCode'; +import LastUpdated from '@site/src/components/docs/LastUpdated'; + + +It lets you inspect the diamond’s routing table and retrieve the registered facet list. + + + +- Query which facet a function selector is routed to. +- Enumerate registered facets (and their exported selectors). +- Read the diamond’s composition from shared diamond storage (`DIAMOND_STORAGE_POSITION`). + +## Storage + +### State Variables + + + +### Diamond Storage + + + +{`/** storage-location: erc8042:erc8153.diamond */ +struct DiamondStorage { + mapping(bytes4 functionSelector => FacetNode) facetNodes; + FacetList facetList; +} + +struct FacetList { + bytes4 headFacetNodeId; + bytes4 tailFacetNodeId; + uint32 facetCount; + uint32 selectorCount; +} + +struct FacetNode { + address facet; + bytes4 prevFacetNodeId; + bytes4 nextFacetNodeId; +} +`} + + +--- +### Facet + +Used as a return type for the [`facets`](#facets) function. + + +{`struct Facet { + address facet; + bytes4[] functionSelectors; +}`} + + +## Functions + +### facetAddress + +Gets the facet address that handles the given selector. If facet is not found, return `address(0)`. + + +{`function facetAddress(bytes4 _functionSelector) external view returns (address facet);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### facetFunctionSelectors + +Gets the function selectors exported by the given facet and returns them as `bytes4[]`. + +- If the diamond is not routing the facet's first exported selector, this function returns an empty array. +- If `_facet` does not implement `exportSelectors()` with the expected packing, the call will revert. + + +{`function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetSelectors);`} + + +**Parameters:** + + (packed `bytes4` selectors)." + } + ]} + showRequired={false} +/> + +**Returns:** + + + +--- +### facetAddresses + +Gets the facet addresses used by the diamond. + +If no facets are registered, this returns an empty array. + + +{`function facetAddresses() external view returns (address[] memory allFacets);`} + + +**Returns:** + + + +--- +### facets + +Returns the facet address and function selectors of all facets in the diamond. + + +{`function facets() external view returns (Facet[] memory facetsAndSelectors);`} + + +**Returns:** + + + An array of Facet structs containing each facet address and its function selectors. + + ) + } + ]} + showRequired={false} +/> + +## Selector Packing Notes (`exportSelectors()`) + +`exportSelectors()` returns a packed `bytes` blob where each `bytes4` selector is encoded into 4 consecutive bytes (a `bytes.concat(sel1, sel2, ...)` style packing). + +`DiamondInspectFacet` follows that convention: + +- `facetFunctionSelectors(facet)` calls `facet.exportSelectors()` and unpacks the packed bytes into a `bytes4[]`. +- `facets()` does the same for every facet discovered in the diamond. + + + +## Best Practices + +- Integrate this facet to enable external inspection of diamond facet mappings (Useful for tooling and indexing). +- Use `facetAddress` to determine which facet handles a specific function selector. +- Use `facets` and `facetFunctionSelectors` for comprehensive diamond structure analysis. + +## Security Considerations + +Most functions are `view`/`pure` and do not mutate state. + +However, `facetFunctionSelectors()` and `facets()` perform external calls to `IFacet(facet).exportSelectors()`. + +If the provided address is not a valid `IFacet` implementation (or if `exportSelectors()` reverts / returns unexpected data), these calls can revert. + +That being said, always make sure to provide verified and trusted facet addresses. + + diff --git a/website/docs/library/diamond/DiamondMod.mdx b/website/docs/library/diamond/DiamondMod.mdx new file mode 100644 index 00000000..91c69ee3 --- /dev/null +++ b/website/docs/library/diamond/DiamondMod.mdx @@ -0,0 +1,472 @@ +--- +sidebar_position: 1 +title: "Diamond Module" +description: "Internal functions and storage for diamond proxy functionality." +sidebar_label: "Diamond Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/diamond/DiamondMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Callout from '@site/src/components/ui/Callout'; +import CalloutBox from '@site/src/components/ui/CalloutBox'; +import Accordion, { AccordionGroup } from '@site/src/components/ui/Accordion'; +import PropertyTable from '@site/src/components/api/PropertyTable'; +import ExpandableCode from '@site/src/components/code/ExpandableCode'; +import CodeShowcase from '@site/src/components/code/CodeShowcase'; +import RelatedDocs from '@site/src/components/docs/RelatedDocs'; +import WasThisHelpful from '@site/src/components/docs/WasThisHelpful'; +import LastUpdated from '@site/src/components/docs/LastUpdated'; +import ReadingTime from '@site/src/components/docs/ReadingTime'; + + +This module provides core internal functions and storage management for diamond proxies. + + + +- Provides functions for diamond proxy operations. +- Manages diamond storage using a dedicated storage slot (`"erc8153.diamond"`). +- Supports facet registration and retrieval through internal mechanisms. + + + +## Storage + +### State Variables + + + +### Diamond Storage + + + +{`/** storage-location: erc8042:erc8153.diamond */ +struct DiamondStorage { + mapping(bytes4 functionSelector => FacetNode) facetNodes; + FacetList facetList; +} + +struct FacetList { + bytes4 headFacetNodeId; + bytes4 tailFacetNodeId; + uint32 facetCount; + uint32 selectorCount; +} + +struct FacetNode { + address facet; + bytes4 prevFacetNodeId; + bytes4 nextFacetNodeId; +} +`} + + +## Functions + +### getDiamondStorage + + +{`function getDiamondStorage() pure returns (DiamondStorage storage s);`} + + +**Returns:** + + + +--- +### diamondFallback + +This is the core dispatch path used by the diamond proxy pattern. The fallback function is used to route unmatched calls to the facet registered for the called selector (`msg.sig`) in diamond storage. + + +{`function diamondFallback() ;`} + + +**Execution Flow:** + +- Loads `DiamondStorage` and resolves the facet address for the called selector. +- Reverts if no facet is registered for the selector (see [`FunctionNotFound`](#errors) error). +- Copies calldata to memory (`calldatacopy`) and executes `delegatecall` on the resolved facet. +- Copies returndata (`returndatacopy`) and bubbles the exact result back to the caller. + + +`delegatecall` executes facet code in the diamond's context. + +State writes happen inside the diamond storage, and `msg.sender`/`msg.value` are preserved from the original external call. + + +--- + +### addFacets + +Registers one or more facets to a diamond. For each facet, selectors are discovered by calling `exportSelectors()`. Each selector is then mapped to the facet in diamond storage. + +Reverts if any selector is already registered on the diamond. + + +{`function addFacets(address[] memory _facets) ;`} + + +**Parameters:** + + + The facet addresses to add. Each must implement{" "} + IFacet + {" "}and return selectors from exportSelectors(). + + ) + } + ]} + showRequired={false} +/> + +--- +### importSelectors + +Retrieves the function selectors exposed by a facet by calling its `exportSelectors()`. Validates the returned ABI-encoded `bytes` (offset, length, and that the payload length is a multiple of 4) and returns the packed selectors without copying (zero-copy decode). + +Used internally by [`addFacets`](#addfacets). + + +{`function importSelectors(address _facet) view returns (bytes memory selectors);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### at + +Returns the 4-byte function selector at the given index in a packed `bytes` array of selectors (e.g. from [`importSelectors`](#importselectors) or other selector packing). + + +{`function at(bytes memory selectors, uint256 index) pure returns (bytes4 selector);`} + + +**Parameters:** + + + +**Returns:** + + + +## Events + + + +
+ Emitted when a facet is added to a diamond. + + The function selectors this facet handles can be retrieved by calling `IFacet(_facet).exportSelectors()` +
+ +
+ Signature: + +{`event FacetAdded(address indexed _facet);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when a facet is removed from a diamond. + + The function selectors this facet handles can be retrieved by calling `IFacet(_facet).exportSelectors()` +
+ +
+ Signature: + +{`event FacetRemoved(address indexed _facet);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when an existing facet is replaced with a new facet. + + - Selectors that are present in the new facet but not in the old facet are added to the diamond. + - Selectors that are present in both the new and old facet are updated to use the new facet. + - Selectors that are not present in the new facet but are present in the old facet are removed from the diamond. + + The function selectors handled by these facets can be retrieved by calling: + - `IFacet(_oldFacet).exportSelectors()` + - `IFacet(_newFacet).exportSelectors()` +
+ +
+ Signature: + +{`event FacetReplaced(address indexed _oldFacet, address indexed _newFacet);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when a diamond's constructor function or function from a facet makes a `delegatecall`. +
+ +
+ Signature: + +{`event DiamondDelegateCall(address indexed _delegate, bytes _delegateCalldata);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted to record information about a diamond. This event records any arbitrary metadata. + + The format of `_tag` and `_data` are not specified by the standard. +
+ +
+ Signature: + +{`event DiamondMetadata(bytes32 indexed _tag, bytes _data);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown by [addFacets](#addfacets) when a selector from one of the facets is already registered in the diamond via another facet. +
+
+ Signature: + +error CannotAddFunctionToDiamondThatAlreadyExists(bytes4 _selector); + +
+
+ +
+ Thrown when a selector cannot be found inside the diamond during the fallback function (see [`diamondFallback`](#diamondfallback)). +
+
+ Signature: + +error FunctionNotFound(bytes4 _selector); + +
+
+ +
+ Thrown by [importSelectors](#importselectors) when the staticcall to _facet.exportSelectors() fails. +
+
+ Signature: + +error FunctionSelectorsCallFailed(address _facet); + +
+
+ +
+ Thrown by [importSelectors](#importselectors) when the facet returns data that is not valid ABI-encoded bytes (e.g. wrong offset, length not a multiple of 4, or length exceeds payload). +
+
+ Signature: + +error IncorrectSelectorsEncoding(address _facet); + +
+
+ +
+ Thrown by [importSelectors](#importselectors) when the facet address has no code (e.g. EOA or uninitialized contract). +
+
+ Signature: + +error NoBytecodeAtAddress(address _contractAddress); + +
+
+ +
+ Thrown by [importSelectors](#importselectors) when the facet returns fewer than 4 bytes of selectors (i.e. no valid selector). +
+
+ Signature: + +error NoSelectorsForFacet(address _facet); + +
+
+
+ +## Best Practices + +- Ensure that facet registration functions (like `addFacets` and `importSelectors`) are called only during diamond initialization or controlled upgrade processes. +- Verify that the `DiamondStorage` struct is correctly defined and that any new fields are added at the end to maintain storage layout compatibility. +- Handle custom errors such as `CannotAddFunctionToDiamondThatAlreadyExists` and `NoSelectorsForFacet` to ensure robust error management. + +## Integration Notes + +This module interacts directly with the diamond's shared storage at the `DIAMOND_STORAGE_POSITION`, which is identified by `keccak256("erc8153.diamond")`. All functions within this module read from and write to this shared storage. + +Changes to the `facetList` or other storage elements are immediately visible to any facet that accesses the same storage slot. + + diff --git a/website/docs/library/diamond/DiamondUpgradeFacet.mdx b/website/docs/library/diamond/DiamondUpgradeFacet.mdx new file mode 100644 index 00000000..5a7b07ed --- /dev/null +++ b/website/docs/library/diamond/DiamondUpgradeFacet.mdx @@ -0,0 +1,489 @@ +--- +sidebar_position: 3 +title: "Diamond Upgrade Facet" +description: "Diamond upgrade and management facet" +sidebar_label: "Upgrade Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/diamond/DiamondUpgradeFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Callout from '@site/src/components/ui/Callout'; +import Accordion, { AccordionGroup } from '@site/src/components/ui/Accordion'; +import PropertyTable from '@site/src/components/api/PropertyTable'; +import ExpandableCode from '@site/src/components/code/ExpandableCode'; +import LastUpdated from '@site/src/components/docs/LastUpdated'; +import StepIndicator from '@site/src/components/docs/StepIndicator'; + + +Orchestrates upgrade functionality, enabling the addition, replacement, and removal of facets. + + + +- Owner-gated upgrade entrypoint (ERC-173 `owner`). +- Optional `delegatecall` for post-upgrade initialization/state migration. +- Updates selector routing so subsequent calls dispatch to the new facet. + + +## Storage + +### State Variables + + + +### Diamond Storage + + + +{`/** storage-location: erc8042:erc8153.diamond */ +struct DiamondStorage { + mapping(bytes4 functionSelector => FacetNode) facetNodes; + FacetList facetList; +} + +struct FacetList { + bytes4 headFacetNodeId; + bytes4 tailFacetNodeId; + uint32 facetCount; + uint32 selectorCount; +} + +struct FacetNode { + address facet; + bytes4 prevFacetNodeId; + bytes4 nextFacetNodeId; +} +`} + + +### Owner Storage + + +{`/** storage-location: erc8042:erc173.owner */ +struct OwnerStorage { + address owner; +}`} + + +--- +### FacetReplacement + + +{`struct FacetReplacement { + address oldFacet; + address newFacet; +}`} + + +## Functions + +### upgradeDiamond + +Upgrade the diamond by adding, replacing, and/or removing facets. + +Execution order: + + + +Then, if `_delegate != address(0)`, the diamond performs a `delegatecall` with `_delegateCalldata` and emits `DiamondDelegateCall`. + + +{`function upgradeDiamond( + address[] calldata _addFacets, + FacetReplacement[] calldata _replaceFacets, + address[] calldata _removeFacets, + address _delegate, + bytes calldata _delegateCalldata, + bytes32 _tag, + bytes calldata _metadata +) external;`} + + + +**Parameters:** + + + + +Facets must implement `exportSelectors()` in order to make their selectors discoverable by diamonds. +Only the exported selectors will be added to the diamond. + +```solidity +interface IFacet { + function exportSelectors() external pure returns (bytes memory); +} +``` + +See [Facet-Based Diamond `EIP-8153`](https://eips.ethereum.org/EIPS/eip-8153) for more details. + + +## Events + + + +
+ Emitted when a facet is added to a diamond. + + The function selectors this facet handles can be retrieved by calling `IFacet(_facet).exportSelectors()` +
+ +
+ Signature: + +{`event FacetAdded(address indexed _facet);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when a facet is removed from a diamond. + + The function selectors this facet handles can be retrieved by calling `IFacet(_facet).exportSelectors()` +
+ +
+ Signature: + +{`event FacetRemoved(address indexed _facet);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when an existing facet is replaced with a new facet. + + - Selectors that are present in the new facet but not in the old facet are added to the diamond. + - Selectors that are present in both the new and old facet are updated to use the new facet. + - Selectors that are not present in the new facet but are present in the old facet are removed from the diamond. + + The function selectors handled by these facets can be retrieved by calling: + - `IFacet(_oldFacet).exportSelectors()` + - `IFacet(_newFacet).exportSelectors()` +
+ +
+ Signature: + +{`event FacetReplaced(address indexed _oldFacet, address indexed _newFacet);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when a diamond's constructor function or function from a facet makes a `delegatecall`. +
+ +
+ Signature: + +{`event DiamondDelegateCall(address indexed _delegate, bytes _delegateCalldata);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted to record information about a diamond. This event records any arbitrary metadata. + + The format of `_tag` and `_data` are not specified by the standard. +
+ +
+ Signature: + +{`event DiamondMetadata(bytes32 indexed _tag, bytes _data);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown by [upgradeDiamond](#upgradediamond) when msg.sender is not the current diamond owner. +
+ +
+ Signature: + +error OwnerUnauthorizedAccount(); + +
+
+ +
+ Thrown by [addFacets](#upgradediamond) when a selector exported by a facet already exists in the diamond. +
+ +
+ Signature: + +error CannotAddFunctionToDiamondThatAlreadyExists(bytes4 _selector); + +
+
+ +
+ Thrown by [removeFacets](#upgradediamond) when the facet being removed is not currently part of the diamond. +
+ +
+ Signature: + +error CannotRemoveFacetThatDoesNotExist(address _facet); + +
+
+ +
+ Thrown by [replaceFacets](#upgradediamond) when a replacement pair provides the same address for both oldFacet and newFacet. +
+ +
+ Signature: + +error CannotReplaceFacetWithSameFacet(address _facet); + +
+
+ +
+ Thrown by [replaceFacets](#upgradediamond) when a selector in newFacet is already owned by a facet other than the specified oldFacet. +
+
+ Signature: + +error CannotReplaceFunctionFromNonReplacementFacet(bytes4 _selector); + +
+
+ +
+ Thrown by [replaceFacets](#upgradediamond) when the provided oldFacet does not match the facet currently registered in the diamond. +
+
+ Signature: + +error FacetToReplaceDoesNotExist(address _oldFacet); + +
+
+ +
+ Thrown by [upgradeDiamond](#upgradediamond) when the optional post-upgrade delegatecall fails without returning revert data. +
+ +
+ Signature: + +error DelegateCallReverted(address _delegate, bytes _delegateCalldata); + +
+
+ +
+ Thrown by [importSelectors](#importselectors) when the staticcall to _facet.exportSelectors() fails. +
+
+ Signature: + +error ExportSelectorsCallFailed(address _facet); + +
+
+ +
+ Thrown by [importSelectors](#importselectors) when the facet returns data that is not valid ABI-encoded bytes (e.g. wrong offset, length not a multiple of 4, or length exceeds payload). +
+
+ Signature: + +error IncorrectSelectorsEncoding(address _facet); + +
+
+ +
+ Thrown by [importSelectors](#importselectors) when the facet address has no code (e.g. EOA or uninitialized contract). +
+
+ Signature: + +error NoBytecodeAtAddress(address _contractAddress); + +
+
+ +
+ Thrown by [importSelectors](#importselectors) when the facet returns fewer than 4 bytes of selectors (i.e. no valid selector). +
+
+ Signature: + +error NoSelectorsForFacet(address _facet); + +
+
+
+ +## Best Practices + +- Ensure all diamond upgrade operations are performed by authorized wallet. This facet is configure to use the unique owner, use the [Upgrade Module](/docs/library/diamond/DiamondUpgradeMod) to wrap around your own access control logic. +- Always verify facet logic contracts are immutable and trusted before using it inside a diamond. +- Ensure each facet’s `exportSelectors()` returns a valid packed selector list in deterministic order. +- Carefully audit any optional post-upgrade `delegatecall` (delegate contract + calldata). + +## Security Considerations + +The `upgradeDiamond` function is critical: this function mutates the diamond’s selector routing and facet list. +

+Although this facet protected by [Owner](/docs/library/access/Owner/) checks, it can optionally execute an unrestricted `delegatecall` after facet updates. Allowing changes to the diamond storage. + +That being said, make sure to provide verified and trusted contract addresses to avoid any unwanted changes or vulnerabilities + diff --git a/website/docs/library/diamond/DiamondUpgradeMod.mdx b/website/docs/library/diamond/DiamondUpgradeMod.mdx new file mode 100644 index 00000000..2c6b6fe2 --- /dev/null +++ b/website/docs/library/diamond/DiamondUpgradeMod.mdx @@ -0,0 +1,729 @@ +--- +sidebar_position: 4 +title: "Diamond Upgrade Module" +description: "Upgrade diamond by adding, replacing, or removing facets" +sidebar_label: "Upgrade Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/diamond/DiamondUpgradeMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; + +import Callout from '@site/src/components/ui/Callout'; +import Accordion, { AccordionGroup } from '@site/src/components/ui/Accordion'; +import PropertyTable from '@site/src/components/api/PropertyTable'; +import ExpandableCode from '@site/src/components/code/ExpandableCode'; +import WasThisHelpful from '@site/src/components/docs/WasThisHelpful'; +import LastUpdated from '@site/src/components/docs/LastUpdated'; +import StepIndicator from '@site/src/components/docs/StepIndicator'; + + +Module providing the internal functions to extend the diamond's upgrade functionality by adding, replacing, or removing facets. + + + +- Manages facet lifecycle (add, replace, remove) within a diamond. +- Updates selector routing so subsequent calls dispatch to the new facet. + + + +You can use modules to wrap around you own project logic while using the default Compose building blocks. Modules provides all the necessary internal helpers for maximum integration + +Storage follows the diamond slot layout in this file; any code using the same `STORAGE_POSITION` or related positions reads and writes shared state. + +See Facets & Modules for more information. + + +## Storage + +### State Variables + + +--- +### Diamond Storage + + +{`/** storage-location: erc8042:erc8153.diamond */ +struct DiamondStorage { + mapping(bytes4 functionSelector => FacetNode) facetNodes; + FacetList facetList; +} + +struct FacetList { + bytes4 headFacetNodeId; + bytes4 tailFacetNodeId; + uint32 facetCount; + uint32 selectorCount; +} + +struct FacetNode { + address facet; + bytes4 prevFacetNodeId; + bytes4 nextFacetNodeId; +}`} + + +--- +### FacetReplacement + +This struct is used to replace old facets with new facets. + + +{`struct FacetReplacement { + address oldFacet; + address newFacet; +}`} + + +## Functions + +### getDiamondStorage + + +{`function getDiamondStorage() pure returns (DiamondStorage storage s);`} + + +**Returns:** + + + +--- +### upgradeDiamond + +Upgrade the diamond by adding, replacing, and/or removing facets. + +Execution order: + + + +Then, if `_delegate != address(0)`, the diamond performs a `delegatecall` with `_delegateCalldata` and emits `DiamondDelegateCall`. + + +{`function upgradeDiamond( + address[] calldata _addFacets, + FacetReplacement[] calldata _replaceFacets, + address[] calldata _removeFacets, + address _delegate, + bytes calldata _delegateCalldata, + bytes32 _tag, + bytes calldata _metadata +) external;`} + + + +**Parameters:** + + + + +Facets must implement `exportSelectors()` in order to make their selectors discoverable by diamonds. +Only the exported selectors will be added to the diamond. + +```solidity +interface IFacet { + function exportSelectors() external pure returns (bytes memory); +} +``` + +See [Facet-Based Diamond `EIP-8153`](https://eips.ethereum.org/EIPS/eip-8153) for more details. + + +--- +### addFacets + +Adds new facets to a diamond and maps their exported selectors to the facet address. + +Behavior: + +Each facet in _facets is queried via exportSelectors(). }, + { title: 'Validate uniqueness', description: 'All exported selectors must be new to the diamond.' }, + { title: 'Link facet node', description: 'The first selector of each facet becomes the facet linked-list node id.' }, + { title: 'Emit event', description: <>Emits FacetAdded once per successfully added facet. }, + ]} +/> + + +{`function addFacets(address[] calldata _facets);`} + + +**Parameters:** + + + +--- +### removeFacets + +Removes facets from a diamond and unregisters the selectors they previously exported. + +Behavior: + +Emits FacetRemoved once per removed facet. }, + ]} +/> + + +{`function removeFacets(address[] calldata _facets);`} + + +**Parameters:** + + + +--- +### replaceFacets + +Replaces facets in a diamond using `(oldFacet, newFacet)` pairs. + +Behavior: + +Selectors present in both facets are re-routed to newFacet. }, + { title: 'Add new selectors', description: <>Selectors present only in newFacet are added. }, + { title: 'Remove stale selectors', description: <>Selectors present only in oldFacet are removed. }, + { title: 'Emit event', description: <>Emits FacetReplaced(oldFacet, newFacet) for each replacement pair. }, + ]} +/> + + +{`function replaceFacets(FacetReplacement[] calldata _replaceFacets);`} + + +**Parameters:** + + + +--- +### importSelectors + +Retrieves the function selectors exposed by a facet by calling its `exportSelectors()`. Validates the returned ABI-encoded `bytes` (offset, length, and that the payload length is a multiple of 4) and returns the packed selectors without copying (zero-copy decode). + +Used internally by [`addFacets`](#addfacets), [`replaceFacets`](#replacefacets) and [`removeFacets`](#removefacets). + + +{`function importSelectors(address _facet) view returns (bytes memory selectors);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### at + +Returns the 4-byte function selector at the given index in a packed `bytes` array of selectors (e.g. from [`importSelectors`](#importselectors) or other selector packing). + + +{`function at(bytes memory selectors, uint256 index) pure returns (bytes4 selector);`} + + +**Parameters:** + + + +**Returns:** + + + +## Events + + + +
+ Emitted when a facet is added to a diamond. + + The function selectors this facet handles can be retrieved by calling `IFacet(_facet).exportSelectors()` +
+ +
+ Signature: + +{`event FacetAdded(address indexed _facet);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when a facet is removed from a diamond. + + The function selectors this facet handles can be retrieved by calling `IFacet(_facet).exportSelectors()` +
+ +
+ Signature: + +{`event FacetRemoved(address indexed _facet);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when an existing facet is replaced with a new facet. + + - Selectors that are present in the new facet but not in the old facet are added to the diamond. + - Selectors that are present in both the new and old facet are updated to use the new facet. + - Selectors that are not present in the new facet but are present in the old facet are removed from the diamond. + + The function selectors handled by these facets can be retrieved by calling: + - `IFacet(_oldFacet).exportSelectors()` + - `IFacet(_newFacet).exportSelectors()` +
+ +
+ Signature: + +{`event FacetReplaced(address indexed _oldFacet, address indexed _newFacet);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when a diamond's constructor function or function from a facet makes a `delegatecall`. +
+ +
+ Signature: + +{`event DiamondDelegateCall(address indexed _delegate, bytes _delegateCalldata);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted to record information about a diamond. This event records any arbitrary metadata. + + The format of `_tag` and `_data` are not specified by the standard. +
+ +
+ Signature: + +{`event DiamondMetadata(bytes32 indexed _tag, bytes _data);`} + +
+
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown by [addFacets](#addfacets) when a selector exported by a facet already exists in the diamond. +
+ +
+ Signature: + +error CannotAddFunctionToDiamondThatAlreadyExists(bytes4 _selector); + +
+
+ +
+ Thrown by [removeFacets](#removefacets) when the facet being removed is not currently part of the diamond. +
+ +
+ Signature: + +error CannotRemoveFacetThatDoesNotExist(address _facet); + +
+
+ +
+ Thrown by [replaceFacets](#replacefacets) when a replacement pair provides the same address for both oldFacet and newFacet. +
+ +
+ Signature: + +error CannotReplaceFacetWithSameFacet(address _facet); + +
+
+ +
+ Thrown by [replaceFacets](#replacefacets) when a selector in newFacet is already owned by a facet other than the specified oldFacet. +
+
+ Signature: + +error CannotReplaceFunctionFromNonReplacementFacet(bytes4 _selector); + +
+
+ +
+ Thrown by [replaceFacets](#replacefacets) when the provided oldFacet does not match the facet currently registered in the diamond. +
+
+ Signature: + +error FacetToReplaceDoesNotExist(address _oldFacet); + +
+
+ +
+ Thrown by [upgradeDiamond](#upgradediamond) when the optional post-upgrade delegatecall fails without returning revert data. +
+ +
+ Signature: + +error DelegateCallReverted(address _delegate, bytes _delegateCalldata); + +
+
+ +
+ Thrown by [importSelectors](#importselectors) when the staticcall to _facet.exportSelectors() fails. +
+
+ Signature: + +error ExportSelectorsCallFailed(address _facet); + +
+
+ +
+ Thrown by [importSelectors](#importselectors) when the facet returns data that is not valid ABI-encoded bytes (e.g. wrong offset, length not a multiple of 4, or length exceeds payload). +
+
+ Signature: + +error IncorrectSelectorsEncoding(address _facet); + +
+
+ +
+ Thrown by [importSelectors](#importselectors) when the facet address has no code (e.g. EOA or uninitialized contract). +
+
+ Signature: + +error NoBytecodeAtAddress(address _contractAddress); + +
+
+ +
+ Thrown by [importSelectors](#importselectors) when the facet returns fewer than 4 bytes of selectors (i.e. no valid selector). +
+
+ Signature: + +error NoSelectorsForFacet(address _facet); + +
+
+
+ + + + +## Best Practices + +- Ensure all diamond upgrade operations are performed by authorized wallet. +- Always verify facet logic contracts are immutable and trusted before using it inside a diamond. +- Ensure each facet’s `exportSelectors()` returns a valid packed selector list in deterministic order. +- Carefully audit any optional post-upgrade `delegatecall` (contract + calldata). + + +## Security Considerations + +This module doesn't provide any access control logic, when using it into your own facet, it is your responsibility to implement proper checks. + +The `upgradeDiamond` function is critical: this function mutates the diamond’s selector routing and facet list. + +That being said, make sure to provide verified and trusted contract addresses to avoid any unwanted changes or vulnerabilities + +## Integration Notes + +This module interacts directly with the diamond's shared storage at the `DIAMOND_STORAGE_POSITION`, which is identified by `keccak256("erc8153.diamond")`. All functions within this module read from and write to this shared storage. + +Changes made through `upgradeDiamond`, `addFacets`, `replaceFacets`, and `removeFacets` are immediately visible to the diamond and all facet that access the shared storage. + + + diff --git a/website/docs/library/diamond/_category_.json b/website/docs/library/diamond/_category_.json new file mode 100644 index 00000000..26c8cc37 --- /dev/null +++ b/website/docs/library/diamond/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Diamond Core", + "position": 1, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/diamond/index" + } +} diff --git a/website/docs/library/diamond/index.mdx b/website/docs/library/diamond/index.mdx new file mode 100644 index 00000000..65758b08 --- /dev/null +++ b/website/docs/library/diamond/index.mdx @@ -0,0 +1,43 @@ +--- +title: "Diamond Core" +description: "Core diamond proxy functionality for ERC-2535 diamonds." +--- + +import DocCard, { DocCardGrid } from '@site/src/components/docs/DocCard'; +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Icon from '@site/src/components/ui/Icon'; + + + Core diamond proxy functionality for ERC-8153 diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/index.mdx b/website/docs/library/index.mdx new file mode 100644 index 00000000..d5314e23 --- /dev/null +++ b/website/docs/library/index.mdx @@ -0,0 +1,23 @@ +--- +title: "Library" +description: "Contract references for Compose modules and facets, storage layout, and upgrade wiring." +sidebar_class_name: "hidden" +--- + +import DocCard, { DocCardGrid } from '@site/src/components/docs/DocCard'; +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Icon from '@site/src/components/ui/Icon'; + + + Contract-level docs for each **Compose** facets & modules. What to call on chain, how state is organized, and how upgrades works. + + + + } + size="medium" + /> + diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index e2fc632c..abab2a75 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -326,18 +326,22 @@ const config = { }, }, ], - [ - "posthog-docusaurus", - { - apiKey: process.env.POSTHOG_API_KEY, - appUrl: 'https://compose.diamonds/54Q17895d65', - uiHost: 'https://us.posthog.com', - enableInDevelopment: false, - capturePageLeave: true, - defaults: '2026-01-30', - cookieless_mode: 'on_reject', - }, - ], + ...(process.env.POSTHOG_API_KEY + ? [ + [ + 'posthog-docusaurus', + { + apiKey: process.env.POSTHOG_API_KEY, + appUrl: 'https://compose.diamonds/54Q17895d65', + uiHost: 'https://us.posthog.com', + enableInDevelopment: false, + capturePageLeave: true, + defaults: '2026-01-30', + cookieless_mode: 'on_reject', + }, + ], + ] + : []), ].filter(Boolean), }; diff --git a/website/package.json b/website/package.json index 2421ec0b..287a281d 100644 --- a/website/package.json +++ b/website/package.json @@ -11,7 +11,8 @@ "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids" + "write-heading-ids": "docusaurus write-heading-ids", + "generate-docs": "cd .. && forge doc && SKIP_ENHANCEMENT=true node .github/scripts/generate-docs.js --all" }, "dependencies": { "@acid-info/docusaurus-og": "^1.0.3-beta.2", diff --git a/website/sidebars.js b/website/sidebars.js index a6326118..f86efb16 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -58,6 +58,21 @@ const sidebars = { }, ], }, + { + type: 'category', + label: 'Library', + collapsed: true, + link: { + type: 'doc', + id: 'library/index', + }, + items: [ + { + type: 'autogenerated', + dirName: 'library', + }, + ], + }, { type: 'category', label: 'Contribution', diff --git a/website/src/components/api/PropertyTable/index.js b/website/src/components/api/PropertyTable/index.js index 496f2fc3..22fd68e0 100644 --- a/website/src/components/api/PropertyTable/index.js +++ b/website/src/components/api/PropertyTable/index.js @@ -1,6 +1,32 @@ import React from 'react'; import styles from './styles.module.css'; +/** + * Parse description string and convert markdown-style code (backticks) to JSX code elements + * @param {string|React.ReactNode} description - Description string or React element + * @returns {React.ReactNode} Description with code elements rendered + */ +function parseDescription(description) { + if (!description || typeof description !== 'string') { + return description; + } + + // Split by backticks and alternate between text and code + const parts = description.split(/(`[^`]+`)/g); + return parts.map((part, index) => { + if (part.startsWith('`') && part.endsWith('`')) { + // This is a code block + const codeContent = part.slice(1, -1); // Remove backticks + return ( + + {codeContent} + + ); + } + return {part}; + }); +} + /** * PropertyTable Component - Modern API property documentation table * Inspired by Shadcn UI design patterns @@ -51,7 +77,7 @@ export default function PropertyTable({ )} - {prop.description || prop.desc || '-'} + {prop.descriptionElement || parseDescription(prop.description || prop.desc) || '-'} {prop.default !== undefined && (
Default: {String(prop.default)} diff --git a/website/src/components/api/PropertyTable/styles.module.css b/website/src/components/api/PropertyTable/styles.module.css index d6a75d41..c50a2be5 100644 --- a/website/src/components/api/PropertyTable/styles.module.css +++ b/website/src/components/api/PropertyTable/styles.module.css @@ -20,6 +20,7 @@ .tableWrapper { position: relative; width: 100%; + max-width: 100%; border: 1px solid var(--ifm-color-emphasis-200); border-radius: 0.5rem; background: var(--ifm-background-surface-color); @@ -38,6 +39,7 @@ -webkit-overflow-scrolling: touch; scrollbar-width: thin; scrollbar-color: var(--ifm-color-emphasis-300) transparent; + max-width: 100%; } /* Custom Scrollbar */ @@ -69,9 +71,10 @@ /* Table */ .table { width: 100%; + max-width: 100%; border-collapse: separate; border-spacing: 0; - min-width: 640px; + table-layout: auto; } /* Table Header */ @@ -162,22 +165,26 @@ /* Column Styles */ .nameColumn { - width: 20%; - min-width: 180px; + width: auto; + min-width: 120px; + max-width: 25%; } .typeColumn { - width: 15%; - min-width: 140px; + width: auto; + min-width: 100px; + max-width: 20%; } .requiredColumn { - width: 12%; - min-width: 100px; + width: auto; + min-width: 80px; + max-width: 15%; } .descriptionColumn { - width: auto; + width: 1%; /* Small width forces expansion to fill remaining space in auto layout */ + min-width: 200px; } /* Name Cell */ @@ -272,6 +279,8 @@ .descriptionCell { line-height: 1.6; color: var(--ifm-color-emphasis-700); + width: 100%; /* Ensure cell expands to fill column width */ + min-width: 0; /* Allow shrinking if needed, but column width will enforce expansion */ } [data-theme='dark'] .descriptionCell { @@ -310,6 +319,27 @@ color: #93c5fd; } +/* Inline code in descriptions */ +.descriptionCell .inlineCode { + font-family: var(--ifm-font-family-monospace); + font-size: 0.8125rem; + font-weight: 500; + background: var(--ifm-color-emphasis-100); + padding: 0.25rem 0.5rem; + border-radius: 0.375rem; + color: var(--ifm-color-primary); + border: 1px solid var(--ifm-color-emphasis-200); + display: inline-block; + line-height: 1.4; + margin: 0 0.125rem; +} + +[data-theme='dark'] .descriptionCell .inlineCode { + background: rgba(59, 130, 246, 0.1); + border-color: rgba(59, 130, 246, 0.2); + color: #93c5fd; +} + /* Responsive Design */ @media (max-width: 996px) { .propertyTable { diff --git a/website/src/components/code/ExpandableCode/index.js b/website/src/components/code/ExpandableCode/index.js index fa868a9b..30602b05 100644 --- a/website/src/components/code/ExpandableCode/index.js +++ b/website/src/components/code/ExpandableCode/index.js @@ -1,4 +1,5 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo, useEffect } from 'react'; +import CodeBlock from '@theme/CodeBlock'; import Icon from '../../ui/Icon'; import clsx from 'clsx'; import styles from './styles.module.css'; @@ -18,34 +19,29 @@ export default function ExpandableCode({ children }) { const [isExpanded, setIsExpanded] = useState(false); - const codeRef = React.useRef(null); - const [needsExpansion, setNeedsExpansion] = React.useState(false); - - React.useEffect(() => { - if (codeRef.current) { - const lines = codeRef.current.textContent.split('\n').length; - setNeedsExpansion(lines > maxLines); - } - }, [children, maxLines]); + const [needsExpansion, setNeedsExpansion] = useState(false); const codeContent = typeof children === 'string' ? children : children?.props?.children || ''; + const lineCount = useMemo(() => codeContent.split('\n').length, [codeContent]); + + useEffect(() => { + setNeedsExpansion(lineCount > maxLines); + }, [lineCount, maxLines]); return (
{title &&
{title}
}
-
-          {codeContent}
-        
+ {codeContent} + {needsExpansion && ( diff --git a/website/src/components/docs/DocCard/index.js b/website/src/components/docs/DocCard/index.js index 3c52d4a0..a9993f67 100644 --- a/website/src/components/docs/DocCard/index.js +++ b/website/src/components/docs/DocCard/index.js @@ -4,6 +4,21 @@ import clsx from 'clsx'; import Icon from '../../ui/Icon'; import styles from './styles.module.css'; +/** Inline arrow so it inherits color (Icon uses , so currentColor doesn't work in dark mode) */ +function DocCardArrow() { + return ( + + + + ); +} + /** * DocCard * @@ -40,7 +55,7 @@ export default function DocCard({ {children}
- +
); diff --git a/website/src/components/docs/DocCard/styles.module.css b/website/src/components/docs/DocCard/styles.module.css index 355a40c8..b9e8693c 100644 --- a/website/src/components/docs/DocCard/styles.module.css +++ b/website/src/components/docs/DocCard/styles.module.css @@ -194,6 +194,10 @@ opacity: 0.5; } +[data-theme='dark'] .docCardArrow { + color: #ffffff; +} + .docCard:hover .docCardArrow { color: #1d4ed8; transform: translateX(5px); diff --git a/website/src/components/docs/WasThisHelpful/index.js b/website/src/components/docs/WasThisHelpful/index.js index c8ae4655..3f3a0f0b 100644 --- a/website/src/components/docs/WasThisHelpful/index.js +++ b/website/src/components/docs/WasThisHelpful/index.js @@ -102,6 +102,7 @@ export default function WasThisHelpful({ if (onSubmit) { onSubmit({ pageId, feedback: 'no', comment: comment.trim() }); } + setSubmitted(true); }; diff --git a/website/src/components/docs/WasThisHelpful/styles.module.css b/website/src/components/docs/WasThisHelpful/styles.module.css index e2ec4c99..84c55f0c 100644 --- a/website/src/components/docs/WasThisHelpful/styles.module.css +++ b/website/src/components/docs/WasThisHelpful/styles.module.css @@ -353,6 +353,12 @@ flex-shrink: 0; } +.feedbackIcon { + display: inline-block; + vertical-align: middle; + flex-shrink: 0; +} + .feedbackForm { margin-top: 1rem; padding-top: 1rem; diff --git a/website/src/components/ui/Badge/styles.module.css b/website/src/components/ui/Badge/styles.module.css index eee3e93d..855d0556 100644 --- a/website/src/components/ui/Badge/styles.module.css +++ b/website/src/components/ui/Badge/styles.module.css @@ -8,6 +8,11 @@ transition: all 0.2s ease; } +/* Prevent Markdown-wrapped children from adding extra space */ +.badge p { + margin: 0; +} + /* Sizes */ .badge.small { padding: 0.25rem 0.625rem; diff --git a/website/src/components/ui/GradientButton/styles.module.css b/website/src/components/ui/GradientButton/styles.module.css index 5bead8be..9aeb3753 100644 --- a/website/src/components/ui/GradientButton/styles.module.css +++ b/website/src/components/ui/GradientButton/styles.module.css @@ -1,8 +1,12 @@ .gradientButton { position: relative; + padding-left: 0px; display: inline-flex; align-items: center; justify-content: center; + box-sizing: border-box; + line-height: 1; + vertical-align: middle; font-weight: 600; text-decoration: none; border: none; @@ -14,10 +18,21 @@ } .buttonContent { + display: inline-flex; + align-items: center; + gap: 0.35rem; + line-height: 1; position: relative; z-index: 2; } +/* Prevent Markdown-wrapped children from adding extra space */ +.gradientButton p, +.buttonContent p { + margin: 0; + color: white; +} + .buttonGlow { position: absolute; top: 50%; @@ -46,18 +61,18 @@ /* Sizes */ .gradientButton.small { - padding: 0.5rem 1rem; - font-size: 0.875rem; + padding: 0.55rem 1rem; + font-size: 0.9rem; } .gradientButton.medium { - padding: 0.65rem 1.25rem; + padding: 0.7rem 1.3rem; font-size: 1rem; } .gradientButton.large { - padding: 0.875rem 1.75rem; - font-size: 1.125rem; + padding: 0.9rem 1.75rem; + font-size: 1.05rem; } /* Variants */ @@ -66,6 +81,11 @@ color: white; } +.gradientButton:visited, +.gradientButton * { + color: inherit; +} + .gradientButton.secondary { background: linear-gradient(135deg, #60a5fa 0%, #2563eb 100%); color: white; diff --git a/website/src/hooks/useDocumentationFeedback.js b/website/src/hooks/useDocumentationFeedback.js new file mode 100644 index 00000000..73ffbf8d --- /dev/null +++ b/website/src/hooks/useDocumentationFeedback.js @@ -0,0 +1,35 @@ +import { useCallback } from 'react'; + +/** + * Custom hook for tracking documentation feedback with PostHog + * + * @returns {Function} submitFeedback - Function to submit feedback + */ +export function useDocumentationFeedback() { + /** + * Submit feedback to PostHog analytics + * + * @param {string} pageId - Unique identifier for the page + * @param {string} feedback - 'yes' or 'no' + * @param {string} comment - Optional comment text + */ + const submitFeedback = useCallback((pageId, feedback, comment = null) => { + const posthog = typeof window !== 'undefined' ? window.posthog : null; + + if (!posthog) { + console.log('Feedback submitted:', { pageId, feedback, comment: comment || null }); + return; + } + + posthog.capture('documentation_feedback', { + page_id: pageId, + feedback: feedback, + comment: comment || null, + timestamp: new Date().toISOString(), + url: typeof window !== 'undefined' ? window.location.href : null, + }); + }, []); + + return { submitFeedback }; +} + diff --git a/website/src/pages/index.js b/website/src/pages/index.js index 88a0a440..a9a71566 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -7,10 +7,9 @@ import StatsSection from '../components/home/StatsSection'; import CtaSection from '../components/home/CtaSection'; export default function Home() { - const {siteConfig} = useDocusaurusContext(); return (
diff --git a/website/src/theme/EditThisPage/DocsEditThisPage.js b/website/src/theme/EditThisPage/DocsEditThisPage.js new file mode 100644 index 00000000..3ed1ec80 --- /dev/null +++ b/website/src/theme/EditThisPage/DocsEditThisPage.js @@ -0,0 +1,48 @@ +import React from 'react'; +import Link from '@docusaurus/Link'; +import {useDoc} from '@docusaurus/plugin-content-docs/client'; +import styles from './styles.module.css'; + +/** + * DocsEditThisPage component for documentation pages + * Uses useDoc hook to access frontMatter for "View Source" link + * + * WARNING: This component should ONLY be rendered when we're certain + * we're in a docs page context. It will throw an error if used outside + * the DocProvider context. + * + * @param {string} editUrl - URL to edit the page + */ +export default function DocsEditThisPage({editUrl}) { + const {frontMatter} = useDoc(); + const viewSource = frontMatter?.gitSource; + + // Nothing to show + if (!editUrl && !viewSource) { + return null; + } + + return ( +
+ {viewSource && ( + <> + + View Source + + | + + )} + {editUrl && ( + + Edit this page + + )} +
+ ); +} + diff --git a/website/src/theme/EditThisPage/SafeDocsEditThisPage.js b/website/src/theme/EditThisPage/SafeDocsEditThisPage.js new file mode 100644 index 00000000..7da71c5d --- /dev/null +++ b/website/src/theme/EditThisPage/SafeDocsEditThisPage.js @@ -0,0 +1,35 @@ +import React from 'react'; +import DocsEditThisPage from './DocsEditThisPage'; +import SimpleEditThisPage from './SimpleEditThisPage'; + +/** + * Error boundary wrapper for DocsEditThisPage + * Catches errors if useDoc hook is called outside DocProvider context + * Falls back to SimpleEditThisPage if an error occurs + * + * @param {string} editUrl - URL to edit the page + */ +export default class SafeDocsEditThisPage extends React.Component { + constructor(props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error) { + // If useDoc fails, fall back to simple version + return { hasError: true }; + } + + componentDidCatch(error, errorInfo) { + // Error caught, will render fallback + // Could log to error reporting service here if needed + } + + render() { + if (this.state.hasError) { + return ; + } + + return ; + } +} diff --git a/website/src/theme/EditThisPage/SimpleEditThisPage.js b/website/src/theme/EditThisPage/SimpleEditThisPage.js new file mode 100644 index 00000000..eb7d676c --- /dev/null +++ b/website/src/theme/EditThisPage/SimpleEditThisPage.js @@ -0,0 +1,24 @@ +import React from 'react'; +import Link from '@docusaurus/Link'; +import styles from './styles.module.css'; + +/** + * Simple EditThisPage component for non-docs contexts (blog, etc.) + * Safe to use anywhere - doesn't require any special context + * + * @param {string} editUrl - URL to edit the page + */ +export default function SimpleEditThisPage({editUrl}) { + if (!editUrl) { + return null; + } + + return ( +
+ + Edit this page + +
+ ); +} + diff --git a/website/src/theme/EditThisPage/index.js b/website/src/theme/EditThisPage/index.js new file mode 100644 index 00000000..db62da16 --- /dev/null +++ b/website/src/theme/EditThisPage/index.js @@ -0,0 +1,34 @@ +import React from 'react'; +import {useLocation} from '@docusaurus/router'; +import SimpleEditThisPage from './SimpleEditThisPage'; +import SafeDocsEditThisPage from './SafeDocsEditThisPage'; + +/** + * Main EditThisPage component + * + * Intelligently renders the appropriate EditThisPage variant based on the current route: + * - Docs pages (/docs/*): Uses SafeDocsEditThisPage (with useDoc hook, wrapped in error boundary) + * - Other pages: Uses SimpleEditThisPage + * + * Route checking is necessary because error boundaries don't work reliably during SSR/build. + * + * @param {string} editUrl - URL to edit the page + */ +export default function EditThisPage({editUrl}) { + let isDocsPage = false; + + try { + const location = useLocation(); + const pathname = location?.pathname || ''; + + isDocsPage = pathname.startsWith('/docs/'); + } catch (error) { + isDocsPage = false; + } + + if (isDocsPage) { + return ; + } + + return ; +} diff --git a/website/src/theme/EditThisPage/styles.module.css b/website/src/theme/EditThisPage/styles.module.css new file mode 100644 index 00000000..fc7a21e0 --- /dev/null +++ b/website/src/theme/EditThisPage/styles.module.css @@ -0,0 +1,26 @@ +.wrapper { + display: inline-flex; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; +} + +.link { + display: inline-flex; + align-items: center; + gap: 0.35rem; + font-weight: 600; + color: var(--ifm-link-color); + text-decoration: none; +} + +.link:hover { + text-decoration: underline; + color: var(--ifm-link-hover-color, var(--ifm-link-color)); +} + +.link:visited { + color: var(--ifm-link-color); +} + + diff --git a/website/static/icons/light-bulb-round.svg b/website/static/icons/light-bulb-round.svg new file mode 100644 index 00000000..d08ab7ef --- /dev/null +++ b/website/static/icons/light-bulb-round.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/website/static/icons/light-bulb-svgrepo-com.svg b/website/static/icons/light-bulb-svgrepo-com.svg new file mode 100644 index 00000000..9f8940a6 --- /dev/null +++ b/website/static/icons/light-bulb-svgrepo-com.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/website/static/icons/lightbulb.svg b/website/static/icons/lightbulb.svg index 2181d952..5861a5ec 100644 --- a/website/static/icons/lightbulb.svg +++ b/website/static/icons/lightbulb.svg @@ -1,20 +1,9 @@ - + - - - - - - - - - - - - - + + \ No newline at end of file diff --git a/website/static/icons/thumbs-down-white.svg b/website/static/icons/thumbs-down-white.svg new file mode 100644 index 00000000..98c4bc4d --- /dev/null +++ b/website/static/icons/thumbs-down-white.svg @@ -0,0 +1,4 @@ + + + + diff --git a/website/static/icons/thumbs-down.svg b/website/static/icons/thumbs-down.svg index 7e04b100..75d91722 100644 --- a/website/static/icons/thumbs-down.svg +++ b/website/static/icons/thumbs-down.svg @@ -7,3 +7,4 @@ d="M.5,12H5.28l6.11,7.06A2,2,0,0,1,12,20.49a2,2,0,0,0,2,2,2.74,2.74,0,0,0,2-.8,2.79,2.79,0,0,0,.8-1.95c0-2-2.87-5.86-2.87-5.86h6A2.61,2.61,0,0,0,22.5,11.3a2.94,2.94,0,0,0-.05-.51L20.89,3A1.91,1.91,0,0,0,19,1.48H11.25a9.13,9.13,0,0,0-4,1h0a9.08,9.08,0,0,1-4.06,1H.5" /> + diff --git a/website/static/icons/thumbs-up-white.svg b/website/static/icons/thumbs-up-white.svg new file mode 100644 index 00000000..9e9b1c0a --- /dev/null +++ b/website/static/icons/thumbs-up-white.svg @@ -0,0 +1,4 @@ + + + +