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/ai-provider/README.md b/.github/scripts/ai-provider/README.md new file mode 100644 index 00000000..58ad2218 --- /dev/null +++ b/.github/scripts/ai-provider/README.md @@ -0,0 +1,179 @@ +# AI Provider Service + +Simple, configurable AI service for CI workflows supporting multiple providers. + +## Features + +- **Simple API**: One function to call any AI model +- **Multiple Providers**: GitHub Models (GPT-4o) and Google Gemini +- **Auto-detection**: Automatically uses available provider +- **Rate Limiting**: Built-in request and token-based rate limiting +- **Configurable**: Override provider and model via environment variables + +## Supported Providers + +| Provider | Models | Rate Limits | API Key | +|----------|--------|-------------|---------| +| **GitHub Models** | gpt-4o, gpt-4o-mini | 10 req/min, 40k tokens/min | `GITHUB_TOKEN` | +| **Google Gemini** | gemini-1.5-flash, gemini-1.5-pro | 15 req/min, 1M tokens/min | `GOOGLE_AI_API_KEY` | + +## Usage + +### Basic Usage + +```javascript +const ai = require('./ai-provider'); + +const response = await ai.call( + 'You are a helpful assistant', // system prompt + 'Explain quantum computing' // user prompt +); + +console.log(response); +``` + +### With Options + +```javascript +const response = await ai.call( + systemPrompt, + userPrompt, + { + maxTokens: 1000, + onSuccess: (text, tokens) => { + console.log(`Success! Used ${tokens} tokens`); + }, + onError: (error) => { + console.error('Failed:', error); + } + } +); +``` + +## Environment Variables + +### Provider Selection + +```bash +# Auto-detect (default) - Try other provider with fallback to Github +AI_PROVIDER=auto + +# Use specific provider +AI_PROVIDER=github # Use GitHub Models +AI_PROVIDER=gemini # Use Google Gemini +``` + +### Model Override + +```bash +# Override default model for the provider +AI_MODEL=gpt-4o # For GitHub Models +AI_MODEL=gemini-1.5-pro # For Gemini +``` + +### API Keys + +```bash +# Google Gemini +GOOGLE_AI_API_KEY= +``` + +## Examples + +## GitHub Actions Integration + +```yaml +- name: Run AI-powered task + env: + # Option 1: Auto-detect (recommended) + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }} + + # Option 2: Force specific provider + # AI_PROVIDER: 'gemini' + # AI_MODEL: 'gemini-1.5-pro' + run: node .github/scripts/your-script.js +``` + +## Architecture + +``` +ai-provider/ +├── index.js # Main service (singleton) +├── base-provider.js # Base provider class +├── provider-factory.js # Provider creation logic +├── rate-limiter.js # Rate limiting logic +└── providers/ + ├── github-models.js # GitHub Models implementation + └── gemini.js # Gemini implementation +``` + +## Adding a New Provider + +1. Create a new provider class in `providers/`: + +```javascript +const BaseAIProvider = require('../base-provider'); + +class MyProvider extends BaseAIProvider { + constructor(config, apiKey) { + super('My Provider', config, apiKey); + } + + buildRequestOptions() { + // Return HTTP request options + } + + buildRequestBody(systemPrompt, userPrompt, maxTokens) { + // Return JSON.stringify(...) of request body + } + + extractContent(response) { + // Return { content: string, tokens: number|null } + } +} + +module.exports = MyProvider; +``` + +2. Register in `provider-factory.js`: + +```javascript +const MyProvider = require('./providers/my-provider'); + +function createMyProvider(customModel) { + const apiKey = process.env.MY_PROVIDER_API_KEY; + if (!apiKey) return null; + + return new MyProvider({ model: customModel || 'default-model' }, apiKey); +} +``` + +3. Add to auto-detection or switch statement. + +## Rate Limiting + +The service automatically handles rate limiting: + +- **Request-based**: Ensures minimum delay between requests +- **Token-based**: Tracks token consumption in a 60-second rolling window +- **Smart waiting**: Calculates exact wait time needed + +Rate limits are provider-specific and configured automatically. + +## Error Handling + +```javascript +try { + const response = await ai.call(systemPrompt, userPrompt); + // Use response +} catch (error) { + if (error.message.includes('429')) { + console.log('Rate limited - try again later'); + } else if (error.message.includes('401')) { + console.log('Invalid API key'); + } else { + console.log('Other error:', error.message); + } +} +``` diff --git a/.github/scripts/ai-provider/index.js b/.github/scripts/ai-provider/index.js new file mode 100644 index 00000000..26e57611 --- /dev/null +++ b/.github/scripts/ai-provider/index.js @@ -0,0 +1,132 @@ +/** + * AI Provider Service + * Simple, configurable AI service supporting multiple providers + * + * Usage: + * const ai = require('./ai-provider'); + * const response = await ai.call(systemPrompt, userPrompt); + * + * Environment Variables: + * AI_PROVIDER - 'github' | 'gemini' | 'auto' (default: auto) + * AI_MODEL - Override default model + * GITHUB_TOKEN - For GitHub Models + * GOOGLE_AI_API_KEY - For Gemini + */ + +const { getProvider } = require('./provider-factory'); +const RateLimiter = require('./rate-limiter'); + +class AIProvider { + constructor() { + this.provider = null; + this.rateLimiter = new RateLimiter(); + this.initialized = false; + } + + /** + * Initialize the provider (lazy loading) + */ + _init() { + if (this.initialized) { + return; + } + + this.provider = getProvider(); + if (!this.provider) { + throw new Error( + 'No AI provider available. Set AI_PROVIDER or corresponding API key.' + ); + } + + this.rateLimiter.setProvider(this.provider); + this.initialized = true; + } + + /** + * Make an AI call + * + * @param {string} systemPrompt - System prompt + * @param {string} userPrompt - User prompt + * @param {object} options - Optional settings + * @param {number} options.maxTokens - Override max tokens + * @param {function} options.onSuccess - Success callback + * @param {function} options.onError - Error callback + * @returns {Promise} Response text + */ + async call(systemPrompt, userPrompt, options = {}) { + this._init(); + + const { + maxTokens = null, + onSuccess = null, + onError = null, + } = options; + + if (!systemPrompt || !userPrompt) { + throw new Error('systemPrompt and userPrompt are required'); + } + + try { + // Estimate tokens and wait for rate limits + const tokensToUse = maxTokens || this.provider.getMaxTokens(); + const estimatedTokens = this.rateLimiter.estimateTokenUsage( + systemPrompt, + userPrompt, + tokensToUse + ); + + await this.rateLimiter.waitForRateLimit(estimatedTokens); + + // Build and send request + const requestBody = this.provider.buildRequestBody(systemPrompt, userPrompt, tokensToUse); + const requestOptions = this.provider.buildRequestOptions(); + + const response = await this._makeRequest(requestOptions, requestBody); + + // Extract content + const extracted = this.provider.extractContent(response); + if (!extracted) { + throw new Error('Invalid response format from API'); + } + + // Record actual token usage + const actualTokens = extracted.tokens || estimatedTokens; + this.rateLimiter.recordTokenConsumption(actualTokens); + + if (onSuccess) { + onSuccess(extracted.content, actualTokens); + } + + return extracted.content; + + } catch (error) { + if (onError) { + onError(error); + } + + throw error; + } + } + + /** + * Make HTTPS request + */ + async _makeRequest(options, body) { + const { makeHttpsRequest } = require('../workflow-utils'); + return await makeHttpsRequest(options, body); + } + + /** + * Get provider info + */ + getProviderInfo() { + this._init(); + return { + name: this.provider.name, + limits: this.provider.getRateLimits(), + maxTokens: this.provider.getMaxTokens(), + }; + } +} + +module.exports = new AIProvider(); \ No newline at end of file diff --git a/.github/scripts/ai-provider/provider-factory.js b/.github/scripts/ai-provider/provider-factory.js new file mode 100644 index 00000000..12c47497 --- /dev/null +++ b/.github/scripts/ai-provider/provider-factory.js @@ -0,0 +1,65 @@ +/** + * Provider Factory + * Creates the appropriate AI provider based on environment variables + */ + +const { createGitHubProvider } = require('./providers/github-models'); +const { createGeminiProvider } = require('./providers/gemini'); + +/** + * Get the active AI provider based on environment configuration + * + * Environment variables: + * - AI_PROVIDER: 'github' | 'gemini' | 'auto' (default: 'auto') + * - AI_MODEL: Override default model for the provider + * - GITHUB_TOKEN: API key for GitHub Models + * - GOOGLE_AI_API_KEY: API key for Gemini + * + * @returns {BaseAIProvider|null} Provider instance or null if none available + */ +function getProvider() { + const providerName = (process.env.AI_PROVIDER || 'auto').toLowerCase(); + const customModel = process.env.AI_MODEL; + + if (providerName === 'auto') { + return autoDetectProvider(customModel); + } + + switch (providerName) { + case 'github': + case 'github-models': + return createGitHubProvider(customModel); + + case 'gemini': + case 'google': + return createGeminiProvider(customModel); + + default: + console.warn(`⚠️ Unknown provider: ${providerName}. Falling back to auto-detect.`); + return autoDetectProvider(customModel); + } +} + +/** + * Auto-detect provider based on available API keys + */ +function autoDetectProvider(customModel) { + // Try Gemini + const geminiProvider = createGeminiProvider(customModel); + if (geminiProvider) { + return geminiProvider; + } + + // Fallback to GitHub Models (free in GitHub Actions) + const githubProvider = createGitHubProvider(customModel); + if (githubProvider) { + return githubProvider; + } + + return null; +} + +module.exports = { + getProvider, +}; + diff --git a/.github/scripts/ai-provider/providers/base-provider.js b/.github/scripts/ai-provider/providers/base-provider.js new file mode 100644 index 00000000..fe23fb1c --- /dev/null +++ b/.github/scripts/ai-provider/providers/base-provider.js @@ -0,0 +1,68 @@ +/** + * Base AI Provider class + * All provider implementations should extend this class + */ +class BaseAIProvider { + constructor(name, config, apiKey) { + if (!apiKey) { + throw new Error('API key is required'); + } + + this.name = name; + this.config = config; + this.apiKey = apiKey; + } + + /** + * Get maximum output tokens for this provider + */ + getMaxTokens() { + return this.config.maxTokens || 2500; + } + + /** + * Get rate limits for this provider + */ + getRateLimits() { + return { + maxRequestsPerMinute: this.config.maxRequestsPerMinute || 10, + maxTokensPerMinute: this.config.maxTokensPerMinute || 40000, + }; + } + + /** + * Build HTTP request options + * Must be implemented by subclass + */ + buildRequestOptions() { + throw new Error('buildRequestOptions must be implemented by subclass'); + } + + /** + * Build request body with prompts + * Must be implemented by subclass + */ + buildRequestBody(systemPrompt, userPrompt, maxTokens) { + throw new Error('buildRequestBody must be implemented by subclass'); + } + + /** + * Extract content and token usage from API response + * Must be implemented by subclass + * @returns {{content: string, tokens: number|null}|null} + */ + extractContent(response) { + throw new Error('extractContent must be implemented by subclass'); + } + + /** + * Check if error is a rate limit error + */ + isRateLimitError(error) { + const msg = error?.message || ''; + return msg.includes('429') || msg.toLowerCase().includes('rate limit'); + } +} + +module.exports = BaseAIProvider; + diff --git a/.github/scripts/ai-provider/providers/gemini.js b/.github/scripts/ai-provider/providers/gemini.js new file mode 100644 index 00000000..9af4f159 --- /dev/null +++ b/.github/scripts/ai-provider/providers/gemini.js @@ -0,0 +1,107 @@ +/** + * Google AI (Gemini) Provider + * Uses Google AI API key for authentication + */ +const BaseAIProvider = require('./base-provider'); + +/** + * Gemini Provider Class + * Default model: gemini-2.5-flash-lite + * This model is a lightweight model that is designed to be fast and efficient. + * Refer to https://ai.google.dev/gemini-api/docs for the list of models. + */ +class GeminiProvider extends BaseAIProvider { + /** + * Constructor + * @param {object} config - Configuration object + * @param {string} config.model - Model to use + * @param {number} config.maxTokens - Maximum number of tokens to generate + * @param {number} config.maxRequestsPerMinute - Maximum number of requests per minute + * @param {number} config.maxTokensPerMinute - Maximum number of tokens per minute + * @param {string} apiKey - Google AI API key (required) + */ + constructor(config, apiKey) { + const model = config.model || 'gemini-2.5-flash-lite'; + super(`Google AI (${model})`, config, apiKey); + this.model = model; + } + + buildRequestOptions() { + return { + hostname: 'generativelanguage.googleapis.com', + port: 443, + path: `/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'User-Agent': 'Compose-CI/1.0', + }, + }; + } + + buildRequestBody(systemPrompt, userPrompt, maxTokens) { + // Gemini combines system and user prompts + const combinedPrompt = `${systemPrompt}\n\n${userPrompt}`; + + return JSON.stringify({ + contents: [{ + parts: [{ text: combinedPrompt }] + }], + generationConfig: { + maxOutputTokens: maxTokens || this.getMaxTokens(), + temperature: 0.7, + topP: 0.95, + topK: 40, + }, + safetySettings: [ + { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }, + { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" }, + { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" }, + { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" } + ] + }); + } + + extractContent(response) { + const text = response.candidates?.[0]?.content?.parts?.[0]?.text; + if (text) { + return { + content: text, + tokens: response.usageMetadata?.totalTokenCount || null, + }; + } + return null; + } + + getRateLimits() { + return { + maxRequestsPerMinute: 15, + maxTokensPerMinute: 1000000, // 1M tokens per minute + }; + } + + +} + +/** + * Create Gemini provider + */ +function createGeminiProvider(customModel) { + const apiKey = process.env.GOOGLE_AI_API_KEY; + + + const config = { + model: customModel, + maxTokens: 2500, + maxRequestsPerMinute: 15, + maxTokensPerMinute: 1000000, + }; + + return new GeminiProvider(config, apiKey); +} + +module.exports = { + GeminiProvider, + createGeminiProvider, +}; + diff --git a/.github/scripts/ai-provider/providers/github-models.js b/.github/scripts/ai-provider/providers/github-models.js new file mode 100644 index 00000000..6d9426cc --- /dev/null +++ b/.github/scripts/ai-provider/providers/github-models.js @@ -0,0 +1,79 @@ +/** + * GitHub Models (Azure OpenAI) Provider + * Uses GitHub token for authentication in GitHub Actions + */ +const BaseAIProvider = require('./base-provider'); + +class GitHubModelsProvider extends BaseAIProvider { + constructor(config, apiKey) { + const model = config.model || 'gpt-4o'; + super(`GitHub Models (${model})`, config, apiKey); + this.model = model; + } + + buildRequestOptions() { + return { + hostname: 'models.inference.ai.azure.com', + port: 443, + path: '/chat/completions', + method: 'POST', + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'Compose-CI/1.0', + }, + }; + } + + buildRequestBody(systemPrompt, userPrompt, maxTokens) { + return JSON.stringify({ + messages: [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: userPrompt }, + ], + model: this.model, + max_tokens: maxTokens || this.getMaxTokens(), + temperature: 0.7, + }); + } + + extractContent(response) { + if (response.choices?.[0]?.message?.content) { + return { + content: response.choices[0].message.content, + tokens: response.usage?.total_tokens || null, + }; + } + return null; + } + + getRateLimits() { + return { + maxRequestsPerMinute: 10, + maxTokensPerMinute: 40000, + }; + } +} + +/** + * Create GitHub Models provider + */ +function createGitHubProvider(customModel) { + const apiKey = process.env.GITHUB_TOKEN; + + const config = { + model: customModel, + maxTokens: 2500, + maxRequestsPerMinute: 10, + maxTokensPerMinute: 40000, + }; + + return new GitHubModelsProvider(config, apiKey); +} + +module.exports = { + GitHubModelsProvider, + createGitHubProvider, +}; + diff --git a/.github/scripts/ai-provider/rate-limiter.js b/.github/scripts/ai-provider/rate-limiter.js new file mode 100644 index 00000000..aa59b28f --- /dev/null +++ b/.github/scripts/ai-provider/rate-limiter.js @@ -0,0 +1,137 @@ +/** + * Rate Limiter + * Handles request-based and token-based rate limiting + */ + +class RateLimiter { + constructor() { + this.provider = null; + this.lastCallTime = 0; + this.tokenHistory = []; + this.limits = { + maxRequestsPerMinute: 10, + maxTokensPerMinute: 40000, + }; + this.tokenWindowMs = 60000; // 60 seconds + this.safetyMargin = 0.85; // Use 85% of token budget + } + + /** + * Set the active provider and update rate limits + */ + setProvider(provider) { + this.provider = provider; + this.limits = provider.getRateLimits(); + } + + /** + * Estimate token usage for a request + * Uses rough heuristic: ~4 characters per token + */ + estimateTokenUsage(systemPrompt, userPrompt, maxTokens) { + const inputText = (systemPrompt || '') + (userPrompt || ''); + const estimatedInputTokens = Math.ceil(inputText.length / 4); + return estimatedInputTokens + (maxTokens || 0); + } + + /** + * Wait for rate limits before making a request + */ + async waitForRateLimit(estimatedTokens) { + const now = Date.now(); + + // 1. Request-based rate limit (requests per minute) + const minDelayMs = Math.ceil(60000 / this.limits.maxRequestsPerMinute); + const elapsed = now - this.lastCallTime; + + if (this.lastCallTime > 0 && elapsed < minDelayMs) { + const waitTime = minDelayMs - elapsed; + await this._sleep(waitTime); + } + + // 2. Token-based rate limit + this._cleanTokenHistory(); + const currentConsumption = this._getCurrentTokenConsumption(); + const effectiveBudget = this.limits.maxTokensPerMinute * this.safetyMargin; + const availableTokens = effectiveBudget - currentConsumption; + + if (estimatedTokens > availableTokens) { + const waitTime = this._calculateTokenWaitTime(estimatedTokens, currentConsumption); + if (waitTime > 0) { + await this._sleep(waitTime); + this._cleanTokenHistory(); + } + } + + this.lastCallTime = Date.now(); + } + + /** + * Record actual token consumption after a request + */ + recordTokenConsumption(tokens) { + this.tokenHistory.push({ + timestamp: Date.now(), + tokens: tokens, + }); + this._cleanTokenHistory(); + } + + /** + * Clean expired entries from token history + */ + _cleanTokenHistory() { + const now = Date.now(); + this.tokenHistory = this.tokenHistory.filter( + entry => (now - entry.timestamp) < this.tokenWindowMs + ); + } + + /** + * Get current token consumption in the rolling window + */ + _getCurrentTokenConsumption() { + return this.tokenHistory.reduce((sum, entry) => sum + entry.tokens, 0); + } + + /** + * Calculate how long to wait for token budget to free up + */ + _calculateTokenWaitTime(tokensNeeded, currentConsumption) { + const effectiveBudget = this.limits.maxTokensPerMinute * this.safetyMargin; + const availableTokens = effectiveBudget - currentConsumption; + + if (tokensNeeded <= availableTokens) { + return 0; + } + + if (this.tokenHistory.length === 0) { + return 0; + } + + // Find how many tokens need to expire + const tokensToFree = tokensNeeded - availableTokens; + let freedTokens = 0; + let oldestTimestamp = Date.now(); + + for (const entry of this.tokenHistory) { + freedTokens += entry.tokens; + oldestTimestamp = entry.timestamp; + + if (freedTokens >= tokensToFree) { + break; + } + } + + // Calculate wait time until that entry expires + const timeUntilExpiry = this.tokenWindowMs - (Date.now() - oldestTimestamp); + return Math.max(0, timeUntilExpiry + 2000); // Add 2s buffer + } + + async _sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} + +module.exports = RateLimiter; + 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/generate-docs-utils/README.md b/.github/scripts/generate-docs-utils/README.md new file mode 100644 index 00000000..b55b485a --- /dev/null +++ b/.github/scripts/generate-docs-utils/README.md @@ -0,0 +1,96 @@ +## Documentation Generator + +This directory contains the utilities used by the GitHub Actions workflow to generate the Markdown/MDX documentation for the Solidity contracts and the docs site. + +- **Entry script**: `../../generate-docs.js` +- **Templates**: `templates/` +- **Core logic**: `core/`, `parsing/`, `category/`, `utils/`, `ai/` + +Use this README as a reference for running the same process **locally**. + +--- + +## Prerequisites + +- Node.js 20.x +- `forge` (Foundry) installed and available on your `PATH` +- From the repo root, run once (or whenever dependencies change): + +```bash +cd .github/scripts/generate-docs-utils/templates +npm install +cd ../../../.. +``` + +--- + +## Basic local workflow + +All commands below are run from the **repo root**. + +### 1. Generate base forge docs + +```bash +forge doc +``` + +This produces the raw contract documentation that the generator consumes. + +### 2. Run the docs generator + +#### Process all Solidity files + +```bash +node .github/scripts/generate-docs.js --all +``` + +#### Process specific Solidity files + +Create a text file with one Solidity path per line (relative to the repo root, usually under `src/`), for example: + +```bash +printf "src/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchFacet.sol\n" > /tmp/changed_sol_files.txt +``` + +Then run: + +```bash +node .github/scripts/generate-docs.js /tmp/changed_sol_files.txt +``` + +--- + +## AI enhancement controls + +By default, the generator can call an AI provider to enhance descriptions. In CI, this is controlled by the `SKIP_ENHANCEMENT` input on the workflow. Locally, you can control this via an environment variable: + +- **Disable AI enhancement**: + +```bash +SKIP_ENHANCEMENT=true node .github/scripts/generate-docs.js --all +``` + +- **Enable AI enhancement** (requires a valid provider key, see `.github/scripts/ai-provider/README.md`): + +```bash +GITHUB_TOKEN= \ +GOOGLE_AI_API_KEY= \ +node .github/scripts/generate-docs.js --all +``` + +If no valid provider/key is available, the generator falls back to non‑AI content. + +--- + +## Verifying the docs site build + +After generating docs, you can ensure the documentation site still builds: + +```bash +cd website +npm ci +npm run build +``` + +New or updated pages should appear under `website/docs/library`. + diff --git a/.github/scripts/generate-docs-utils/ai/ai-enhancement.js b/.github/scripts/generate-docs-utils/ai/ai-enhancement.js new file mode 100644 index 00000000..4edf760c --- /dev/null +++ b/.github/scripts/generate-docs-utils/ai/ai-enhancement.js @@ -0,0 +1,76 @@ +/** + * AI Enhancement + * + * Orchestrates AI-powered documentation enhancement. + */ + +const ai = require('../../ai-provider'); +const { buildSystemPrompt, buildPrompt } = require('./prompt-builder'); +const { extractJSON, convertEnhancedFields } = require('./response-parser'); +const { addFallbackContent } = require('./fallback-content-provider'); + +/** + * Check if enhancement should be skipped for a file + * @param {object} data - Documentation data + * @returns {boolean} True if should skip + */ +function shouldSkipEnhancement(data) { + if (!data.functions || data.functions.length === 0) { + return true; + } + + if (data.title.startsWith('I') && data.title.length > 1 && + data.title[1] === data.title[1].toUpperCase()) { + return true; + } + + return false; +} + +/** + * Enhance documentation data using AI + * @param {object} data - Parsed documentation data + * @param {'module' | 'facet'} contractType - Type of contract + * @param {string} token - Legacy token parameter (deprecated, uses env vars now) + * @returns {Promise<{data: object, usedFallback: boolean, error?: string}>} Enhanced data with fallback status + */ +async function enhanceWithAI(data, contractType, token) { + try { + const systemPrompt = buildSystemPrompt(); + const userPrompt = buildPrompt(data, contractType); + + // Call AI provider + const responseText = await ai.call(systemPrompt, userPrompt, { + onSuccess: () => { + // Silent success - no logging + }, + onError: () => { + // Silent error - will be caught below + } + }); + + // Parse JSON response + let enhanced; + try { + enhanced = JSON.parse(responseText); + } catch (directParseError) { + const cleanedContent = extractJSON(responseText); + enhanced = JSON.parse(cleanedContent); + } + + return { data: convertEnhancedFields(enhanced, data), usedFallback: false }; + + } catch (error) { + return { + data: addFallbackContent(data, contractType), + usedFallback: true, + error: error.message + }; + } +} + +module.exports = { + enhanceWithAI, + shouldSkipEnhancement, +}; + diff --git a/.github/scripts/generate-docs-utils/ai/context-extractor.js b/.github/scripts/generate-docs-utils/ai/context-extractor.js new file mode 100644 index 00000000..abf3b490 --- /dev/null +++ b/.github/scripts/generate-docs-utils/ai/context-extractor.js @@ -0,0 +1,250 @@ +/** + * Context Extractor for AI Documentation Enhancement + * + * Extracts and formats additional context from source files and parsed data + * to provide richer information to the AI for more accurate documentation generation. + */ + +const fs = require('fs'); +const path = require('path'); +const { readFileSafe } = require('../../workflow-utils'); +const { findRelatedContracts } = require('../core/relationship-detector'); +const { getContractRegistry } = require('../core/contract-registry'); + +/** + * Extract context from source file (pragma, imports, etc.) + * @param {string} sourceFilePath - Path to the Solidity source file + * @returns {object} Extracted source context + */ +function extractSourceContext(sourceFilePath) { + if (!sourceFilePath) { + return { + pragmaVersion: null, + imports: [], + }; + } + + const sourceContent = readFileSafe(sourceFilePath); + if (!sourceContent) { + return { + pragmaVersion: null, + imports: [], + }; + } + + // Extract pragma version + const pragmaMatch = sourceContent.match(/pragma\s+solidity\s+([^;]+);/); + const pragmaVersion = pragmaMatch ? pragmaMatch[1].trim() : null; + + // Extract imports + const importMatches = sourceContent.matchAll(/import\s+["']([^"']+)["']/g); + const imports = Array.from(importMatches, m => m[1]); + + return { + pragmaVersion, + imports, + }; +} + +/** + * Compute import path from source file path + * Converts: src/access/AccessControl/AccessControlFacet.sol + * To: @compose/access/AccessControl/AccessControlFacet + * @param {string} sourceFilePath - Path to the Solidity source file + * @returns {string} Import path + */ +function computeImportPath(sourceFilePath) { + if (!sourceFilePath) { + return null; + } + + // Remove src/ prefix and .sol extension + let importPath = sourceFilePath + .replace(/^src\//, '') + .replace(/\.sol$/, ''); + + // Convert to @compose/ format + return `@compose/${importPath}`; +} + +/** + * Format complete function signatures with parameter types and return types + * @param {Array} functions - Array of function objects + * @returns {string} Formatted function signatures + */ +function formatFunctionSignatures(functions) { + if (!functions || functions.length === 0) { + return 'None'; + } + + return functions.map(fn => { + // Format parameters + const params = (fn.params || []).map(p => { + const type = p.type || ''; + const name = p.name || ''; + if (!type && !name) return ''; + return name ? `${type} ${name}` : type; + }).filter(Boolean).join(', '); + + // Format return types + const returns = (fn.returns || []).map(r => r.type || '').filter(Boolean); + const returnStr = returns.length > 0 ? ` returns (${returns.join(', ')})` : ''; + + // Include visibility and mutability if available in signature + const signature = fn.signature || ''; + const visibility = signature.match(/\b(public|external|internal|private)\b/)?.[0] || ''; + const mutability = signature.match(/\b(view|pure|payable)\b/)?.[0] || ''; + + const modifiers = [visibility, mutability].filter(Boolean).join(' '); + + return `function ${fn.name}(${params})${modifiers ? ' ' + modifiers : ''}${returnStr}`; + }).join('\n'); +} + +/** + * Format storage context information + * @param {object} storageInfo - Storage info object + * @param {Array} structs - Array of struct definitions + * @param {Array} stateVariables - Array of state variables + * @returns {string} Formatted storage context + */ +function formatStorageContext(storageInfo, structs, stateVariables) { + const parts = []; + + // Extract storage position from state variables + const storagePositionVar = (stateVariables || []).find(v => + v.name && (v.name.includes('STORAGE_POSITION') || v.name.includes('STORAGE') || v.name.includes('_POSITION')) + ); + + if (storagePositionVar) { + parts.push(`Storage Position: ${storagePositionVar.name}`); + if (storagePositionVar.value) { + parts.push(`Value: ${storagePositionVar.value}`); + } + if (storagePositionVar.description) { + parts.push(`Description: ${storagePositionVar.description}`); + } + } + + // Extract storage struct + const storageStruct = (structs || []).find(s => + s.name && s.name.includes('Storage') + ); + + if (storageStruct) { + parts.push(`Storage Struct: ${storageStruct.name}`); + if (storageStruct.definition) { + // Extract key fields from struct definition + const fieldMatches = storageStruct.definition.matchAll(/(\w+)\s+(\w+)(?:\[.*?\])?;/g); + const fields = Array.from(fieldMatches, m => `${m[1]} ${m[2]}`); + if (fields.length > 0) { + parts.push(`Key Fields: ${fields.slice(0, 5).join(', ')}${fields.length > 5 ? '...' : ''}`); + } + } + } + + // Add storage info if available + if (storageInfo) { + if (typeof storageInfo === 'string') { + parts.push(storageInfo); + } else if (storageInfo.storagePosition) { + parts.push(`Storage Position: ${storageInfo.storagePosition}`); + } + } + + return parts.length > 0 ? parts.join('\n') : 'None'; +} + +/** + * Format related contracts context + * @param {string} contractName - Name of the contract + * @param {string} contractType - Type of contract ('module' or 'facet') + * @param {string} category - Category of the contract + * @param {object} registry - Contract registry (optional) + * @returns {string} Formatted related contracts context + */ +function formatRelatedContracts(contractName, contractType, category, registry = null) { + const related = findRelatedContracts(contractName, contractType, category, registry); + + if (related.length === 0) { + return 'None'; + } + + return related.map(r => `- ${r.title}: ${r.description}`).join('\n'); +} + +/** + * Format struct definitions with field types + * @param {Array} structs - Array of struct objects + * @returns {string} Formatted struct definitions + */ +function formatStructDefinitions(structs) { + if (!structs || structs.length === 0) { + return 'None'; + } + + return structs.map(s => { + const fields = (s.fields || []).map(f => { + const type = f.type || ''; + const name = f.name || ''; + return name ? `${type} ${name}` : type; + }).join(', '); + + return `struct ${s.name} { ${fields} }`; + }).join('\n'); +} + +/** + * Format event signatures with parameters + * @param {Array} events - Array of event objects + * @returns {string} Formatted event signatures + */ +function formatEventSignatures(events) { + if (!events || events.length === 0) { + return 'None'; + } + + return events.map(e => { + const params = (e.params || []).map(p => { + const indexed = p.indexed ? 'indexed ' : ''; + const type = p.type || ''; + const name = p.name || ''; + return name ? `${indexed}${type} ${name}` : `${indexed}${type}`; + }).join(', '); + + return `event ${e.name}(${params})`; + }).join('\n'); +} + +/** + * Format error signatures with parameters + * @param {Array} errors - Array of error objects + * @returns {string} Formatted error signatures + */ +function formatErrorSignatures(errors) { + if (!errors || errors.length === 0) { + return 'None'; + } + + return errors.map(e => { + const params = (e.params || []).map(p => { + const type = p.type || ''; + const name = p.name || ''; + return name ? `${type} ${name}` : type; + }).join(', '); + + return `error ${e.name}(${params})`; + }).join('\n'); +} + +module.exports = { + extractSourceContext, + computeImportPath, + formatFunctionSignatures, + formatStorageContext, + formatRelatedContracts, + formatStructDefinitions, + formatEventSignatures, + formatErrorSignatures, +}; + diff --git a/.github/scripts/generate-docs-utils/ai/fallback-content-provider.js b/.github/scripts/generate-docs-utils/ai/fallback-content-provider.js new file mode 100644 index 00000000..1afabac0 --- /dev/null +++ b/.github/scripts/generate-docs-utils/ai/fallback-content-provider.js @@ -0,0 +1,36 @@ +/** + * Fallback Content Provider + * + * Provides fallback content when AI enhancement is unavailable. + * Centralizes fallback content logic to avoid duplication. + */ + +const { loadPrompts } = require('./prompt-loader'); + +/** + * Add fallback content when AI is unavailable + * @param {object} data - Documentation data + * @param {'module' | 'facet'} contractType - Type of contract + * @returns {object} Data with fallback content + */ +function addFallbackContent(data, contractType) { + const prompts = loadPrompts(); + const enhanced = { ...data }; + + if (contractType === 'module') { + enhanced.integrationNotes = prompts.moduleFallback.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.`; + enhanced.keyFeatures = prompts.moduleFallback.keyFeatures || + `- All functions are \`internal\` for use in custom facets\n- Follows diamond storage pattern (EIP-8042)\n- Compatible with ERC-2535 diamonds\n- No external dependencies or \`using\` directives`; + } else { + enhanced.keyFeatures = prompts.facetFallback.keyFeatures || + `- Self-contained facet with no imports or inheritance\n- Only \`external\` and \`internal\` function visibility\n- Follows Compose readability-first conventions\n- Ready for diamond integration`; + } + + return enhanced; +} + +module.exports = { + addFallbackContent, +}; + diff --git a/.github/scripts/generate-docs-utils/ai/prompt-builder.js b/.github/scripts/generate-docs-utils/ai/prompt-builder.js new file mode 100644 index 00000000..362ecf02 --- /dev/null +++ b/.github/scripts/generate-docs-utils/ai/prompt-builder.js @@ -0,0 +1,186 @@ +/** + * Prompt Builder + * + * Builds system and user prompts for AI enhancement. + */ + +const { + extractSourceContext, + computeImportPath, + formatFunctionSignatures, + formatStorageContext, + formatRelatedContracts, + formatStructDefinitions, + formatEventSignatures, + formatErrorSignatures, +} = require('./context-extractor'); +const { getContractRegistry } = require('../core/contract-registry'); +const { loadPrompts, loadRepoInstructions } = require('./prompt-loader'); + +/** + * Build the system prompt with repository context + * Uses the system prompt from the prompts file, or a fallback if not found + * @returns {string} System prompt for AI + */ +function buildSystemPrompt() { + const prompts = loadPrompts(); + const repoInstructions = loadRepoInstructions(); + + let systemPrompt = prompts.systemPrompt || `You are a Solidity smart contract documentation expert for the Compose framework. +Always respond with valid JSON only, no markdown formatting. +Follow the project conventions and style guidelines strictly.`; + + if (repoInstructions) { + const relevantSections = prompts.relevantSections.length > 0 + ? prompts.relevantSections + : [ + '## 3. Core Philosophy', + '## 4. Facet Design Principles', + '## 5. Banned Solidity Features', + '## 6. Composability Guidelines', + '## 11. Code Style Guide', + ]; + + let contextSnippets = []; + for (const section of relevantSections) { + const startIdx = repoInstructions.indexOf(section); + if (startIdx !== -1) { + // Extract section content (up to next ## or 2000 chars max) + const nextSection = repoInstructions.indexOf('\n## ', startIdx + section.length); + const endIdx = nextSection !== -1 ? nextSection : startIdx + 2000; + const snippet = repoInstructions.slice(startIdx, Math.min(endIdx, startIdx + 2000)); + contextSnippets.push(snippet.trim()); + } + } + + if (contextSnippets.length > 0) { + systemPrompt += `\n\n--- PROJECT GUIDELINES ---\n${contextSnippets.join('\n\n')}`; + } + } + + return systemPrompt; +} + +/** + * Build the prompt for AI based on contract type + * @param {object} data - Parsed documentation data + * @param {'module' | 'facet'} contractType - Type of contract + * @returns {string} Prompt for AI + */ +function buildPrompt(data, contractType) { + const prompts = loadPrompts(); + + const functionNames = data.functions.map(f => f.name).join(', '); + const functionDescriptions = data.functions + .map(f => `- ${f.name}: ${f.description || 'No description'}`) + .join('\n'); + + // Include events and errors for richer context + const eventNames = (data.events || []).map(e => e.name).join(', '); + const errorNames = (data.errors || []).map(e => e.name).join(', '); + + // Extract additional context + const sourceContext = extractSourceContext(data.sourceFilePath); + const importPath = computeImportPath(data.sourceFilePath); + const functionSignatures = formatFunctionSignatures(data.functions); + const eventSignatures = formatEventSignatures(data.events); + const errorSignatures = formatErrorSignatures(data.errors); + const structDefinitions = formatStructDefinitions(data.structs); + + // Get storage context + const storageContext = formatStorageContext( + data.storageInfo, + data.structs, + data.stateVariables + ); + + // Get related contracts context + const registry = getContractRegistry(); + // Try to get category from registry entry, or use empty string + const registryEntry = registry.byName.get(data.title); + const category = data.category || (registryEntry ? registryEntry.category : ''); + const relatedContracts = formatRelatedContracts( + data.title, + contractType, + category, + registry + ); + + const promptTemplate = contractType === 'module' + ? prompts.modulePrompt + : prompts.facetPrompt; + + // If we have a template from the file, use it with variable substitution + if (promptTemplate) { + return promptTemplate + .replace(/\{\{title\}\}/g, data.title) + .replace(/\{\{description\}\}/g, data.description || 'No description provided') + .replace(/\{\{functionNames\}\}/g, functionNames || 'None') + .replace(/\{\{functionDescriptions\}\}/g, functionDescriptions || ' None') + .replace(/\{\{eventNames\}\}/g, eventNames || 'None') + .replace(/\{\{errorNames\}\}/g, errorNames || 'None') + .replace(/\{\{functionSignatures\}\}/g, functionSignatures || 'None') + .replace(/\{\{eventSignatures\}\}/g, eventSignatures || 'None') + .replace(/\{\{errorSignatures\}\}/g, errorSignatures || 'None') + .replace(/\{\{importPath\}\}/g, importPath || 'N/A') + .replace(/\{\{pragmaVersion\}\}/g, sourceContext.pragmaVersion || '^0.8.30') + .replace(/\{\{storageContext\}\}/g, storageContext || 'None') + .replace(/\{\{relatedContracts\}\}/g, relatedContracts || 'None') + .replace(/\{\{structDefinitions\}\}/g, structDefinitions || 'None'); + } + + // Fallback to hardcoded prompt if template not loaded + return `Given this ${contractType} documentation from the Compose diamond proxy framework, enhance it by generating: + +1. **description**: A concise one-line description (max 100 chars) for the page subtitle. Derive this from the contract's purpose based on its functions, events, and errors. + +2. **overview**: A clear, concise overview (2-3 sentences) explaining what this ${contractType} does and why it's useful in the context of diamond contracts. + +3. **usageExample**: A practical Solidity code example (10-20 lines) showing how to use this ${contractType}. For modules, show importing and calling functions. For facets, show how it would be used in a diamond. Use the EXACT import path and function signatures provided below. + +4. **bestPractices**: 2-3 bullet points of best practices for using this ${contractType}. + +${contractType === 'module' ? '5. **integrationNotes**: A note about how this module works with diamond storage pattern and how changes made through it are visible to facets.' : ''} + +${contractType === 'facet' ? '5. **securityConsiderations**: Important security considerations when using this facet (access control, reentrancy, etc.).' : ''} + +6. **keyFeatures**: A brief bullet list of key features. + +Contract Information: +- Name: ${data.title} +- Current Description: ${data.description || 'No description provided'} +- Import Path: ${importPath || 'N/A'} +- Pragma Version: ${sourceContext.pragmaVersion || '^0.8.30'} +- Functions: ${functionNames || 'None'} +- Function Signatures: +${functionSignatures || ' None'} +- Events: ${eventNames || 'None'} +- Event Signatures: +${eventSignatures || ' None'} +- Errors: ${errorNames || 'None'} +- Error Signatures: +${errorSignatures || ' None'} +- Function Details: +${functionDescriptions || ' None'} +${storageContext && storageContext !== 'None' ? `\n- Storage Information:\n${storageContext}` : ''} +${relatedContracts && relatedContracts !== 'None' ? `\n- Related Contracts:\n${relatedContracts}` : ''} +${structDefinitions && structDefinitions !== 'None' ? `\n- Struct Definitions:\n${structDefinitions}` : ''} + +IMPORTANT: Use the EXACT function signatures, import paths, and storage information provided above. Do not invent or modify function names, parameter types, or import paths. + +Respond ONLY with valid JSON in this exact format (no markdown code blocks, no extra text): +{ + "description": "concise one-line description here", + "overview": "enhanced overview text here", + "usageExample": "solidity code here (use \\n for newlines)", + "bestPractices": "- Point 1\\n- Point 2\\n- Point 3", + "keyFeatures": "- Feature 1\\n- Feature 2", + ${contractType === 'module' ? '"integrationNotes": "integration notes here"' : '"securityConsiderations": "security notes here"'} +}`; +} + +module.exports = { + buildSystemPrompt, + buildPrompt, +}; + diff --git a/.github/scripts/generate-docs-utils/ai/prompt-loader.js b/.github/scripts/generate-docs-utils/ai/prompt-loader.js new file mode 100644 index 00000000..f4f8cd1f --- /dev/null +++ b/.github/scripts/generate-docs-utils/ai/prompt-loader.js @@ -0,0 +1,132 @@ +/** + * Prompt Loader + * + * Loads and parses AI prompts from markdown files. + */ + +const fs = require('fs'); +const path = require('path'); + +const AI_PROMPT_PATH = path.join(__dirname, '../../../docs-gen-prompts.md'); +const REPO_INSTRUCTIONS_PATH = path.join(__dirname, '../../../copilot-instructions.md'); + +// Cache loaded prompts +let cachedPrompts = null; +let cachedRepoInstructions = null; + +/** + * Load repository instructions for context + * @returns {string} Repository instructions content + */ +function loadRepoInstructions() { + if (cachedRepoInstructions !== null) { + return cachedRepoInstructions; + } + + try { + cachedRepoInstructions = fs.readFileSync(REPO_INSTRUCTIONS_PATH, 'utf8'); + } catch (e) { + console.warn('Could not load copilot-instructions.md:', e.message); + cachedRepoInstructions = ''; + } + + return cachedRepoInstructions; +} + +/** + * Parse the prompts markdown file to extract individual prompts + * @param {string} content - Raw markdown content + * @returns {object} Parsed prompts and configurations + */ +function parsePromptsFile(content) { + const sections = content.split(/^---$/m).map(s => s.trim()).filter(Boolean); + + const prompts = { + systemPrompt: '', + modulePrompt: '', + facetPrompt: '', + relevantSections: [], + moduleFallback: { integrationNotes: '', keyFeatures: '' }, + facetFallback: { keyFeatures: '' }, + }; + + for (const section of sections) { + if (section.includes('## System Prompt')) { + const match = section.match(/## System Prompt\s*\n([\s\S]*)/); + if (match) { + prompts.systemPrompt = match[1].trim(); + } + } else if (section.includes('## Relevant Guideline Sections')) { + // Extract sections from the code block + const codeMatch = section.match(/```\n([\s\S]*?)```/); + if (codeMatch) { + prompts.relevantSections = codeMatch[1] + .split('\n') + .map(s => s.trim()) + .filter(s => s.startsWith('## ')); + } + } else if (section.includes('## Module Prompt Template')) { + const match = section.match(/## Module Prompt Template\s*\n([\s\S]*)/); + if (match) { + prompts.modulePrompt = match[1].trim(); + } + } else if (section.includes('## Facet Prompt Template')) { + const match = section.match(/## Facet Prompt Template\s*\n([\s\S]*)/); + if (match) { + prompts.facetPrompt = match[1].trim(); + } + } else if (section.includes('## Module Fallback Content')) { + // Parse subsections for integrationNotes and keyFeatures + const integrationMatch = section.match(/### integrationNotes\s*\n([\s\S]*?)(?=###|$)/); + if (integrationMatch) { + prompts.moduleFallback.integrationNotes = integrationMatch[1].trim(); + } + const keyFeaturesMatch = section.match(/### keyFeatures\s*\n([\s\S]*?)(?=###|$)/); + if (keyFeaturesMatch) { + prompts.moduleFallback.keyFeatures = keyFeaturesMatch[1].trim(); + } + } else if (section.includes('## Facet Fallback Content')) { + const keyFeaturesMatch = section.match(/### keyFeatures\s*\n([\s\S]*?)(?=###|$)/); + if (keyFeaturesMatch) { + prompts.facetFallback.keyFeatures = keyFeaturesMatch[1].trim(); + } + } + } + + return prompts; +} + +/** + * Load AI prompts from markdown file + * @returns {object} Parsed prompts object + */ +function loadPrompts() { + if (cachedPrompts !== null) { + return cachedPrompts; + } + + const defaultPrompts = { + systemPrompt: '', + modulePrompt: '', + facetPrompt: '', + relevantSections: [], + moduleFallback: { integrationNotes: '', keyFeatures: '' }, + facetFallback: { keyFeatures: '' }, + }; + + try { + const promptsContent = fs.readFileSync(AI_PROMPT_PATH, 'utf8'); + cachedPrompts = parsePromptsFile(promptsContent); + } catch (e) { + console.warn('Could not load ai-prompts.md:', e.message); + cachedPrompts = defaultPrompts; + } + + return cachedPrompts; +} + +module.exports = { + loadPrompts, + loadRepoInstructions, +}; + diff --git a/.github/scripts/generate-docs-utils/ai/response-parser.js b/.github/scripts/generate-docs-utils/ai/response-parser.js new file mode 100644 index 00000000..a7e7204e --- /dev/null +++ b/.github/scripts/generate-docs-utils/ai/response-parser.js @@ -0,0 +1,137 @@ +/** + * Response Parser + * + * Parses and cleans AI response content. + */ + +/** + * Extract and clean JSON from API response + * Handles markdown code blocks, wrapped text, and attempts to fix truncated JSON + * Also removes control characters that break JSON parsing + * @param {string} content - Raw API response content + * @returns {string} Cleaned JSON string ready for parsing + */ +function extractJSON(content) { + if (!content || typeof content !== 'string') { + return content; + } + + let cleaned = content.trim(); + + // Remove markdown code blocks (```json ... ``` or ``` ... ```) + // Handle both at start and anywhere in the string + cleaned = cleaned.replace(/^```(?:json)?\s*\n?/gm, ''); + cleaned = cleaned.replace(/\n?```\s*$/gm, ''); + cleaned = cleaned.trim(); + + // Remove control characters (0x00-0x1F except newline, tab, carriage return) + // These are illegal in JSON strings and cause "Bad control character" parsing errors + cleaned = cleaned.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, ''); + + // Find the first { and last } to extract JSON object + const firstBrace = cleaned.indexOf('{'); + const lastBrace = cleaned.lastIndexOf('}'); + + if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) { + cleaned = cleaned.substring(firstBrace, lastBrace + 1); + } else if (firstBrace !== -1) { + // We have a { but no closing }, JSON might be truncated + cleaned = cleaned.substring(firstBrace); + } + + // Try to fix common truncation issues + const openBraces = (cleaned.match(/\{/g) || []).length; + const closeBraces = (cleaned.match(/\}/g) || []).length; + + if (openBraces > closeBraces) { + // JSON might be truncated - try to close incomplete strings and objects + // Check if we're in the middle of a string (simple heuristic) + const lastChar = cleaned[cleaned.length - 1]; + const lastQuote = cleaned.lastIndexOf('"'); + const lastBraceInCleaned = cleaned.lastIndexOf('}'); + + // If last quote is after last brace and not escaped, we might be in a string + if (lastQuote > lastBraceInCleaned && lastChar !== '"') { + // Check if the quote before last is escaped + let isEscaped = false; + for (let i = lastQuote - 1; i >= 0 && cleaned[i] === '\\'; i--) { + isEscaped = !isEscaped; + } + + if (!isEscaped) { + // We're likely in an incomplete string, close it + cleaned = cleaned + '"'; + } + } + + // Close any incomplete objects/arrays + const missingBraces = openBraces - closeBraces; + // Try to intelligently close - if we're in the middle of a property, add a value first + const trimmed = cleaned.trim(); + if (trimmed.endsWith(',') || trimmed.endsWith(':')) { + // We're in the middle of a property, add null and close + cleaned = cleaned.replace(/[,:]\s*$/, ': null'); + } + cleaned = cleaned + '\n' + '}'.repeat(missingBraces); + } + + return cleaned.trim(); +} + +/** + * Convert literal \n strings to actual newlines + * @param {string} str - String with escaped newlines + * @returns {string} String with actual newlines + */ +function convertNewlines(str) { + if (!str || typeof str !== 'string') return str; + return str.replace(/\\n/g, '\n'); +} + +/** + * Decode HTML entities (for code blocks) + * @param {string} str - String with HTML entities + * @returns {string} Decoded string + */ +function decodeHtmlEntities(str) { + if (!str || typeof str !== 'string') return str; + return str + .replace(/"/g, '"') + .replace(/=/g, '=') + .replace(/=>/g, '=>') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/'/g, "'") + .replace(/&/g, '&'); +} + +/** + * Convert enhanced data fields (newlines, HTML entities) + * @param {object} enhanced - Parsed JSON from API + * @param {object} data - Original documentation data + * @returns {object} Enhanced data with converted fields + */ +function convertEnhancedFields(enhanced, data) { + // Use AI-generated description if provided, otherwise keep original + const aiDescription = enhanced.description?.trim(); + const finalDescription = aiDescription || data.description; + + return { + ...data, + // Description is used for page subtitle - AI improves it from NatSpec + description: finalDescription, + subtitle: finalDescription, + overview: convertNewlines(enhanced.overview) || data.overview, + usageExample: decodeHtmlEntities(convertNewlines(enhanced.usageExample)) || null, + bestPractices: convertNewlines(enhanced.bestPractices) || null, + keyFeatures: convertNewlines(enhanced.keyFeatures) || null, + integrationNotes: convertNewlines(enhanced.integrationNotes) || null, + securityConsiderations: convertNewlines(enhanced.securityConsiderations) || null, + }; +} + +module.exports = { + extractJSON, + convertEnhancedFields, +}; + diff --git a/.github/scripts/generate-docs-utils/category/category-generator.js b/.github/scripts/generate-docs-utils/category/category-generator.js new file mode 100644 index 00000000..7068eca3 --- /dev/null +++ b/.github/scripts/generate-docs-utils/category/category-generator.js @@ -0,0 +1,628 @@ +/** + * Category Generator + * + * Automatically generates _category_.json files to mirror + * the src/ folder structure in the documentation. + * + * This module provides: + * - Source structure scanning + * - Category file generation + * - Path computation for doc output + * - Structure synchronization + */ + +const fs = require('fs'); +const path = require('path'); +const CONFIG = require('../config'); +const { + getCategoryItems, + createCategoryIndexFile: createIndexFile, +} = require('./index-page-generator'); + +// ============================================================================ +// Constants +// ============================================================================ + +/** + * Human-readable labels for directory names + * Add new entries here when adding new top-level categories + */ +const CATEGORY_LABELS = { + // Top-level categories + access: 'Access Control', + token: 'Token Standards', + diamond: 'Diamond Core', + libraries: 'Utilities', + utils: 'Utilities', + interfaceDetection: 'Interface Detection', + + // Token subcategories + ERC20: 'ERC-20', + ERC721: 'ERC-721', + ERC1155: 'ERC-1155', + ERC6909: 'ERC-6909', + Royalty: 'Royalty', + + // Access subcategories + AccessControl: 'Access Control', + AccessControlPausable: 'Pausable Access Control', + AccessControlTemporal: 'Temporal Access Control', + Owner: 'Owner', + OwnerTwoSteps: 'Two-Step Owner', +}; + +/** + * Descriptions for categories + * Add new entries here for custom descriptions + */ +const CATEGORY_DESCRIPTIONS = { + // Top-level categories + access: 'Access control patterns for permission management in Compose diamonds.', + token: 'Token standard implementations for Compose diamonds.', + diamond: 'Core diamond proxy functionality for ERC-2535 diamonds.', + libraries: 'Utility libraries and helpers for diamond development.', + utils: 'Utility libraries and helpers for diamond development.', + interfaceDetection: 'ERC-165 interface detection support.', + + // Token subcategories + ERC20: 'ERC-20 fungible token implementations.', + ERC721: 'ERC-721 non-fungible token implementations.', + ERC1155: 'ERC-1155 multi-token implementations.', + ERC6909: 'ERC-6909 minimal multi-token implementations.', + Royalty: 'ERC-2981 royalty standard implementations.', + + // Access subcategories + AccessControl: 'Role-based access control (RBAC) pattern.', + AccessControlPausable: 'RBAC with pause functionality.', + AccessControlTemporal: 'Time-limited role-based access control.', + Owner: 'Single-owner access control pattern.', + OwnerTwoSteps: 'Two-step ownership transfer pattern.', +}; + +/** + * Sidebar positions for categories + * Lower numbers appear first in the sidebar + */ +const CATEGORY_POSITIONS = { + // Top-level (lower = higher priority) + diamond: 1, + access: 2, + token: 3, + libraries: 4, + utils: 4, + interfaceDetection: 5, + + // Token subcategories + ERC20: 1, + ERC721: 2, + ERC1155: 3, + ERC6909: 4, + Royalty: 5, + + // Access subcategories + Owner: 1, + OwnerTwoSteps: 2, + AccessControl: 3, + AccessControlPausable: 4, + AccessControlTemporal: 5, + + // Leaf directories (ERC20/ERC20, etc.) - alphabetical + ERC20Bridgeable: 2, + ERC20Permit: 3, + ERC721Enumerable: 2, +}; + +// ============================================================================ +// Label & Description Generation +// ============================================================================ + +/** + * Generate a human-readable label from a directory name + * @param {string} name - Directory name (e.g., 'AccessControlPausable', 'ERC20') + * @returns {string} Human-readable label + */ +function generateLabel(name) { + // Check explicit mapping first + if (CATEGORY_LABELS[name]) { + return CATEGORY_LABELS[name]; + } + + // Handle ERC standards specially + if (/^ERC\d+/.test(name)) { + const match = name.match(/^(ERC)(\d+)(.*)$/); + if (match) { + const variant = match[3] + ? ' ' + match[3].replace(/([A-Z])/g, ' $1').trim() + : ''; + return `ERC-${match[2]}${variant}`; + } + return name; + } + + // CamelCase to Title Case with spaces + return name.replace(/([A-Z])/g, ' $1').replace(/^ /, '').trim(); +} + +/** + * Generate description for a category based on its path + * @param {string} name - Directory name + * @param {string[]} parentPath - Parent path segments + * @returns {string} Category description + */ +function generateDescription(name, parentPath = []) { + // Check explicit mapping first + if (CATEGORY_DESCRIPTIONS[name]) { + return CATEGORY_DESCRIPTIONS[name]; + } + + // Generate from context + const label = generateLabel(name); + const parent = parentPath[parentPath.length - 1]; + + if (parent === 'token') { + return `${label} token implementations with modules and facets.`; + } + if (parent === 'access') { + return `${label} access control pattern for Compose diamonds.`; + } + if (parent === 'ERC20' || parent === 'ERC721') { + return `${label} extension for ${generateLabel(parent)} tokens.`; + } + + return `${label} components for Compose diamonds.`; +} + +/** + * Get sidebar position for a category + * @param {string} name - Directory name + * @param {number} depth - Nesting depth + * @returns {number} Sidebar position + */ +function getCategoryPosition(name, depth) { + if (CATEGORY_POSITIONS[name] !== undefined) { + return CATEGORY_POSITIONS[name]; + } + return 99; // Default to end +} + +// ============================================================================ +// Source Structure Scanning +// ============================================================================ + +/** + * Check if a directory contains .sol files (directly or in subdirectories) + * @param {string} dirPath - Directory path to check + * @returns {boolean} True if contains .sol files + */ +function containsSolFiles(dirPath) { + try { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + + for (const entry of entries) { + if (entry.isFile() && entry.name.endsWith('.sol')) { + return true; + } + if (entry.isDirectory() && !entry.name.startsWith('.')) { + if (containsSolFiles(path.join(dirPath, entry.name))) { + return true; + } + } + } + } catch (error) { + console.warn(`Warning: Could not read directory ${dirPath}: ${error.message}`); + } + + return false; +} + +/** + * Scan the src/ directory and build structure map + * @returns {Map} Map of relative paths to category info + */ +function scanSourceStructure() { + const srcDir = CONFIG.srcDir || 'src'; + const structure = new Map(); + + function scanDir(dirPath, relativePath = '') { + let entries; + try { + entries = fs.readdirSync(dirPath, { withFileTypes: true }); + } catch (error) { + console.error(`Error reading directory ${dirPath}: ${error.message}`); + return; + } + + for (const entry of entries) { + if (!entry.isDirectory()) continue; + + // Skip hidden directories and interfaces + if (entry.name.startsWith('.') || entry.name === 'interfaces') { + continue; + } + + const fullPath = path.join(dirPath, entry.name); + const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name; + + // Only include directories that contain .sol files + if (containsSolFiles(fullPath)) { + const parts = relPath.split('/'); + structure.set(relPath, { + name: entry.name, + path: relPath, + depth: parts.length, + parent: relativePath || null, + parentParts: relativePath ? relativePath.split('/') : [], + }); + + // Recurse into subdirectories + scanDir(fullPath, relPath); + } + } + } + + if (fs.existsSync(srcDir)) { + scanDir(srcDir); + } else { + console.warn(`Warning: Source directory ${srcDir} does not exist`); + } + + return structure; +} + +// ============================================================================ +// Category File Generation +// ============================================================================ + +/** + * Map source directory name to docs directory name + * @param {string} srcName - Source directory name + * @returns {string} Documentation directory name + */ +function mapDirectoryName(srcName) { + // Map libraries -> utils for URL consistency + if (srcName === 'libraries') { + return 'utils'; + } + return srcName; +} + +/** + * Compute slug from output directory path + * @param {string} outputDir - Full output directory path + * @param {string} libraryDir - Base library directory + * @returns {string} Slug path (e.g., '/docs/library/access') + */ +function computeSlug(outputDir, libraryDir) { + const relativePath = path.relative(libraryDir, outputDir); + + if (!relativePath || relativePath.startsWith('..')) { + // Root library directory + return '/docs/library'; + } + + // Convert path separators and create slug + const normalizedPath = relativePath.replace(/\\/g, '/'); + return `/docs/library/${normalizedPath}`; +} + +/** + * Wrapper function to create category index file using the index-page-generator utility + * @param {string} outputDir - Directory to create index file in + * @param {string} relativePath - Relative path from library dir + * @param {string} label - Category label + * @param {string} description - Category description + * @param {boolean} overwrite - Whether to overwrite existing files (default: false) + * @param {boolean} hideFromSidebar - Whether to hide the index page from sidebar (default: false) + * @returns {boolean} True if file was created/updated, false if skipped + */ +function createCategoryIndexFile(outputDir, relativePath, label, description, overwrite = false, hideFromSidebar = false) { + return createIndexFile( + outputDir, + relativePath, + label, + description, + generateLabel, + generateDescription, + overwrite, + hideFromSidebar + ); +} + +/** + * Create a _category_.json file for a directory + * @param {string} outputDir - Directory to create category file in + * @param {string} name - Directory name + * @param {string} relativePath - Relative path from library dir + * @param {number} depth - Nesting depth + * @returns {boolean} True if file was created, false if it already existed + */ +function createCategoryFile(outputDir, name, relativePath, depth) { + const categoryFile = path.join(outputDir, '_category_.json'); + const libraryDir = CONFIG.libraryOutputDir || 'website/docs/library'; + + // Don't overwrite existing category files (allows manual customization) + if (fs.existsSync(categoryFile)) { + return false; + } + + // Get the actual directory name from the output path (may be mapped, e.g., utils instead of libraries) + const actualDirName = path.basename(outputDir); + const parentParts = relativePath.split('/').slice(0, -1); + // Use actual directory name for label generation (supports both original and mapped names) + const label = generateLabel(actualDirName); + const position = getCategoryPosition(actualDirName, depth); + const description = generateDescription(actualDirName, parentParts); + + // Create index.mdx file first + createCategoryIndexFile(outputDir, relativePath, label, description); + + // Create category file pointing to index.mdx + const docId = relativePath ? `library/${relativePath}/index` : 'library/index'; + + const category = { + label, + position, + collapsible: true, + collapsed: true, // Collapse all categories by default + link: { + type: 'doc', + id: docId, + }, + }; + + // Ensure directory exists + fs.mkdirSync(outputDir, { recursive: true }); + fs.writeFileSync(categoryFile, JSON.stringify(category, null, 2) + '\n'); + + return true; +} + +/** + * Ensure the base library category file exists + * @param {string} libraryDir - Path to library directory + * @returns {boolean} True if created, false if existed + */ +function ensureBaseCategory(libraryDir) { + const categoryFile = path.join(libraryDir, '_category_.json'); + + if (fs.existsSync(categoryFile)) { + return false; + } + + const label = 'Library'; + const description = 'API reference for all Compose modules and facets.'; + + // Create index.mdx for base library category + // Hide from sidebar (sidebar_class_name: "hidden") so it doesn't appear as a page in the sidebar + createIndexFile(libraryDir, '', label, description, generateLabel, generateDescription, false, true); + + const baseCategory = { + label, + position: 4, + collapsible: true, + collapsed: true, // Collapse base Library category by default + link: { + type: 'doc', + id: 'library/index', + }, + }; + + fs.mkdirSync(libraryDir, { recursive: true }); + fs.writeFileSync(categoryFile, JSON.stringify(baseCategory, null, 2) + '\n'); + + return true; +} + +// ============================================================================ +// Path Computation +// ============================================================================ + +/** + * Compute output path for a source file + * Mirrors the src/ structure in website/docs/library/ + * Applies directory name mapping (e.g., libraries -> utils) + * + * @param {string} solFilePath - Path to .sol file (e.g., 'src/access/AccessControl/AccessControlMod.sol') + * @returns {object} Output path information + */ +function computeOutputPath(solFilePath) { + const libraryDir = CONFIG.libraryOutputDir || 'website/docs/library'; + + // Normalize path separators + const normalizedPath = solFilePath.replace(/\\/g, '/'); + + // Remove 'src/' prefix and '.sol' extension + const relativePath = normalizedPath.replace(/^src\//, '').replace(/\.sol$/, ''); + + const parts = relativePath.split('/'); + const fileName = parts.pop(); + + // Map directory names (e.g., libraries -> utils) + const mappedParts = parts.map(part => mapDirectoryName(part)); + + const outputDir = path.join(libraryDir, ...mappedParts); + const outputFile = path.join(outputDir, `${fileName}.mdx`); + + return { + outputDir, + outputFile, + relativePath: mappedParts.join('/'), + fileName, + category: mappedParts[0] || '', + subcategory: mappedParts[1] || '', + fullRelativePath: mappedParts.join('/'), + depth: mappedParts.length, + }; +} + +/** + * Ensure all parent category files exist for a given output path + * Creates _category_.json files for each directory level + * + * @param {string} outputDir - Full output directory path + */ +function ensureCategoryFiles(outputDir) { + const libraryDir = CONFIG.libraryOutputDir || 'website/docs/library'; + + // Get relative path from library base + const relativePath = path.relative(libraryDir, outputDir); + + if (!relativePath || relativePath.startsWith('..')) { + return; // outputDir is not under libraryDir + } + + // Ensure base category exists + ensureBaseCategory(libraryDir); + + // Walk up the directory tree, creating category files + const parts = relativePath.split(path.sep); + let currentPath = libraryDir; + + for (let i = 0; i < parts.length; i++) { + currentPath = path.join(currentPath, parts[i]); + const segment = parts[i]; + // Use the mapped path for the relative path (already mapped in computeOutputPath) + const relPath = parts.slice(0, i + 1).join('/'); + + createCategoryFile(currentPath, segment, relPath, i + 1); + } +} + +// ============================================================================ +// Structure Synchronization +// ============================================================================ + +/** + * Regenerate index.mdx files for all categories + * @param {boolean} overwrite - Whether to overwrite existing files (default: true) + * @returns {object} Summary of regenerated categories + */ +function regenerateAllIndexFiles(overwrite = true) { + const structure = scanSourceStructure(); + const libraryDir = CONFIG.libraryOutputDir || 'website/docs/library'; + + const regenerated = []; + const skipped = []; + + // Regenerate base library index + // Always hide from sidebar (sidebar_class_name: "hidden") + const label = 'Library'; + const description = 'API reference for all Compose modules and facets.'; + if (createCategoryIndexFile(libraryDir, '', label, description, overwrite, true)) { + regenerated.push('library'); + } else { + skipped.push('library'); + } + + // Regenerate index for each category + const sortedPaths = Array.from(structure.entries()).sort((a, b) => + a[0].localeCompare(b[0]) + ); + + for (const [relativePath, info] of sortedPaths) { + const pathParts = relativePath.split('/'); + const mappedPathParts = pathParts.map(part => mapDirectoryName(part)); + const mappedRelativePath = mappedPathParts.join('/'); + const outputDir = path.join(libraryDir, ...mappedPathParts); + + const actualDirName = path.basename(outputDir); + const parentParts = mappedRelativePath.split('/').slice(0, -1); + const label = generateLabel(actualDirName); + const description = generateDescription(actualDirName, parentParts); + + if (createCategoryIndexFile(outputDir, mappedRelativePath, label, description, overwrite)) { + regenerated.push(mappedRelativePath); + } else { + skipped.push(mappedRelativePath); + } + } + + return { + regenerated, + skipped, + total: structure.size + 1, // +1 for base library + }; +} + +/** + * Synchronize docs structure with src structure + * Creates any missing category directories and _category_.json files + * + * @returns {object} Summary of created categories + */ +function syncDocsStructure() { + const structure = scanSourceStructure(); + const libraryDir = CONFIG.libraryOutputDir || 'website/docs/library'; + + const created = []; + const existing = []; + + // Ensure base library directory exists with category + if (ensureBaseCategory(libraryDir)) { + created.push('library'); + } else { + existing.push('library'); + } + + // Create category for each directory in the structure + // Sort by path to ensure parents are created before children + const sortedPaths = Array.from(structure.entries()).sort((a, b) => + a[0].localeCompare(b[0]) + ); + + for (const [relativePath, info] of sortedPaths) { + // Map directory names in the path (e.g., libraries -> utils) + const pathParts = relativePath.split('/'); + const mappedPathParts = pathParts.map(part => mapDirectoryName(part)); + const mappedRelativePath = mappedPathParts.join('/'); + const outputDir = path.join(libraryDir, ...mappedPathParts); + + const wasCreated = createCategoryFile( + outputDir, + info.name, + mappedRelativePath, + info.depth + ); + + if (wasCreated) { + created.push(mappedRelativePath); + } else { + existing.push(mappedRelativePath); + } + } + + return { + created, + existing, + total: structure.size, + structure, + }; +} + +// ============================================================================ +// Exports +// ============================================================================ + +module.exports = { + // Core functions + scanSourceStructure, + syncDocsStructure, + computeOutputPath, + ensureCategoryFiles, + createCategoryIndexFile, + regenerateAllIndexFiles, + + // Utilities + generateLabel, + generateDescription, + getCategoryPosition, + containsSolFiles, + mapDirectoryName, + computeSlug, + + // For extending/customizing + CATEGORY_LABELS, + CATEGORY_DESCRIPTIONS, + CATEGORY_POSITIONS, +}; + diff --git a/.github/scripts/generate-docs-utils/category/index-page-generator.js b/.github/scripts/generate-docs-utils/category/index-page-generator.js new file mode 100644 index 00000000..b999b350 --- /dev/null +++ b/.github/scripts/generate-docs-utils/category/index-page-generator.js @@ -0,0 +1,253 @@ +/** + * Index Page Generator + * + * Generates index.mdx files for category directories with custom DocCard components. + * This module provides utilities for creating styled category index pages. + */ + +const fs = require('fs'); +const path = require('path'); +const CONFIG = require('../config'); + +// ============================================================================ +// Category Items Discovery +// ============================================================================ + +/** + * Get all items (documents and subcategories) in a directory + * @param {string} outputDir - Directory to scan + * @param {string} relativePath - Relative path from library dir + * @param {Function} generateLabel - Function to generate labels from names + * @param {Function} generateDescription - Function to generate descriptions + * @returns {Array} Array of items with type, name, label, href, description + */ +function getCategoryItems(outputDir, relativePath, generateLabel, generateDescription) { + const items = []; + + if (!fs.existsSync(outputDir)) { + return items; + } + + const entries = fs.readdirSync(outputDir, { withFileTypes: true }); + + for (const entry of entries) { + // Skip hidden files, category files, and index files + if (entry.name.startsWith('.') || + entry.name === '_category_.json' || + entry.name === 'index.mdx') { + continue; + } + + if (entry.isFile() && entry.name.endsWith('.mdx')) { + // It's a document + const docName = entry.name.replace('.mdx', ''); + const docPath = path.join(outputDir, entry.name); + + // Try to read frontmatter for title and description + let title = generateLabel(docName); + let description = ''; + + try { + const content = fs.readFileSync(docPath, 'utf8'); + const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); + if (frontmatterMatch) { + const frontmatter = frontmatterMatch[1]; + const titleMatch = frontmatter.match(/^title:\s*["']?(.*?)["']?$/m); + const descMatch = frontmatter.match(/^description:\s*["']?(.*?)["']?$/m); + if (titleMatch) title = titleMatch[1].trim(); + if (descMatch) description = descMatch[1].trim(); + } + } catch (error) { + // If reading fails, use defaults + } + + const docRelativePath = relativePath ? `${relativePath}/${docName}` : docName; + items.push({ + type: 'doc', + name: docName, + label: title, + description: description, + href: `/docs/library/${docRelativePath}`, + }); + } else if (entry.isDirectory()) { + // It's a subcategory + const subcategoryName = entry.name; + const subcategoryLabel = generateLabel(subcategoryName); + const subcategoryRelativePath = relativePath ? `${relativePath}/${subcategoryName}` : subcategoryName; + const subcategoryDescription = generateDescription(subcategoryName, relativePath.split('/')); + + items.push({ + type: 'category', + name: subcategoryName, + label: subcategoryLabel, + description: subcategoryDescription, + href: `/docs/library/${subcategoryRelativePath}`, + }); + } + } + + // Sort items + // + // Default: categories first, then docs, both alphabetically. + // Special case: Diamond Core (`library/diamond`) to match sidebar order: + // Module, Inspect Facet, Upgrade Facet, Upgrade Module, Examples. + if (relativePath === 'diamond') { + const preferredOrder = [ + 'DiamondMod', + 'DiamondInspectFacet', + 'DiamondUpgradeFacet', + 'DiamondUpgradeMod', + 'example', + ]; + + const getIndex = (item) => { + const idx = preferredOrder.indexOf(item.name); + return idx === -1 ? Number.MAX_SAFE_INTEGER : idx; + }; + + items.sort((a, b) => { + const aIdx = getIndex(a); + const bIdx = getIndex(b); + if (aIdx !== bIdx) { + return aIdx - bIdx; + } + // Fallback deterministic ordering + return a.label.localeCompare(b.label); + }); + } else { + items.sort((a, b) => { + if (a.type !== b.type) { + return a.type === 'category' ? -1 : 1; + } + return a.label.localeCompare(b.label); + }); + } + + return items; +} + +// ============================================================================ +// MDX Content Generation +// ============================================================================ + +/** + * Generate MDX content for a category index page + * @param {string} label - Category label + * @param {string} description - Category description + * @param {Array} items - Array of items to display + * @returns {string} Generated MDX content + */ +function generateIndexMdxContent(label, description, items, hideFromSidebar = false) { + // Escape quotes in label and description for frontmatter + const escapedLabel = label.replace(/"/g, '\\"'); + const escapedDescription = description.replace(/"/g, '\\"'); + + // Add sidebar_class_name: "hidden" to hide from sidebar if requested + const sidebarClass = hideFromSidebar ? '\nsidebar_class_name: "hidden"' : ''; + + let mdxContent = `--- +title: "${escapedLabel}" +description: "${escapedDescription}"${sidebarClass} +--- + +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'; + + + ${escapedDescription} + + +`; + + if (items.length > 0) { + mdxContent += `\n`; + + for (const item of items) { + // Icon mapping: + // - Categories (higher-level groupings): package + // - Facets (contract names ending with "Facet"): showcase-facet + // - Modules (contract names ending with "Mod"): box-detailed + // - Everything else: package + let iconName = 'package'; + if (item.type === 'category') { + iconName = 'package'; + } else if (item.name.endsWith('Facet')) { + iconName = 'showcase-facet'; + } else if (item.name.endsWith('Mod')) { + iconName = 'box-detailed'; + } + const itemDescription = item.description ? `"${item.description.replace(/"/g, '\\"')}"` : '""'; + + mdxContent += ` } + size="medium" + />\n`; + } + + mdxContent += `\n`; + } else { + mdxContent += `_No items in this category yet._\n`; + } + + return mdxContent; +} + +// ============================================================================ +// Index File Creation +// ============================================================================ + +/** + * Generate index.mdx file for a category + * @param {string} outputDir - Directory to create index file in + * @param {string} relativePath - Relative path from library dir + * @param {string} label - Category label + * @param {string} description - Category description + * @param {Function} generateLabel - Function to generate labels from names + * @param {Function} generateDescription - Function to generate descriptions + * @param {boolean} overwrite - Whether to overwrite existing files (default: false) + * @returns {boolean} True if file was created/updated, false if skipped + */ +function createCategoryIndexFile( + outputDir, + relativePath, + label, + description, + generateLabel, + generateDescription, + overwrite = false, + hideFromSidebar = false +) { + const indexFile = path.join(outputDir, 'index.mdx'); + + // Don't overwrite existing index files unless explicitly requested (allows manual customization) + if (!overwrite && fs.existsSync(indexFile)) { + return false; + } + + // Get items in this category + const items = getCategoryItems(outputDir, relativePath, generateLabel, generateDescription); + + // Generate MDX content + const mdxContent = generateIndexMdxContent(label, description, items, hideFromSidebar); + + // Ensure directory exists + fs.mkdirSync(outputDir, { recursive: true }); + fs.writeFileSync(indexFile, mdxContent); + + return true; +} + +// ============================================================================ +// Exports +// ============================================================================ + +module.exports = { + getCategoryItems, + generateIndexMdxContent, + createCategoryIndexFile, +}; + diff --git a/.github/scripts/generate-docs-utils/config.js b/.github/scripts/generate-docs-utils/config.js new file mode 100644 index 00000000..de1c60d7 --- /dev/null +++ b/.github/scripts/generate-docs-utils/config.js @@ -0,0 +1,168 @@ +/** + * Configuration for documentation generation + * + * Centralized configuration for paths, settings, and defaults. + * Modify this file to change documentation output paths or behavior. + */ + +module.exports = { + // ============================================================================ + // Input Paths + // ============================================================================ + + /** Directory containing forge doc output */ + forgeDocsDir: 'docs/src/src', + + /** Source code directory to mirror */ + srcDir: 'src', + + // ============================================================================ + // Output Paths + // ============================================================================ + + /** + * Base output directory for contract documentation + * Structure mirrors src/ automatically + */ + contractsOutputDir: 'website/docs/contracts', + + // ============================================================================ + // Sidebar Positions + // ============================================================================ + + /** Default sidebar position for contracts without explicit mapping */ + defaultSidebarPosition: 50, + + /** + * Contract-specific sidebar positions + * Maps contract name to position number (lower = higher in sidebar) + * + * Convention: + * - Facets come before their corresponding modules + * - Core/base contracts come before extensions + * - Burn facets come after main facets + */ + contractPositions: { + // Diamond core – order: DiamondMod, DiamondInspectFacet, DiamondUpgradeFacet, DiamondUpgradeMod, then Examples + DiamondMod: 1, + DiamondInspectFacet: 2, + DiamondUpgradeFacet: 3, + DiamondUpgradeMod: 4, + DiamondCutMod: 3, + DiamondCutFacet: 1, + DiamondLoupeFacet: 4, + + // Access - Owner pattern + OwnerMod: 2, + OwnerFacet: 1, + + // Access - Two-step owner + OwnerTwoStepsMod: 2, + OwnerTwoStepsFacet: 1, + + // Access - AccessControl pattern + AccessControlMod: 2, + AccessControlFacet: 1, + + // Access - AccessControlPausable + AccessControlPausableMod: 2, + AccessControlPausableFacet: 1, + + // Access - AccessControlTemporal + AccessControlTemporalMod: 2, + AccessControlTemporalFacet: 1, + + // ERC-20 base + ERC20Mod: 2, + ERC20Facet: 1, + ERC20BurnFacet: 3, + + // ERC-20 Bridgeable + ERC20BridgeableMod: 2, + ERC20BridgeableFacet: 1, + + // ERC-20 Permit + ERC20PermitMod: 2, + ERC20PermitFacet: 1, + + // ERC-721 base + ERC721Mod: 2, + ERC721Facet: 1, + ERC721BurnFacet: 3, + + // ERC-721 Enumerable + ERC721EnumerableMod: 2, + ERC721EnumerableFacet: 1, + ERC721EnumerableBurnFacet: 3, + + // ERC-1155 + ERC1155Mod: 2, + ERC1155Facet: 1, + + // ERC-6909 + ERC6909Mod: 2, + ERC6909Facet: 1, + + // Royalty + RoyaltyMod: 2, + RoyaltyFacet: 1, + + // Libraries + NonReentrancyMod: 1, + ERC165Mod: 2, + ERC165Facet: 1, + }, + + /** + * Diamond docs: sidebar labels for the sidebar nav (e.g. "Module", "Inspect Facet"). + * File names stay as contract names (e.g. DiamondMod.mdx, DiamondInspectFacet.mdx). + */ + diamondSidebarLabels: { + DiamondMod: 'Module', + DiamondInspectFacet: 'Inspect Facet', + DiamondUpgradeFacet: 'Upgrade Facet', + DiamondUpgradeMod: 'Upgrade Module', + }, + + // ============================================================================ + // Repository Configuration + // ============================================================================ + + /** Main repository URL - always use this for source links */ + mainRepoUrl: 'https://github.com/Perfect-Abstractions/Compose', + + /** + * Normalize gitSource URL to always point to the main repository's main branch + * Replaces any fork or incorrect repository URLs with the main repo URL + * Converts blob URLs to tree URLs pointing to main branch + * @param {string} gitSource - Original gitSource URL from forge doc + * @returns {string} Normalized gitSource URL + */ + normalizeGitSource(gitSource) { + if (!gitSource) return gitSource; + + // Pattern: https://github.com/USER/Compose/blob/COMMIT/src/path/to/file.sol + // Convert to: https://github.com/Perfect-Abstractions/Compose/tree/main/src/path/to/file.sol + const githubUrlPattern = /https:\/\/github\.com\/[^\/]+\/Compose\/(?:blob|tree)\/[^\/]+\/(.+)/; + const match = gitSource.match(githubUrlPattern); + + if (match) { + // Extract the path after the repo name (should start with src/) + const pathPart = match[1]; + // Ensure it starts with src/ (remove any leading src/ if duplicated) + const normalizedPath = pathPart.startsWith('src/') ? pathPart : `src/${pathPart}`; + return `${this.mainRepoUrl}/tree/main/${normalizedPath}`; + } + + // If it doesn't match the pattern, try to construct from the main repo + // Extract just the file path if it's a relative path or partial URL + if (gitSource.includes('/src/')) { + const srcIndex = gitSource.indexOf('/src/'); + const pathAfterSrc = gitSource.substring(srcIndex + 1); + return `${this.mainRepoUrl}/tree/main/${pathAfterSrc}`; + } + + // If it doesn't match any pattern, return as-is (might be a different format) + return gitSource; + }, +}; diff --git a/.github/scripts/generate-docs-utils/core/contract-processor.js b/.github/scripts/generate-docs-utils/core/contract-processor.js new file mode 100644 index 00000000..e9e10bcf --- /dev/null +++ b/.github/scripts/generate-docs-utils/core/contract-processor.js @@ -0,0 +1,102 @@ +/** + * Contract Processing Pipeline + * + * Shared processing logic for both regular and aggregated contract files. + * Handles the complete pipeline from parsed data to written MDX file. + */ + +const fs = require('fs'); +const { extractStorageInfo } = require('../parsing/storage-extractor'); +const { getOutputPath } = require('../utils/path-computer'); +const { getSidebarPosition } = require('../utils/sidebar-position-calculator'); +const { registerContract, getContractRegistry } = require('./contract-registry'); +const { generateFacetDoc, generateModuleDoc } = require('../templates/templates'); +const { enhanceWithAI, shouldSkipEnhancement } = require('../ai/ai-enhancement'); +const { addFallbackContent } = require('../ai/fallback-content-provider'); +const { applyDescriptionFallback } = require('./description-manager'); +const { writeFileSafe } = require('../../workflow-utils'); + +/** + * Process contract data through the complete pipeline + * @param {object} data - Parsed documentation data + * @param {string} solFilePath - Path to source Solidity file + * @param {'module' | 'facet'} contractType - Type of contract + * @param {object} tracker - Tracker object for recording results (temporary, will be replaced with SummaryTracker) + * @returns {Promise<{success: boolean, error?: string}>} Processing result + */ +async function processContractData(data, solFilePath, contractType, tracker) { + // 1. Extract storage info for modules + if (contractType === 'module') { + data.storageInfo = extractStorageInfo(data); + } + + // 2. Apply description fallback + data = applyDescriptionFallback(data, contractType, solFilePath); + + // 3. Compute output path (mirrors src/ structure) + const pathInfo = getOutputPath(solFilePath, contractType); + + // 4. Get registry for relationship detection + const registry = getContractRegistry(); + + // 5. Get smart sidebar position (uses registry if available) + data.position = getSidebarPosition(data.title, contractType, pathInfo.category, registry); + + // 6. Set contract type for registry (before registering) + data.contractType = contractType; + + // 7. Register contract in registry (before AI enhancement so it's available for relationship detection) + registerContract(data, pathInfo); + + // 8. Enhance with AI if not skipped + const skipAIEnhancement = shouldSkipEnhancement(data) || process.env.SKIP_ENHANCEMENT === 'true'; + let enhancedData = data; + let usedFallback = false; + let enhancementError = null; + + if (!skipAIEnhancement) { + const token = process.env.GITHUB_TOKEN; + const result = await enhanceWithAI(data, contractType, token); + enhancedData = result.data; + usedFallback = result.usedFallback; + enhancementError = result.error; + + // Track fallback usage + if (usedFallback) { + tracker.recordFallback(data.title, pathInfo.outputFile, enhancementError || 'Unknown error'); + } + } else { + enhancedData = addFallbackContent(data, contractType); + } + + // Ensure contractType is preserved after AI enhancement + enhancedData.contractType = contractType; + + // 9. Generate MDX content with registry for relationship detection + const mdxContent = contractType === 'module' + ? generateModuleDoc(enhancedData, enhancedData.position, pathInfo, registry) + : generateFacetDoc(enhancedData, enhancedData.position, pathInfo, registry); + + // 10. Ensure output directory exists + fs.mkdirSync(pathInfo.outputDir, { recursive: true }); + + // 11. Write the file + if (writeFileSafe(pathInfo.outputFile, mdxContent)) { + // Track success + if (contractType === 'module') { + tracker.recordModule(data.title, pathInfo.outputFile); + } else { + tracker.recordFacet(data.title, pathInfo.outputFile); + } + return { success: true }; + } + + // Track write error + tracker.recordError(pathInfo.outputFile, 'Could not write file'); + return { success: false, error: 'Could not write file' }; +} + +module.exports = { + processContractData, +}; + diff --git a/.github/scripts/generate-docs-utils/core/contract-registry.js b/.github/scripts/generate-docs-utils/core/contract-registry.js new file mode 100644 index 00000000..a8981752 --- /dev/null +++ b/.github/scripts/generate-docs-utils/core/contract-registry.js @@ -0,0 +1,97 @@ +/** + * Contract Registry System + * + * Tracks all contracts (modules and facets) for relationship detection + * and cross-reference generation in documentation. + * + * Features: + * - Register contracts with metadata (name, type, category, path) + * - Provide registry access for relationship detection and other operations + */ + +// ============================================================================ +// Registry State +// ============================================================================ + +/** + * Global registry to track all contracts for relationship detection + * This allows us to find related contracts and generate cross-references + */ +const contractRegistry = { + byName: new Map(), + byCategory: new Map(), + byType: { modules: [], facets: [] } +}; + +// ============================================================================ +// Registry Management +// ============================================================================ + +/** + * Register a contract in the global registry + * @param {object} contractData - Contract documentation data + * @param {object} outputPath - Output path information from getOutputPath + * @returns {object} Registered contract entry + */ +function registerContract(contractData, outputPath) { + // Construct full path including filename (without .mdx extension) + // This ensures RelatedDocs links point to the actual page, not the category index + const fullPath = outputPath.relativePath + ? `${outputPath.relativePath}/${outputPath.fileName}` + : outputPath.fileName; + + const entry = { + name: contractData.title, + type: contractData.contractType, // 'module' or 'facet' + category: outputPath.category, + path: fullPath, + sourcePath: contractData.sourceFilePath, + functions: contractData.functions || [], + storagePosition: contractData.storageInfo?.storagePosition + }; + + contractRegistry.byName.set(contractData.title, entry); + + if (!contractRegistry.byCategory.has(outputPath.category)) { + contractRegistry.byCategory.set(outputPath.category, []); + } + contractRegistry.byCategory.get(outputPath.category).push(entry); + + if (contractData.contractType === 'module') { + contractRegistry.byType.modules.push(entry); + } else { + contractRegistry.byType.facets.push(entry); + } + + return entry; +} + +/** + * Get the contract registry + * @returns {object} The contract registry + */ +function getContractRegistry() { + return contractRegistry; +} + +/** + * Clear the contract registry (useful for testing or reset) + */ +function clearContractRegistry() { + contractRegistry.byName.clear(); + contractRegistry.byCategory.clear(); + contractRegistry.byType.modules = []; + contractRegistry.byType.facets = []; +} + +// ============================================================================ +// Exports +// ============================================================================ + +module.exports = { + // Registry management + registerContract, + getContractRegistry, + clearContractRegistry, +}; + diff --git a/.github/scripts/generate-docs-utils/core/description-generator.js b/.github/scripts/generate-docs-utils/core/description-generator.js new file mode 100644 index 00000000..34cd18b1 --- /dev/null +++ b/.github/scripts/generate-docs-utils/core/description-generator.js @@ -0,0 +1,109 @@ +/** + * Description Generator + * + * Generates fallback descriptions from contract names. + */ + +const CONFIG = require('../config'); + +/** + * Generate a fallback description from contract name + * + * This is a minimal, generic fallback used only when: + * 1. No NatSpec @title/@notice exists in source + * 2. AI enhancement will improve it later + * + * The AI enhancement step receives this as input and generates + * a richer, context-aware description from the actual code. + * + * @param {string} contractName - Name of the contract + * @returns {string} Generic description (will be enhanced by AI) + */ +function generateDescriptionFromName(contractName) { + if (!contractName) return ''; + + // Detect library type from naming convention + const isModule = contractName.endsWith('Mod') || contractName.endsWith('Module'); + const isFacet = contractName.endsWith('Facet'); + const typeLabel = isModule ? 'module' : isFacet ? 'facet' : 'library'; + + // Remove suffix and convert CamelCase to readable text + const baseName = contractName + .replace(/Mod$/, '') + .replace(/Module$/, '') + .replace(/Facet$/, ''); + + // Convert CamelCase to readable format + // Handles: ERC20 -> ERC-20, AccessControl -> Access Control + const readable = baseName + .replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase splits + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') // acronym handling + .replace(/^ERC(\d+)/, 'ERC-$1') // ERC20 -> ERC-20 + .trim(); + + return `${readable} ${typeLabel} for Compose diamonds`; +} + +/** + * Compute an optional sidebar label for a contract. + * + * For diamond category, uses config diamondSidebarLabels (e.g. "Module", "Inspect Facet"). + * For other library categories, returns minimal "Facet" or "Module". Utilities keep null. + * + * @param {'facet' | 'module' | string} contractType + * @param {string} category + * @param {string} [contractName] - Contract name (e.g. DiamondMod), used for diamond-specific labels + * @returns {string|null} Sidebar label or null when no override should be used + */ +function getSidebarLabel(contractType, category, contractName) { + if (!contractType) return null; + + const normalizedCategory = (category || '').toLowerCase(); + + // Diamond: use short labels from config (e.g. "Module", "Inspect Facet", "Upgrade Module") + if (normalizedCategory === 'diamond' && contractName && CONFIG.diamondSidebarLabels && CONFIG.diamondSidebarLabels[contractName]) { + return CONFIG.diamondSidebarLabels[contractName]; + } + + if (normalizedCategory === 'utils') { + return null; + } + + if (contractType === 'facet') { + return 'Facet'; + } + + if (contractType === 'module') { + return 'Module'; + } + + return null; +} + +/** + * Format contract name as display title for page headers and frontmatter. + * Adds spacing (splits camelCase) and expands "Mod" to "Module". + * + * @param {string} contractName - Raw contract name (e.g. OwnerDataMod, ERC20TransferFacet) + * @returns {string} Display title (e.g. "Owner Data Module", "ERC-20 Transfer Facet") + */ +function formatDisplayTitle(contractName) { + if (!contractName || typeof contractName !== 'string') return ''; + + const withSpaces = contractName + .replace(/^ERC(\d+)/, 'ERC-$1') + .replace(/([a-z])([A-Z])/g, '$1 $2') + .replace(/(\d)([A-Z])/g, '$1 $2') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') + .trim(); + + const withModule = withSpaces.replace(/\s+Mod$/, ' Module'); + return withModule; +} + +module.exports = { + generateDescriptionFromName, + getSidebarLabel, + formatDisplayTitle, +}; + diff --git a/.github/scripts/generate-docs-utils/core/description-manager.js b/.github/scripts/generate-docs-utils/core/description-manager.js new file mode 100644 index 00000000..0b2c14ab --- /dev/null +++ b/.github/scripts/generate-docs-utils/core/description-manager.js @@ -0,0 +1,84 @@ +/** + * Description Manager + * + * Handles description generation and fallback logic for contracts. + * Consolidates description generation from multiple sources. + */ + +const { extractModuleDescriptionFromSource } = require('../utils/source-parser'); +const { generateDescriptionFromName } = require('./description-generator'); + +/** + * Apply description fallback logic to contract data + * @param {object} data - Contract documentation data + * @param {'module' | 'facet'} contractType - Type of contract + * @param {string} solFilePath - Path to source Solidity file + * @returns {object} Data with description applied + */ +function applyDescriptionFallback(data, contractType, solFilePath) { + // For modules, try to get description from source file first + if (contractType === 'module' && solFilePath) { + const sourceDescription = extractModuleDescriptionFromSource(solFilePath); + if (sourceDescription) { + data.description = sourceDescription; + data.subtitle = sourceDescription; + data.overview = sourceDescription; + return data; + } + } + + // For facets, check if description is generic and needs replacement + if (contractType === 'facet') { + const looksLikeEnum = + data.description && + /\w+\s*=\s*\d+/.test(data.description) && + (data.description.match(/\w+\s*=\s*\d+/g) || []).length >= 2; + + const isGenericDescription = + !data.description || + data.description.startsWith('Contract documentation for') || + looksLikeEnum || + data.description.length < 20; + + if (isGenericDescription) { + const generatedDescription = generateDescriptionFromName(data.title); + if (generatedDescription) { + data.description = generatedDescription; + data.subtitle = generatedDescription; + data.overview = generatedDescription; + return data; + } + } + } + + // For modules, try generating from name + if (contractType === 'module') { + const generatedDescription = generateDescriptionFromName(data.title); + if (generatedDescription) { + data.description = generatedDescription; + data.subtitle = generatedDescription; + data.overview = generatedDescription; + return data; + } + + // Last resort fallback for modules + const genericDescription = `Module providing internal functions for ${data.title}`; + if ( + !data.description || + data.description.includes('Event emitted') || + data.description.includes('Thrown when') || + data.description.includes('function to') + ) { + data.description = genericDescription; + data.subtitle = genericDescription; + data.overview = genericDescription; + } + } + + return data; +} + +module.exports = { + applyDescriptionFallback, +}; + diff --git a/.github/scripts/generate-docs-utils/core/file-processor.js b/.github/scripts/generate-docs-utils/core/file-processor.js new file mode 100644 index 00000000..4825ac19 --- /dev/null +++ b/.github/scripts/generate-docs-utils/core/file-processor.js @@ -0,0 +1,151 @@ +/** + * File Processor + * + * Handles processing of Solidity source files and their forge doc outputs. + */ + +const { findForgeDocFiles } = require('../utils/file-finder'); +const { isInterface, getContractType } = require('../utils/contract-classifier'); +const { extractModuleNameFromPath } = require('../utils/source-parser'); +const { readFileSafe } = require('../../workflow-utils'); +const { parseForgeDocMarkdown } = require('../parsing/markdown-parser'); +const { + parseIndividualItemFile, + aggregateParsedItems, + detectItemTypeFromFilename, +} = require('../parsing/item-parser'); +const { processContractData } = require('./contract-processor'); + +/** + * Process a single forge doc markdown file + * @param {string} forgeDocFile - Path to forge doc markdown file + * @param {string} solFilePath - Original .sol file path + * @param {object} tracker - Tracker instance + * @returns {Promise} True if processed successfully + */ +async function processForgeDocFile(forgeDocFile, solFilePath, tracker) { + const content = readFileSafe(forgeDocFile); + if (!content) { + tracker.recordError(forgeDocFile, 'Could not read file'); + return false; + } + + // Parse the forge doc markdown + const data = parseForgeDocMarkdown(content, forgeDocFile); + + // Add source file path for parameter extraction + if (solFilePath) { + data.sourceFilePath = solFilePath; + } + + if (!data.title) { + tracker.recordSkipped(forgeDocFile, 'No title found'); + return false; + } + + // Skip interfaces + if (isInterface(data.title, content)) { + tracker.recordSkipped(forgeDocFile, 'Interface (filtered)'); + return false; + } + + // Determine contract type + const contractType = getContractType(forgeDocFile, content); + + // Process through shared pipeline (includes description fallback) + const result = await processContractData(data, solFilePath, contractType, tracker); + return result.success; +} + +/** + * Check if files need aggregation (individual item files vs contract-level files) + * @param {string[]} forgeDocFiles - Array of forge doc file paths + * @returns {boolean} True if files are individual items that need aggregation + */ +function needsAggregation(forgeDocFiles) { + for (const file of forgeDocFiles) { + const itemType = detectItemTypeFromFilename(file); + if (itemType) { + return true; + } + } + return false; +} + +/** + * Process aggregated files (for free function modules) + * @param {string[]} forgeDocFiles - Array of forge doc file paths + * @param {string} solFilePath - Original .sol file path + * @param {object} tracker - Tracker instance + * @returns {Promise} True if processed successfully + */ +async function processAggregatedFiles(forgeDocFiles, solFilePath, tracker) { + const parsedItems = []; + let gitSource = ''; + + for (const forgeDocFile of forgeDocFiles) { + const content = readFileSafe(forgeDocFile); + if (!content) { + continue; + } + + const parsed = parseIndividualItemFile(content, forgeDocFile); + if (parsed) { + parsedItems.push(parsed); + if (parsed.gitSource && !gitSource) { + gitSource = parsed.gitSource; + } + } + } + + if (parsedItems.length === 0) { + tracker.recordError(solFilePath, 'No valid items parsed'); + return false; + } + + const data = aggregateParsedItems(parsedItems, solFilePath); + + data.sourceFilePath = solFilePath; + + if (!data.title) { + data.title = extractModuleNameFromPath(solFilePath); + } + + if (gitSource) { + data.gitSource = gitSource; + } + + const contractType = getContractType(solFilePath, ''); + + // Process through shared pipeline (includes description fallback) + const result = await processContractData(data, solFilePath, contractType, tracker); + return result.success; +} + +/** + * Process a Solidity source file + * @param {string} solFilePath - Path to .sol file + * @param {object} tracker - Tracker instance + * @returns {Promise} + */ +async function processSolFile(solFilePath, tracker) { + const forgeDocFiles = findForgeDocFiles(solFilePath); + + if (forgeDocFiles.length === 0) { + tracker.recordSkipped(solFilePath, 'No forge doc output'); + return; + } + + if (needsAggregation(forgeDocFiles)) { + await processAggregatedFiles(forgeDocFiles, solFilePath, tracker); + } else { + for (const forgeDocFile of forgeDocFiles) { + await processForgeDocFile(forgeDocFile, solFilePath, tracker); + } + } +} + +module.exports = { + processSolFile, +}; + diff --git a/.github/scripts/generate-docs-utils/core/file-selector.js b/.github/scripts/generate-docs-utils/core/file-selector.js new file mode 100644 index 00000000..0b9bf34a --- /dev/null +++ b/.github/scripts/generate-docs-utils/core/file-selector.js @@ -0,0 +1,40 @@ +/** + * File Selector + * + * Determines which Solidity files to process based on command line arguments. + */ + +const { getAllSolFiles, readChangedFilesFromFile, getChangedSolFiles } = require('../utils/git-utils'); + +/** + * Get files to process based on command line arguments + * @param {string[]} args - Command line arguments + * @returns {string[]} Array of Solidity file paths to process + */ +function getFilesToProcess(args) { + if (args.includes('--all')) { + console.log('Processing all Solidity files...'); + return getAllSolFiles(); + } + + if (args.length > 0 && !args[0].startsWith('--')) { + const changedFilesPath = args[0]; + console.log(`Reading changed files from: ${changedFilesPath}`); + const solFiles = readChangedFilesFromFile(changedFilesPath); + + if (solFiles.length === 0) { + console.log('No files in list, checking git diff...'); + return getChangedSolFiles(); + } + + return solFiles; + } + + console.log('Getting changed Solidity files from git...'); + return getChangedSolFiles(); +} + +module.exports = { + getFilesToProcess, +}; + diff --git a/.github/scripts/generate-docs-utils/core/relationship-detector.js b/.github/scripts/generate-docs-utils/core/relationship-detector.js new file mode 100644 index 00000000..2e926353 --- /dev/null +++ b/.github/scripts/generate-docs-utils/core/relationship-detector.js @@ -0,0 +1,123 @@ +/** + * Relationship Detector + * + * Detects relationships between contracts (modules and facets) for + * cross-reference generation in documentation. + * + * Features: + * - Find related contracts (module/facet pairs, same category, extensions) + * - Enrich documentation data with relationship information + */ + +const { getContractRegistry } = require('./contract-registry'); + +/** + * Find related contracts for a given contract + * @param {string} contractName - Name of the contract + * @param {string} contractType - Type of contract ('module' or 'facet') + * @param {string} category - Category of the contract + * @param {object} registry - Contract registry (optional, uses global if not provided) + * @returns {Array} Array of related contract objects with title, href, description, icon + */ +function findRelatedContracts(contractName, contractType, category, registry = null) { + const reg = registry || getContractRegistry(); + const related = []; + const contract = reg.byName.get(contractName); + if (!contract) return related; + + // 1. Find corresponding module/facet pair + if (contractType === 'facet') { + const moduleName = contractName.replace('Facet', 'Mod'); + const module = reg.byName.get(moduleName); + if (module) { + related.push({ + title: moduleName, + href: `/docs/library/${module.path}`, + description: `Module used by ${contractName}`, + icon: '📦' + }); + } + } else if (contractType === 'module') { + const facetName = contractName.replace('Mod', 'Facet'); + const facet = reg.byName.get(facetName); + if (facet) { + related.push({ + title: facetName, + href: `/docs/library/${facet.path}`, + description: `Facet using ${contractName}`, + icon: '💎' + }); + } + } + + // 2. Find related contracts in same category (excluding self) + const sameCategory = reg.byCategory.get(category) || []; + sameCategory.forEach(c => { + if (c.name !== contractName && c.type === contractType) { + related.push({ + title: c.name, + href: `/docs/library/${c.path}`, + description: `Related ${contractType} in ${category}`, + icon: contractType === 'module' ? '📦' : '💎' + }); + } + }); + + // 3. Find extension contracts (e.g., ERC20Facet → ERC20BurnFacet) + if (contractType === 'facet') { + const baseName = contractName.replace(/BurnFacet$|PermitFacet$|BridgeableFacet$|EnumerableFacet$/, 'Facet'); + if (baseName !== contractName) { + const base = reg.byName.get(baseName); + if (base) { + related.push({ + title: baseName, + href: `/docs/library/${base.path}`, + description: `Base facet for ${contractName}`, + icon: '💎' + }); + } + } + } + + // 4. Find core dependencies (e.g., all facets depend on DiamondCutFacet) + if (contractType === 'facet' && contractName !== 'DiamondCutFacet') { + const diamondCut = reg.byName.get('DiamondCutFacet'); + if (diamondCut) { + related.push({ + title: 'DiamondCutFacet', + href: `/docs/library/${diamondCut.path}`, + description: 'Required for adding facets to diamonds', + icon: '🔧' + }); + } + } + + return related.slice(0, 4); // Limit to 4 related items +} + +/** + * Enrich contract data with relationship information + * @param {object} data - Contract documentation data + * @param {object} pathInfo - Output path information + * @param {object} registry - Contract registry (optional, uses global if not provided) + * @returns {object} Enriched data with relatedDocs property + */ +function enrichWithRelationships(data, pathInfo, registry = null) { + const relatedDocs = findRelatedContracts( + data.title, + data.contractType, + pathInfo.category, + registry + ); + + return { + ...data, + relatedDocs: relatedDocs.length > 0 ? relatedDocs : null + }; +} + +module.exports = { + findRelatedContracts, + enrichWithRelationships, +}; + diff --git a/.github/scripts/generate-docs-utils/parsing/item-builder.js b/.github/scripts/generate-docs-utils/parsing/item-builder.js new file mode 100644 index 00000000..8956b1c7 --- /dev/null +++ b/.github/scripts/generate-docs-utils/parsing/item-builder.js @@ -0,0 +1,90 @@ +/** + * Item Builder + * + * Functions for creating and saving parsed items. + */ + +/** + * Create a new item object based on section type + * @param {string} name - Item name + * @param {string} section - Section type + * @returns {object} New item object + */ +function createNewItem(name, section) { + const base = { + name, + description: '', + notice: '', + }; + + switch (section) { + case 'functions': + return { + ...base, + signature: '', + params: [], + returns: [], + mutability: 'nonpayable', + }; + case 'events': + return { + ...base, + signature: '', + params: [], + }; + case 'errors': + return { + ...base, + signature: '', + params: [], + }; + case 'structs': + return { + ...base, + definition: '', + fields: [], + }; + case 'stateVariables': + return { + ...base, + type: '', + value: '', + }; + default: + return base; + } +} + +/** + * Save current item to data object + * @param {object} data - Data object to save to + * @param {object} item - Item to save + * @param {string} type - Item type + */ +function saveCurrentItem(data, item, type) { + if (!type || !item) return; + + switch (type) { + case 'functions': + data.functions.push(item); + break; + case 'events': + data.events.push(item); + break; + case 'errors': + data.errors.push(item); + break; + case 'structs': + data.structs.push(item); + break; + case 'stateVariables': + data.stateVariables.push(item); + break; + } +} + +module.exports = { + createNewItem, + saveCurrentItem, +}; + diff --git a/.github/scripts/generate-docs-utils/parsing/item-parser.js b/.github/scripts/generate-docs-utils/parsing/item-parser.js new file mode 100644 index 00000000..3da73237 --- /dev/null +++ b/.github/scripts/generate-docs-utils/parsing/item-parser.js @@ -0,0 +1,366 @@ +/** + * Item Parser + * + * Functions for parsing individual item files and aggregating them. + */ + +const path = require('path'); +const config = require('../config'); +const { sanitizeBrokenLinks, cleanDescription } = require('./text-sanitizer'); + +/** + * Detect item type from filename + * @param {string} filePath - Path to the markdown file + * @returns {string | null} Item type ('function', 'error', 'struct', 'event', 'enum', 'constants', or null) + */ +function detectItemTypeFromFilename(filePath) { + const basename = path.basename(filePath); + + if (basename.startsWith('function.')) return 'function'; + if (basename.startsWith('error.')) return 'error'; + if (basename.startsWith('struct.')) return 'struct'; + if (basename.startsWith('event.')) return 'event'; + if (basename.startsWith('enum.')) return 'enum'; + if (basename.startsWith('constants.')) return 'constants'; + + return null; +} + +/** + * Parse an individual item file (function, error, constant, etc.) + * @param {string} content - Markdown content from forge doc + * @param {string} filePath - Path to the markdown file + * @returns {object | null} Parsed item object or null if parsing fails + */ +function parseIndividualItemFile(content, filePath) { + const itemType = detectItemTypeFromFilename(filePath); + if (!itemType) { + return null; + } + + const lines = content.split('\n'); + let itemName = ''; + let gitSource = ''; + let description = ''; + let signature = ''; + let definition = ''; + let descriptionBuffer = []; + let inCodeBlock = false; + let codeBlockLines = []; + let params = []; + let returns = []; + let constants = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmedLine = line.trim(); + + // Parse title (# heading) + if (line.startsWith('# ') && !itemName) { + itemName = line.replace('# ', '').trim(); + continue; + } + + // Parse git source link + if (trimmedLine.startsWith('[Git Source]')) { + const match = trimmedLine.match(/\[Git Source\]\((.*?)\)/); + if (match) { + gitSource = config.normalizeGitSource(match[1]); + } + continue; + } + + // Parse code block + if (line.startsWith('```solidity')) { + inCodeBlock = true; + codeBlockLines = []; + i++; + while (i < lines.length && !lines[i].startsWith('```')) { + codeBlockLines.push(lines[i]); + i++; + } + const codeContent = codeBlockLines.join('\n').trim(); + + if (itemType === 'constants') { + // For constants, parse multiple constant definitions + // Format: "bytes32 constant NON_REENTRANT_SLOT = keccak256(...)" + // Handle both single and multiple constants in one code block + const constantMatches = codeContent.match(/(\w+(?:\s*\d+)?)\s+constant\s+(\w+)\s*=\s*(.+?)(?:\s*;)?/g); + if (constantMatches) { + for (const match of constantMatches) { + const parts = match.match(/(\w+(?:\s*\d+)?)\s+constant\s+(\w+)\s*=\s*(.+?)(?:\s*;)?$/); + if (parts) { + constants.push({ + name: parts[2], + type: parts[1], + value: parts[3].trim(), + description: descriptionBuffer.join(' ').trim(), + }); + } + } + } else { + // Single constant definition (more flexible regex) + const singleMatch = codeContent.match(/(\w+(?:\s*\d+)?)\s+constant\s+(\w+)\s*=\s*(.+?)(?:\s*;)?$/); + if (singleMatch) { + constants.push({ + name: singleMatch[2], + type: singleMatch[1], + value: singleMatch[3].trim(), + description: descriptionBuffer.join(' ').trim(), + }); + } + } + // Clear description buffer after processing constants + descriptionBuffer = []; + } else { + signature = codeContent; + } + inCodeBlock = false; + continue; + } + + // Parse constants with ### heading format + if (itemType === 'constants' && line.startsWith('### ')) { + const constantName = line.replace('### ', '').trim(); + // Clear description buffer for this constant (only text before this heading) + // Filter out code block delimiters and empty lines + const currentConstantDesc = descriptionBuffer + .filter(l => l && !l.trim().startsWith('```') && l.trim() !== '') + .join(' ') + .trim(); + descriptionBuffer = []; + + // Look ahead for code block (within next 15 lines) + let foundCodeBlock = false; + let codeBlockEndIndex = i; + for (let j = i + 1; j < lines.length && j < i + 15; j++) { + if (lines[j].startsWith('```solidity')) { + foundCodeBlock = true; + const constCodeLines = []; + j++; + while (j < lines.length && !lines[j].startsWith('```')) { + constCodeLines.push(lines[j]); + j++; + } + codeBlockEndIndex = j; // j now points to the line after closing ``` + const constCode = constCodeLines.join('\n').trim(); + // Match: type constant name = value + // Handle complex types like "bytes32", "uint256", etc. + const constMatch = constCode.match(/(\w+(?:\s*\d+)?)\s+constant\s+(\w+)\s*=\s*(.+?)(?:\s*;)?$/); + if (constMatch) { + constants.push({ + name: constantName, + type: constMatch[1], + value: constMatch[3].trim(), + description: currentConstantDesc, + }); + } else { + // Fallback: if no match, still add constant with name from heading + constants.push({ + name: constantName, + type: '', + value: constCode, + description: currentConstantDesc, + }); + } + break; + } + } + if (!foundCodeBlock) { + // No code block found, but we have a heading - might be a constant without definition + // This shouldn't happen in forge doc output, but handle it gracefully + constants.push({ + name: constantName, + type: '', + value: '', + description: currentConstantDesc, + }); + } else { + // Skip to the end of the code block (the loop will increment i, so we set it to one before) + i = codeBlockEndIndex - 1; + } + continue; + } + + // Collect description (text before code block or after title) + // Skip code block delimiters, empty lines, and markdown table separators + if (!inCodeBlock && trimmedLine && + !trimmedLine.startsWith('#') && + !trimmedLine.startsWith('[') && + !trimmedLine.startsWith('|') && + !trimmedLine.startsWith('```') && + trimmedLine !== '') { + if (itemType !== 'constants' || !line.startsWith('###')) { + descriptionBuffer.push(trimmedLine); + } + continue; + } + + // Parse table rows (Parameters or Returns) + if (trimmedLine.startsWith('|') && !trimmedLine.includes('----')) { + const cells = trimmedLine.split('|').map(c => c.trim()).filter(c => c); + + if (cells.length >= 3 && cells[0] !== 'Name' && cells[0] !== 'Parameter') { + const paramName = cells[0].replace(/`/g, '').trim(); + const paramType = cells[1].replace(/`/g, '').trim(); + const paramDesc = sanitizeBrokenLinks(cells[2] || ''); + + // Determine if Parameters or Returns based on preceding lines + const precedingLines = lines.slice(Math.max(0, i - 10), i).join('\n'); + + if (precedingLines.includes('**Returns**')) { + returns.push({ + name: paramName === '' ? '' : paramName, + type: paramType, + description: paramDesc, + }); + } else if (precedingLines.includes('**Parameters**')) { + if (paramType || paramName.startsWith('_')) { + params.push({ + name: paramName, + type: paramType, + description: paramDesc, + }); + } + } + } + } + } + + // Combine description buffer and clean it + if (descriptionBuffer.length > 0) { + description = cleanDescription(sanitizeBrokenLinks(descriptionBuffer.join(' ').trim())); + } + + // For constants, return array of constant objects + if (itemType === 'constants') { + return { + type: 'constants', + constants: constants.length > 0 ? constants : [{ + name: itemName || 'Constants', + type: '', + value: '', + description: description, + }], + gitSource: gitSource, + }; + } + + // For structs, use definition instead of signature + if (itemType === 'struct') { + definition = signature; + signature = ''; + } + + // Create item object based on type + const item = { + name: itemName, + description: description, + notice: description, + signature: signature, + definition: definition, + params: params, + returns: returns, + gitSource: gitSource, + }; + + // Add mutability for functions + if (itemType === 'function' && signature) { + if (signature.includes(' view ')) { + item.mutability = 'view'; + } else if (signature.includes(' pure ')) { + item.mutability = 'pure'; + } else if (signature.includes(' payable ')) { + item.mutability = 'payable'; + } else { + item.mutability = 'nonpayable'; + } + } + + return { + type: itemType, + item: item, + }; +} + +/** + * Aggregate multiple parsed items into a single data structure + * @param {Array} parsedItems - Array of parsed item objects from parseIndividualItemFile + * @param {string} sourceFilePath - Path to the source Solidity file + * @returns {object} Aggregated documentation data + */ +function aggregateParsedItems(parsedItems, sourceFilePath) { + const data = { + title: '', + description: '', + subtitle: '', + overview: '', + gitSource: '', + functions: [], + events: [], + errors: [], + structs: [], + stateVariables: [], + }; + + // Extract module name from source file path + const basename = path.basename(sourceFilePath, '.sol'); + data.title = basename; + + // Extract git source from first item + for (const parsed of parsedItems) { + if (parsed && parsed.gitSource) { + data.gitSource = config.normalizeGitSource(parsed.gitSource); + break; + } + } + + // Group items by type + for (const parsed of parsedItems) { + if (!parsed) continue; + + if (parsed.type === 'function' && parsed.item) { + data.functions.push(parsed.item); + } else if (parsed.type === 'error' && parsed.item) { + data.errors.push(parsed.item); + } else if (parsed.type === 'event' && parsed.item) { + data.events.push(parsed.item); + } else if (parsed.type === 'struct' && parsed.item) { + data.structs.push(parsed.item); + } else if (parsed.type === 'enum' && parsed.item) { + // Enums can be treated as structs for display purposes + data.structs.push(parsed.item); + } else if (parsed.type === 'constants' && parsed.constants) { + // Add constants as state variables + for (const constant of parsed.constants) { + data.stateVariables.push({ + name: constant.name, + type: constant.type, + value: constant.value, + description: constant.description, + }); + } + } + } + + // Set default description if not provided + // Don't use item descriptions as module description - they'll be overridden by source file parsing + if (!data.description || + data.description.includes('Event emitted') || + data.description.includes('Thrown when') || + data.description.includes('function to') || + data.description.length < 20) { + data.description = `Documentation for ${data.title}`; + data.subtitle = data.description; + data.overview = data.description; + } + + return data; +} + +module.exports = { + detectItemTypeFromFilename, + parseIndividualItemFile, + aggregateParsedItems, +}; + diff --git a/.github/scripts/generate-docs-utils/parsing/markdown-parser.js b/.github/scripts/generate-docs-utils/parsing/markdown-parser.js new file mode 100644 index 00000000..f16ade89 --- /dev/null +++ b/.github/scripts/generate-docs-utils/parsing/markdown-parser.js @@ -0,0 +1,236 @@ +/** + * Markdown Parser + * + * Main parser for forge doc markdown output. + */ + +const config = require('../config'); +const { createNewItem, saveCurrentItem } = require('./item-builder'); +const { sanitizeBrokenLinks, cleanDescription } = require('./text-sanitizer'); + +/** + * Parse forge doc markdown output into structured data + * @param {string} content - Markdown content from forge doc + * @param {string} filePath - Path to the markdown file + * @returns {object} Parsed documentation data + */ +function parseForgeDocMarkdown(content, filePath) { + const data = { + title: '', + description: '', + subtitle: '', + overview: '', + gitSource: '', + functions: [], + events: [], + errors: [], + structs: [], + stateVariables: [], + }; + + const lines = content.split('\n'); + let currentSection = null; + let currentItem = null; + let itemType = null; + let collectingDescription = false; + let descriptionBuffer = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmedLine = line.trim(); + + // Parse title (# heading) + if (line.startsWith('# ') && !data.title) { + data.title = line.replace('# ', '').trim(); + continue; + } + + // Parse git source link + if (trimmedLine.startsWith('[Git Source]')) { + const match = trimmedLine.match(/\[Git Source\]\((.*?)\)/); + if (match) { + data.gitSource = config.normalizeGitSource(match[1]); + } + continue; + } + + // Parse description (first non-empty lines after title, before sections) + if (data.title && !currentSection && trimmedLine && !line.startsWith('#') && !line.startsWith('[')) { + const sanitizedLine = cleanDescription(sanitizeBrokenLinks(trimmedLine)); + if (!data.description) { + data.description = sanitizedLine; + data.subtitle = sanitizedLine; + } else if (!data.overview) { + // Capture additional lines as overview + data.overview = data.description + '\n\n' + sanitizedLine; + } + continue; + } + + // Parse main sections + if (line.startsWith('## ')) { + const sectionName = line.replace('## ', '').trim().toLowerCase(); + + // Save current item before switching sections + if (currentItem) { + saveCurrentItem(data, currentItem, itemType); + currentItem = null; + itemType = null; + } + + if (sectionName === 'functions') { + currentSection = 'functions'; + } else if (sectionName === 'events') { + currentSection = 'events'; + } else if (sectionName === 'errors') { + currentSection = 'errors'; + } else if (sectionName === 'structs') { + currentSection = 'structs'; + } else if (sectionName === 'state variables') { + currentSection = 'stateVariables'; + } else { + currentSection = null; + } + continue; + } + + // Parse item definitions (### heading) + if (line.startsWith('### ') && currentSection) { + // Save previous item + if (currentItem) { + saveCurrentItem(data, currentItem, itemType); + } + + const name = line.replace('### ', '').trim(); + itemType = currentSection; + currentItem = createNewItem(name, currentSection); + collectingDescription = true; + descriptionBuffer = []; + continue; + } + + // Process content within current item + if (currentItem) { + // Code block with signature + if (line.startsWith('```solidity')) { + const codeLines = []; + i++; + while (i < lines.length && !lines[i].startsWith('```')) { + codeLines.push(lines[i]); + i++; + } + const codeContent = codeLines.join('\n').trim(); + + if (currentSection === 'functions' || currentSection === 'events' || currentSection === 'errors') { + currentItem.signature = codeContent; + + // Extract mutability from signature + if (codeContent.includes(' view ')) { + currentItem.mutability = 'view'; + } else if (codeContent.includes(' pure ')) { + currentItem.mutability = 'pure'; + } else if (codeContent.includes(' payable ')) { + currentItem.mutability = 'payable'; + } + } else if (currentSection === 'structs') { + currentItem.definition = codeContent; + } else if (currentSection === 'stateVariables') { + // Extract type and value from constant definition + // Format: "bytes32 constant NAME = value;" or "bytes32 NAME = value;" + // Handle both with and without "constant" keyword + // Note: name is already known from the ### heading, so we just need type and value + const constantMatch = codeContent.match(/(\w+(?:\s*\d+)?)\s+(?:constant\s+)?\w+\s*=\s*(.+?)(?:\s*;)?$/); + if (constantMatch) { + currentItem.type = constantMatch[1]; + currentItem.value = constantMatch[2].trim(); + } else { + // Fallback: try to extract just the value part if it's a simple assignment + const simpleMatch = codeContent.match(/=\s*(.+?)(?:\s*;)?$/); + if (simpleMatch) { + currentItem.value = simpleMatch[1].trim(); + } + // Try to extract type from the beginning + const typeMatch = codeContent.match(/^(\w+(?:\s*\d+)?)\s+/); + if (typeMatch) { + currentItem.type = typeMatch[1]; + } + } + } + continue; + } + + // Description text (before **Parameters** or **Returns**) + if (collectingDescription && trimmedLine && !trimmedLine.startsWith('**') && !trimmedLine.startsWith('|')) { + descriptionBuffer.push(trimmedLine); + continue; + } + + // End description collection on special markers + if (trimmedLine.startsWith('**Parameters**') || trimmedLine.startsWith('**Returns**')) { + if (descriptionBuffer.length > 0) { + const description = cleanDescription(sanitizeBrokenLinks(descriptionBuffer.join(' ').trim())); + currentItem.description = description; + currentItem.notice = description; + descriptionBuffer = []; + } + collectingDescription = false; + } + + // Parse table rows (Parameters or Returns) + if (trimmedLine.startsWith('|') && !trimmedLine.includes('----')) { + const cells = trimmedLine.split('|').map(c => c.trim()).filter(c => c); + + // Skip header row + if (cells.length >= 3 && cells[0] !== 'Name' && cells[0] !== 'Parameter') { + const paramName = cells[0].replace(/`/g, '').trim(); + const paramType = cells[1].replace(/`/g, '').trim(); + const paramDesc = sanitizeBrokenLinks(cells[2] || ''); + + // Skip if parameter name matches the function name (parsing error) + if (currentItem && paramName === currentItem.name) { + continue; + } + + // Determine if Parameters or Returns based on preceding lines + const precedingLines = lines.slice(Math.max(0, i - 10), i).join('\n'); + + if (precedingLines.includes('**Returns**')) { + currentItem.returns = currentItem.returns || []; + currentItem.returns.push({ + name: paramName === '' ? '' : paramName, + type: paramType, + description: paramDesc, + }); + } else if (precedingLines.includes('**Parameters**')) { + // Only add if it looks like a valid parameter (has a type or starts with underscore) + if (paramType || paramName.startsWith('_')) { + currentItem.params = currentItem.params || []; + currentItem.params.push({ + name: paramName, + type: paramType, + description: paramDesc, + }); + } + } + } + } + } + } + + // Save last item + if (currentItem) { + saveCurrentItem(data, currentItem, itemType); + } + + // Ensure overview is set + if (!data.overview) { + data.overview = data.description || `Documentation for ${data.title}.`; + } + + return data; +} + +module.exports = { + parseForgeDocMarkdown, +}; + diff --git a/.github/scripts/generate-docs-utils/parsing/storage-extractor.js b/.github/scripts/generate-docs-utils/parsing/storage-extractor.js new file mode 100644 index 00000000..e9f9181a --- /dev/null +++ b/.github/scripts/generate-docs-utils/parsing/storage-extractor.js @@ -0,0 +1,37 @@ +/** + * Storage Extractor + * + * Functions for extracting storage information from parsed data. + */ + +/** + * Extract storage information from parsed data + * @param {object} data - Parsed documentation data + * @returns {string | null} Storage information or null + */ +function extractStorageInfo(data) { + // Look for STORAGE_POSITION in state variables + const storageVar = data.stateVariables.find(v => + v.name.includes('STORAGE') || v.name.includes('storage') + ); + + if (storageVar) { + return `Storage position: \`${storageVar.name}\` - ${storageVar.description || 'Used for diamond storage pattern.'}`; + } + + // Look for storage struct + const storageStruct = data.structs.find(s => + s.name.includes('Storage') + ); + + if (storageStruct) { + return `Uses the \`${storageStruct.name}\` struct following the ERC-8042 diamond storage pattern.`; + } + + return null; +} + +module.exports = { + extractStorageInfo, +}; + diff --git a/.github/scripts/generate-docs-utils/parsing/text-sanitizer.js b/.github/scripts/generate-docs-utils/parsing/text-sanitizer.js new file mode 100644 index 00000000..a4220c80 --- /dev/null +++ b/.github/scripts/generate-docs-utils/parsing/text-sanitizer.js @@ -0,0 +1,66 @@ +/** + * Text Sanitizer + * + * Functions for cleaning and sanitizing text content. + */ + +/** + * Sanitize markdown links that point to non-existent files + * Removes or converts broken links to plain text + * @param {string} text - Text that may contain markdown links + * @returns {string} Sanitized text + */ +function sanitizeBrokenLinks(text) { + if (!text) return text; + + // Remove markdown links that point to /src/ paths (forge doc links) + // Pattern: [text](/src/...) + return text.replace(/\[([^\]]+)\]\(\/src\/[^\)]+\)/g, '$1'); +} + +/** + * Clean description text by removing markdown artifacts + * Strips **Parameters**, **Returns**, **Note:** and other section markers + * that get incorrectly included in descriptions from forge doc output + * @param {string} text - Description text that may contain markdown artifacts + * @returns {string} Cleaned description text + */ +function cleanDescription(text) { + if (!text) return text; + + let cleaned = text; + + // Remove markdown section headers that shouldn't be in descriptions + // These patterns appear when forge doc parsing doesn't stop at section boundaries + const artifactPatterns = [ + /\s*\*\*Parameters\*\*\s*/g, + /\s*\*\*Returns\*\*\s*/g, + /\s*\*\*Note:\*\*\s*/g, + /\s*\*\*Events\*\*\s*/g, + /\s*\*\*Errors\*\*\s*/g, + /\s*\*\*See Also\*\*\s*/g, + /\s*\*\*Example\*\*\s*/g, + ]; + + for (const pattern of artifactPatterns) { + cleaned = cleaned.replace(pattern, ' '); + } + + // Remove @custom: tags that may leak through (e.g., "@custom:error AccessControlUnauthorizedAccount") + cleaned = cleaned.replace(/@custom:\w+\s+/g, ''); + + // Clean up "error: ErrorName" patterns that appear inline + // Keep the error name but format it better: "error: ErrorName If..." -> "Reverts with ErrorName if..." + cleaned = cleaned.replace(/\berror:\s+(\w+)\s+/gi, 'Reverts with $1 '); + + // Normalize whitespace: collapse multiple spaces, trim + cleaned = cleaned.replace(/\s+/g, ' ').trim(); + + return cleaned; +} + +module.exports = { + sanitizeBrokenLinks, + cleanDescription, +}; + diff --git a/.github/scripts/generate-docs-utils/pr-body-generator.js b/.github/scripts/generate-docs-utils/pr-body-generator.js new file mode 100644 index 00000000..c37678cb --- /dev/null +++ b/.github/scripts/generate-docs-utils/pr-body-generator.js @@ -0,0 +1,104 @@ +/** + * PR Body Generator + * + * Generates a PR body from the docgen-summary.json file + * + * Usage: + * node pr-body-generator.js [summary-file-path] + * + * Outputs the PR body in GitHub Actions format to stdout + */ + +const fs = require('fs'); +const path = require('path'); + +/** + * Generate PR body from summary data + * @param {Object} summary - Summary data from docgen-summary.json + * @returns {string} PR body markdown + */ +function generatePRBody(summary) { + const facets = summary.facets || []; + const modules = summary.modules || []; + const total = summary.totalGenerated || 0; + + let body = '## Auto-Generated Docs Pages\n\n'; + body += 'This PR contains auto-generated documentation from contract comments using `forge doc`. '; + body += 'The output is passed through AI to enhance the documentation content and add additional information.\n\n'; + body += '**Please ALWAYS review the generated content and ensure it is accurate and complete to Compose Standards.**\n'; + + + body += '### Summary\n'; + body += `- **Total generated:** ${total} files\n\n`; + + if (facets.length > 0) { + body += '### Facets\n'; + facets.forEach(facet => { + body += `- ${facet.title}\n`; + }); + body += '\n'; + } + + if (modules.length > 0) { + body += '### Modules\n'; + modules.forEach(module => { + body += `- ${module.title}\n`; + }); + body += '\n'; + } + + body += '### What was done\n'; + body += '1. Extracted NatSpec using `forge doc`\n'; + body += '2. Converted to Docusaurus MDX format\n'; + body += '3. Enhanced content with GitHub Copilot (optional)\n'; + body += '4. Verified documentation build\n\n'; + + body += '### Review Checklist\n'; + body += '- [ ] Review generated content for accuracy\n'; + body += '- [ ] Verify code examples are correct\n'; + body += '- [ ] Check for any missing documentation\n'; + body += '- [ ] Ensure consistency with existing docs\n\n'; + + body += '---\n'; + body += '🚨 **This PR was automatically generated. Please ALWAYS review before merging.**\n'; + body += `Generated on: ${new Date().toISOString()}\n`; + + return body; +} + +/** + * Main function + */ +function main() { + const summaryPath = process.argv[2] || 'docgen-summary.json'; + + if (!fs.existsSync(summaryPath)) { + console.error(`Error: Summary file not found: ${summaryPath}`); + process.exit(1); + } + + try { + const summaryContent = fs.readFileSync(summaryPath, 'utf8'); + const summary = JSON.parse(summaryContent); + + const prBody = generatePRBody(summary); + + // Output in GitHub Actions format + console.log('body</g, '>') + .replace(/\{/g, '{') + .replace(/\}/g, '}'); +} + +/** + * Convert object/array to a safe JavaScript expression for JSX attributes + * Returns the value wrapped in curly braces for direct use in JSX: {value} + * @param {*} obj - Value to convert + * @returns {string} JSX expression with curly braces: {JSON} + */ +function toJsxExpression(obj) { + if (obj == null) return '{null}'; + + try { + let jsonStr = JSON.stringify(obj); + // Ensure single line + jsonStr = jsonStr.replace(/[\n\r]/g, ' ').replace(/\s+/g, ' ').trim(); + // Verify it's valid JSON + JSON.parse(jsonStr); + // Return with JSX curly braces included + return `{${jsonStr}}`; + } catch (e) { + console.warn('Invalid JSON generated:', e.message); + return Array.isArray(obj) ? '{[]}' : '{{}}'; + } +} + +/** + * Escape special characters for JSX string attributes + * @param {string} str - String to escape + * @returns {string} Escaped string safe for JSX attributes + */ +function escapeJsx(str) { + if (!str) return ''; + + return sanitizeForMdx(str) + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/'/g, "\\'") + .replace(/\n/g, ' ') + .replace(/\{/g, '{') + .replace(/\}/g, '}') + // Don't escape backticks - they should be preserved for code formatting + .trim(); +} + +/** + * Escape markdown table special characters + * @param {string} str - String to escape + * @returns {string} Escaped string safe for markdown tables + */ +function escapeMarkdownTable(str) { + if (!str) return ''; + return str + .replace(/\|/g, '\\|') + .replace(/\n/g, ' ') + .replace(/\{/g, '{') + .replace(/\}/g, '}'); +} + +/** + * Escape HTML entities for safe display + * @param {string} str - String to escape + * @returns {string} HTML-escaped string + */ +function escapeHtml(str) { + if (!str) return ''; + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +/** + * Escape string for use in JavaScript/JSX object literal values + * Escapes quotes and backslashes for JavaScript strings (not HTML entities) + * Preserves backticks for code formatting + * @param {string} str - String to escape + * @returns {string} Escaped string safe for JavaScript string literals + */ +function escapeJsString(str) { + if (!str) return ''; + return String(str) + .replace(/\\/g, '\\\\') // Escape backslashes first + .replace(/"/g, '\\"') // Escape double quotes + .replace(/'/g, "\\'") // Escape single quotes + .replace(/\n/g, '\\n') // Escape newlines + .replace(/\r/g, '\\r') // Escape carriage returns + .replace(/\t/g, '\\t'); // Escape tabs + // Note: Backticks are preserved for code formatting in descriptions +} + +/** + * Escape string for JSX string attributes, preserving backticks for code formatting + * This is specifically for descriptions that may contain code with backticks + * @param {string} str - String to escape + * @returns {string} Escaped string safe for JSX string attributes with preserved backticks + */ +function escapeJsxPreserveBackticks(str) { + if (!str) return ''; + + // Don't use sanitizeForMdx as it might HTML-escape things + // Just escape what's needed for JSX string attributes + return String(str) + .replace(/\\/g, '\\\\') // Escape backslashes first + .replace(/"/g, '\\"') // Escape double quotes for JSX strings + .replace(/'/g, "\\'") // Escape single quotes + .replace(/\n/g, ' ') // Replace newlines with spaces + .replace(/\{/g, '{') // Escape curly braces for JSX + .replace(/\}/g, '}') // Escape curly braces for JSX + // Preserve backticks - don't escape them, they're needed for code formatting + .trim(); +} + +module.exports = { + escapeYaml, + escapeJsx, + escapeJsxPreserveBackticks, + sanitizeForMdx, + sanitizeMdx: sanitizeForMdx, // Alias for template usage + toJsxExpression, + escapeMarkdownTable, + escapeHtml, + escapeJsString, +}; + diff --git a/.github/scripts/generate-docs-utils/templates/package-lock.json b/.github/scripts/generate-docs-utils/templates/package-lock.json new file mode 100644 index 00000000..b1877ecc --- /dev/null +++ b/.github/scripts/generate-docs-utils/templates/package-lock.json @@ -0,0 +1,79 @@ +{ + "name": "compose-doc-templates", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "compose-doc-templates", + "version": "1.0.0", + "dependencies": { + "handlebars": "^4.7.8" + } + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + } + } +} diff --git a/.github/scripts/generate-docs-utils/templates/package.json b/.github/scripts/generate-docs-utils/templates/package.json new file mode 100644 index 00000000..d5425ad4 --- /dev/null +++ b/.github/scripts/generate-docs-utils/templates/package.json @@ -0,0 +1,10 @@ +{ + "name": "compose-doc-templates", + "version": "1.0.0", + "private": true, + "description": "Template engine for generating MDX documentation", + "dependencies": { + "handlebars": "^4.7.8" + } +} + diff --git a/.github/scripts/generate-docs-utils/templates/pages/contract.mdx.template b/.github/scripts/generate-docs-utils/templates/pages/contract.mdx.template new file mode 100644 index 00000000..6e408dd5 --- /dev/null +++ b/.github/scripts/generate-docs-utils/templates/pages/contract.mdx.template @@ -0,0 +1,275 @@ +--- +sidebar_position: {{position}} +title: "{{escapeYaml title}}" +description: "{{escapeYaml description}}" +{{#if sidebarLabel}} +sidebar_label: "{{sidebarLabel}}" +{{/if}} +{{#if gitSource}} +gitSource: "{{gitSource}}" +{{/if}} +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + + +{{escapeYaml description}} + + +{{#if keyFeatures}} + +{{{sanitizeMdx keyFeatures}}} + +{{/if}} + +{{#if isModule}} + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + +{{/if}} + +## Overview + +{{{sanitizeMdx overview}}} + +## Storage + +{{#if hasStructs}} +{{#each structs}} +### {{name}} + +{{#if description}} +{{{sanitizeMdx description}}} +{{/if}} + +{{#if definition}} + +{{{codeContent definition}}} + +{{/if}} + +{{#unless @last}} +--- +{{/unless}} +{{/each}} +{{/if}} + +{{#if hasStorage}} + +{{#if hasStateVariables}} +### State Variables + + +{{/if}} +{{/if}} + +{{#if hasFunctions}} +## Functions + +{{#each functions}} +### {{name}} + +{{#if description}} +{{{sanitizeMdx description}}} +{{/if}} + +{{#if signature}} + +{{{codeContent signature}}} + +{{/if}} + +{{#if hasParams}} +**Parameters:** + + +{{/if}} + +{{#if hasReturns}} +**Returns:** + + +{{/if}} + +{{#unless @last}} +--- +{{/unless}} +{{/each}} +{{/if}} + +{{#if hasEvents}} +## Events + + +{{#each events}} + + {{#if description}} +
+ {{{sanitizeMdx description}}} +
+ {{/if}} + + {{#if signature}} +
+ Signature: + +{{{codeContent signature}}} + +
+ {{/if}} + + {{#if hasParams}} +
+ Parameters: + +
+ {{/if}} +
+{{/each}} +
+{{/if}} + +{{#if hasErrors}} +## Errors + + +{{#each errors}} + + {{#if description}} +
+ {{{sanitizeMdx description}}} +
+ {{/if}} + + {{#if signature}} +
+ Signature: + +{{signature}} + +
+ {{/if}} +
+{{/each}} +
+{{/if}} + +{{#if usageExample}} + + +{{/if}} + +{{#if bestPractices}} +## Best Practices + + +{{{sanitizeMdx bestPractices}}} + +{{/if}} + +{{#if isFacet}} +{{#if securityConsiderations}} +## Security Considerations + + +{{{sanitizeMdx securityConsiderations}}} + +{{/if}} +{{/if}} + +{{#if isModule}} +{{#if integrationNotes}} +## Integration Notes + + +{{{sanitizeMdx integrationNotes}}} + +{{/if}} +{{/if}} + +{{#if relatedDocs}} +
+ +
+{{/if}} + +
+ +
+ + diff --git a/.github/scripts/generate-docs-utils/templates/template-engine-handlebars.js b/.github/scripts/generate-docs-utils/templates/template-engine-handlebars.js new file mode 100644 index 00000000..18fb38d0 --- /dev/null +++ b/.github/scripts/generate-docs-utils/templates/template-engine-handlebars.js @@ -0,0 +1,262 @@ +/** + * Handlebars Template Engine for MDX Documentation Generation + * + * Replaces the custom template engine with Handlebars for better reliability + * and proper MDX formatting. + */ + +const Handlebars = require('handlebars'); +const fs = require('fs'); +const path = require('path'); +const helpers = require('./helpers'); + +// Track if helpers have been registered (only register once) +let helpersRegistered = false; + +/** + * Register custom helpers for Handlebars + * All helpers from helpers.js are registered for use in templates + */ +function registerHelpers() { + if (helpersRegistered) return; + + // Register escape helpers + Handlebars.registerHelper('escapeYaml', helpers.escapeYaml); + Handlebars.registerHelper('escapeJsx', helpers.escapeJsx); + // Helper to escape JSX strings while preserving backticks for code formatting + Handlebars.registerHelper('escapeJsxPreserveBackticks', function(value) { + if (!value) return ''; + const escaped = helpers.escapeJsxPreserveBackticks(value); + // Return as SafeString to prevent Handlebars from HTML-escaping backticks + return new Handlebars.SafeString(escaped); + }); + Handlebars.registerHelper('sanitizeMdx', helpers.sanitizeMdx); + Handlebars.registerHelper('escapeMarkdownTable', helpers.escapeMarkdownTable); + // Helper to escape value for JavaScript strings in JSX object literals + Handlebars.registerHelper('escapeJsString', function(value) { + if (!value) return ''; + const escaped = helpers.escapeJsString(value); + return new Handlebars.SafeString(escaped); + }); + + // Helper to emit a JSX style literal: returns a string like {{display: "flex", gap: "1rem"}} + Handlebars.registerHelper('styleLiteral', function(styles) { + if (!styles || typeof styles !== 'string') return '{{}}'; + + const styleObj = {}; + const pairs = styles.split(';').filter(pair => pair.trim()); + + pairs.forEach(pair => { + const [key, value] = pair.split(':').map(s => s.trim()); + if (key && value !== undefined) { + const camelKey = key.includes('-') + ? key.replace(/-([a-z])/g, (_, c) => c.toUpperCase()) + : key; + const cleanValue = value.replace(/^["']|["']$/g, ''); + styleObj[camelKey] = cleanValue; + } + }); + + const entries = Object.entries(styleObj).map(([k, v]) => { + const isPureNumber = /^-?\d+\.?\d*$/.test(v.trim()); + // Quote everything except pure numbers + const valueLiteral = isPureNumber ? v : JSON.stringify(v); + return `${k}: ${valueLiteral}`; + }).join(', '); + + // Wrap with double braces so MDX sees style={{...}} + return `{{${entries}}}`; + }); + + // Helper to wrap code content in template literal for MDX + // This ensures MDX treats the content as a string, not JSX to parse + Handlebars.registerHelper('codeContent', function(content) { + if (!content) return '{``}'; + // Escape backticks in the content + const escaped = String(content).replace(/`/g, '\\`').replace(/\$/g, '\\$'); + return `{\`${escaped}\`}`; + }); + + // Helper to generate JSX style object syntax + // Accepts CSS string and converts to JSX object format + // Handles both kebab-case (margin-bottom) and camelCase (marginBottom) + Handlebars.registerHelper('jsxStyle', function(styles) { + if (!styles || typeof styles !== 'string') return '{}'; + + // Parse CSS string like "display: flex; margin-bottom: 1rem;" or "marginBottom: 1rem" + const styleObj = {}; + const pairs = styles.split(';').filter(pair => pair.trim()); + + pairs.forEach(pair => { + const [key, value] = pair.split(':').map(s => s.trim()); + if (key && value) { + // Convert kebab-case to camelCase if needed + const camelKey = key.includes('-') + ? key.replace(/-([a-z])/g, (g) => g[1].toUpperCase()) + : key; + // Remove quotes from value if present + const cleanValue = value.replace(/^["']|["']$/g, ''); + styleObj[camelKey] = cleanValue; + } + }); + + // Convert to JSX object string with proper quoting + // All CSS values should be quoted as strings unless they're pure numbers + const entries = Object.entries(styleObj) + .map(([k, v]) => { + // Check if it's a pure number (integer or decimal without units) + if (/^-?\d+\.?\d*$/.test(v.trim())) { + return `${k}: ${v}`; + } + // Everything else (including CSS units like "0.75rem", "2rem", CSS vars, etc.) should be quoted + return `${k}: ${JSON.stringify(v)}`; + }) + .join(', '); + + // Return the object content wrapped in braces + // When used with {{{jsxStyle ...}}} in template, this becomes style={...} + // But we need style={{...}}, so we return with an extra opening brace + // The template uses {{{jsxStyle ...}}} which outputs raw, giving us style={{{...}}} + // To get style={{...}}, we need the helper to return {{...}} + // But with triple braces in template, we'd get style={{{{...}}}} which is wrong + // Solution: return just the object, template adds one brace manually + // Return the full JSX object expression with double braces + // Template will use raw block: {{{{jsxStyle ...}}}} + // This outputs: style={{{display: "flex", ...}}} + // But we need: style={{display: "flex", ...}} + // Actually, let's try: helper returns {{...}}, template uses {{jsxStyle}} (double) + // Handlebars will output the helper result + // But it will escape... unless we use raw block + // Simplest: return {{...}}, use {{{{jsxStyle}}}} raw block + return `{{${entries}}}`; + }); + + // Custom helper for better null/empty string handling + // Handlebars' default #if treats empty strings as falsy, but we want to be explicit + Handlebars.registerHelper('ifTruthy', function(value, options) { + if (value != null && + !(Array.isArray(value) && value.length === 0) && + !(typeof value === 'string' && value.trim().length === 0) && + !(typeof value === 'object' && Object.keys(value).length === 0)) { + return options.fn(this); + } + return options.inverse(this); + }); + + helpersRegistered = true; +} + +/** + * Normalize MDX formatting to ensure proper blank lines + * MDX requires blank lines between: + * - Import statements and JSX + * - JSX components and markdown + * - JSX components and other JSX + * + * @param {string} content - MDX content to normalize + * @returns {string} Properly formatted MDX + */ +function normalizeMdxFormatting(content) { + if (!content) return ''; + + let normalized = content; + + // 1. Ensure blank line after import statements (before JSX) + // Pattern: import ...;\n\n## + normalized = normalized.replace(/(\/>)\n(##)/g, '$1\n\n$2'); + + // 3. Ensure blank line after JSX closing tags (before other JSX) + // Pattern: \n)\n(<[A-Z])/g, '$1\n\n$2'); + + // 4. Ensure blank line after JSX closing tags (before markdown content) + // Pattern: \n## or \n[text] + normalized = normalized.replace(/(<\/[A-Z][a-zA-Z]+>)\n(##|[A-Z])/g, '$1\n\n$2'); + + // 5. Ensure blank line before JSX components (after markdown) + // Pattern: ]\n line.trimEnd()).join('\n'); + + // 8. Ensure file ends with single newline + normalized = normalized.trimEnd() + '\n'; + + return normalized; +} + +/** + * List available template files + * @returns {string[]} Array of template names (without extension) + */ +function listAvailableTemplates() { + const templatesDir = path.join(__dirname, 'pages'); + try { + return fs.readdirSync(templatesDir) + .filter(f => f.endsWith('.mdx.template')) + .map(f => f.replace('.mdx.template', '')); + } catch (e) { + return []; + } +} + +/** + * Load and render a template file with Handlebars + * @param {string} templateName - Name of template (without extension) + * @param {object} data - Data to render + * @returns {string} Rendered template with proper MDX formatting + * @throws {Error} If template cannot be loaded + */ +function loadAndRenderTemplate(templateName, data) { + const templatePath = path.join(__dirname, 'pages', `${templateName}.mdx.template`); + + if (!fs.existsSync(templatePath)) { + const available = listAvailableTemplates(); + throw new Error( + `Template '${templateName}' not found at: ${templatePath}\n` + + `Available templates: ${available.length > 0 ? available.join(', ') : 'none'}` + ); + } + + // Register helpers (only once, but safe to call multiple times) + registerHelpers(); + + try { + // Load template + const templateContent = fs.readFileSync(templatePath, 'utf8'); + + // Compile template with Handlebars + const template = Handlebars.compile(templateContent); + + // Render with data + let rendered = template(data); + + // Post-process: normalize MDX formatting + rendered = normalizeMdxFormatting(rendered); + + return rendered; + } catch (error) { + if (error.message.includes('Parse error')) { + throw new Error( + `Template parsing error in ${templateName}: ${error.message}\n` + + `Template path: ${templatePath}` + ); + } + throw error; + } +} + +module.exports = { + loadAndRenderTemplate, + registerHelpers, + listAvailableTemplates, +}; + diff --git a/.github/scripts/generate-docs-utils/templates/template-engine.js b/.github/scripts/generate-docs-utils/templates/template-engine.js new file mode 100644 index 00000000..b8e2ed22 --- /dev/null +++ b/.github/scripts/generate-docs-utils/templates/template-engine.js @@ -0,0 +1,366 @@ +/** + * Simple Template Engine + * + * A lightweight template engine with no external dependencies. + * + * Supports: + * - Variable substitution: {{variable}} (HTML escaped) + * - Unescaped output: {{{variable}}} (raw output) + * - Conditionals: {{#if variable}}...{{/if}} + * - Loops: {{#each array}}...{{/each}} + * - Helper functions: {{helperName variable}} or {{helperName(arg1, arg2)}} + * - Dot notation: {{object.property.nested}} + */ + +const fs = require('fs'); +const path = require('path'); + +// Import helpers from separate module +const helpers = require('./helpers'); +const { escapeHtml } = helpers; + +/** + * Get value from object using dot notation path + * @param {object} obj - Object to get value from + * @param {string} dotPath - Dot notation path (e.g., "user.name") + * @returns {*} Value at path or undefined + */ +function getValue(obj, dotPath) { + if (!dotPath || !obj) return undefined; + + const parts = dotPath.split('.'); + let value = obj; + + for (const part of parts) { + if (value == null) return undefined; + value = value[part]; + } + + return value; +} + +/** + * Check if a value is truthy for template conditionals + * - null/undefined → false + * - empty array → false + * - empty object → false + * - empty string → false + * - false → false + * - everything else → true + * + * @param {*} value - Value to check + * @returns {boolean} Whether value is truthy + */ +function isTruthy(value) { + if (value == null) return false; + if (Array.isArray(value)) return value.length > 0; + if (typeof value === 'object') return Object.keys(value).length > 0; + if (typeof value === 'string') return value.trim().length > 0; + return Boolean(value); +} + +/** + * Process a helper function call + * @param {string} helperName - Name of the helper + * @param {string[]} args - Argument strings (variable paths or literals) + * @param {object} context - Current template context + * @param {object} helperRegistry - Registry of helper functions + * @returns {string} Result of helper function + */ +function processHelper(helperName, args, context, helperRegistry) { + const helper = helperRegistry[helperName]; + if (!helper) { + console.warn(`Unknown template helper: ${helperName}`); + return ''; + } + + // Process arguments - can be variable paths or quoted literals + const processedArgs = args.map(arg => { + arg = arg.trim(); + // Check for quoted literal strings + if ((arg.startsWith('"') && arg.endsWith('"')) || + (arg.startsWith("'") && arg.endsWith("'"))) { + return arg.slice(1, -1); + } + // Otherwise treat as variable path + return getValue(context, arg); + }); + + return helper(...processedArgs); +} + +/** + * Process a variable expression (helper or simple variable) + * @param {string} expression - The expression inside {{ }} + * @param {object} context - Current template context + * @param {boolean} escapeOutput - Whether to HTML-escape the output + * @param {object} helperRegistry - Registry of helper functions + * @returns {string} Processed value + */ +function processExpression(expression, context, escapeOutput, helperRegistry) { + const expr = expression.trim(); + + // Check for helper with parentheses: helperName(arg1, arg2) + const parenMatch = expr.match(/^(\w+)\((.*)\)$/); + if (parenMatch) { + const [, helperName, argsStr] = parenMatch; + const args = argsStr ? argsStr.split(',').map(a => a.trim()) : []; + return processHelper(helperName, args, context, helperRegistry); + } + + // Check for helper with space: helperName variable + const spaceMatch = expr.match(/^(\w+)\s+(.+)$/); + if (spaceMatch && helperRegistry[spaceMatch[1]]) { + const [, helperName, arg] = spaceMatch; + return processHelper(helperName, [arg], context, helperRegistry); + } + + // Regular variable lookup + const value = getValue(context, expr); + if (value == null) return ''; + + const str = String(value); + return escapeOutput ? escapeHtml(str) : str; +} + +/** + * Find the matching closing tag for a block, handling nesting + * @param {string} content - Content to search + * @param {string} openTag - Opening tag pattern (e.g., '#if', '#each') + * @param {string} closeTag - Closing tag (e.g., '/if', '/each') + * @param {number} startPos - Position after the opening tag + * @returns {number} Position of the matching closing tag, or -1 if not found + */ +function findMatchingClose(content, openTag, closeTag, startPos) { + let depth = 1; + let pos = startPos; + + const openPattern = new RegExp(`\\{\\{${openTag}\\s+[^}]+\\}\\}`, 'g'); + const closePattern = new RegExp(`\\{\\{${closeTag}\\}\\}`, 'g'); + + while (depth > 0 && pos < content.length) { + // Find next open and close tags + openPattern.lastIndex = pos; + closePattern.lastIndex = pos; + + const openMatch = openPattern.exec(content); + const closeMatch = closePattern.exec(content); + + if (!closeMatch) { + return -1; // No matching close found + } + + // If open comes before close, increase depth + if (openMatch && openMatch.index < closeMatch.index) { + depth++; + pos = openMatch.index + openMatch[0].length; + } else { + depth--; + if (depth === 0) { + return closeMatch.index; + } + pos = closeMatch.index + closeMatch[0].length; + } + } + + return -1; +} + +/** + * Process nested conditionals: {{#if variable}}...{{/if}} + * @param {string} content - Template content + * @param {object} context - Data context + * @param {object} helperRegistry - Registry of helper functions + * @returns {string} Processed content + */ +function processConditionals(content, context, helperRegistry) { + let result = content; + const openPattern = /\{\{#if\s+([^}]+)\}\}/g; + + let match; + while ((match = openPattern.exec(result)) !== null) { + const condition = match[1].trim(); + const startPos = match.index; + const afterOpen = startPos + match[0].length; + + const closePos = findMatchingClose(result, '#if', '/if', afterOpen); + if (closePos === -1) { + console.warn(`Unmatched {{#if ${condition}}} at position ${startPos}`); + break; + } + + const ifContent = result.substring(afterOpen, closePos); + const closeEndPos = closePos + '{{/if}}'.length; + + // Evaluate condition and get replacement + const value = getValue(context, condition); + const replacement = isTruthy(value) + ? processContent(ifContent, context, helperRegistry) + : ''; + + // Replace in result + result = result.substring(0, startPos) + replacement + result.substring(closeEndPos); + + // Reset pattern to start from beginning since we modified the string + openPattern.lastIndex = 0; + } + + return result; +} + +/** + * Process nested loops: {{#each array}}...{{/each}} + * @param {string} content - Template content + * @param {object} context - Data context + * @param {object} helperRegistry - Registry of helper functions + * @returns {string} Processed content + */ +function processLoops(content, context, helperRegistry) { + let result = content; + const openPattern = /\{\{#each\s+([^}]+)\}\}/g; + + let match; + while ((match = openPattern.exec(result)) !== null) { + const arrayPath = match[1].trim(); + const startPos = match.index; + const afterOpen = startPos + match[0].length; + + const closePos = findMatchingClose(result, '#each', '/each', afterOpen); + if (closePos === -1) { + console.warn(`Unmatched {{#each ${arrayPath}}} at position ${startPos}`); + break; + } + + const loopContent = result.substring(afterOpen, closePos); + const closeEndPos = closePos + '{{/each}}'.length; + + // Get array and process each item + const array = getValue(context, arrayPath); + let replacement = ''; + + if (Array.isArray(array) && array.length > 0) { + replacement = array.map((item, index) => { + const itemContext = { ...context, ...item, index }; + return processContent(loopContent, itemContext, helperRegistry); + }).join(''); + } + + // Replace in result + result = result.substring(0, startPos) + replacement + result.substring(closeEndPos); + + // Reset pattern to start from beginning since we modified the string + openPattern.lastIndex = 0; + } + + return result; +} + +/** + * Process template content with the given context + * Handles all variable substitutions, helpers, conditionals, and loops + * + * IMPORTANT: Processing order matters! + * 1. Loops first - so nested conditionals are evaluated with correct item context + * 2. Conditionals second - after loops have expanded their content + * 3. Variables last - after all control structures are resolved + * + * @param {string} content - Template content to process + * @param {object} context - Data context + * @param {object} helperRegistry - Registry of helper functions + * @returns {string} Processed content + */ +function processContent(content, context, helperRegistry) { + let result = content; + + // 1. Process loops FIRST (handles nesting properly) + result = processLoops(result, context, helperRegistry); + + // 2. Process conditionals SECOND (handles nesting properly) + result = processConditionals(result, context, helperRegistry); + + // 3. Process triple braces for unescaped output: {{{variable}}} + const tripleBracePattern = /\{\{\{([^}]+)\}\}\}/g; + result = result.replace(tripleBracePattern, (match, expr) => { + return processExpression(expr, context, false, helperRegistry); + }); + + // 4. Process double braces for escaped output: {{variable}} + const doubleBracePattern = /\{\{([^}]+)\}\}/g; + result = result.replace(doubleBracePattern, (match, expr) => { + return processExpression(expr, context, true, helperRegistry); + }); + + return result; +} + +/** + * Render a template string with data + * @param {string} template - Template string + * @param {object} data - Data to render + * @returns {string} Rendered template + */ +function renderTemplate(template, data) { + if (!template) return ''; + if (!data) data = {}; + + return processContent(template, { ...data }, helpers); +} + +/** + * List available template files + * @returns {string[]} Array of template names (without extension) + */ +function listAvailableTemplates() { + const templatesDir = path.join(__dirname, 'pages'); + try { + return fs.readdirSync(templatesDir) + .filter(f => f.endsWith('.mdx.template')) + .map(f => f.replace('.mdx.template', '')); + } catch (e) { + return []; + } +} + +/** + * Load and render a template file + * @param {string} templateName - Name of template (without extension) + * @param {object} data - Data to render + * @returns {string} Rendered template + * @throws {Error} If template cannot be loaded + */ +function loadAndRenderTemplate(templateName, data) { + console.log('Loading template:', templateName); + console.log('Data:', data); + + const templatePath = path.join(__dirname, 'pages', `${templateName}.mdx.template`); + + try { + if (!fs.existsSync(templatePath)) { + const available = listAvailableTemplates(); + throw new Error( + `Template '${templateName}' not found at: ${templatePath}\n` + + `Available templates: ${available.length > 0 ? available.join(', ') : 'none'}` + ); + } + + const template = fs.readFileSync(templatePath, 'utf8'); + return renderTemplate(template, data); + } catch (error) { + if (error.code === 'ENOENT') { + const available = listAvailableTemplates(); + throw new Error( + `Template file not found: ${templatePath}\n` + + `Available templates: ${available.length > 0 ? available.join(', ') : 'none'}` + ); + } + throw error; + } +} + +module.exports = { + renderTemplate, + loadAndRenderTemplate, + getValue, + isTruthy, + listAvailableTemplates, +}; diff --git a/.github/scripts/generate-docs-utils/templates/templates.js b/.github/scripts/generate-docs-utils/templates/templates.js new file mode 100644 index 00000000..4bc33658 --- /dev/null +++ b/.github/scripts/generate-docs-utils/templates/templates.js @@ -0,0 +1,720 @@ +/** + * MDX Templates for Docusaurus documentation + * Uses Handlebars template engine for reliable MDX generation + */ + +const { loadAndRenderTemplate } = require('./template-engine-handlebars'); +const { sanitizeForMdx } = require('./helpers'); +const { readFileSafe } = require('../../workflow-utils'); +const { enrichWithRelationships } = require('../core/relationship-detector'); +const { getSidebarLabel, formatDisplayTitle } = require('../core/description-generator'); + +/** + * Extract function parameters directly from Solidity source file + * @param {string} sourceFilePath - Path to the Solidity source file + * @param {string} functionName - Name of the function to extract parameters from + * @returns {Array} Array of parameter objects with name and type + */ +function extractParamsFromSource(sourceFilePath, functionName) { + if (!sourceFilePath || !functionName) return []; + + const sourceContent = readFileSafe(sourceFilePath); + if (!sourceContent) { + if (process.env.DEBUG_PARAMS) { + console.log(`[DEBUG] Could not read source file: ${sourceFilePath}`); + } + return []; + } + + // Remove comments to avoid parsing issues + const withoutComments = sourceContent + .replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments + .replace(/\/\/.*$/gm, ''); // Remove line comments + + // Find function definition - match function name followed by opening parenthesis + // Handle both regular functions and free functions + const functionPattern = new RegExp( + `function\\s+${functionName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*\\(([^)]*)\\)`, + 's' + ); + + const match = withoutComments.match(functionPattern); + if (!match || !match[1]) { + if (process.env.DEBUG_PARAMS) { + console.log(`[DEBUG] Function ${functionName} not found in source file`); + } + return []; + } + + const paramsStr = match[1].trim(); + if (!paramsStr) { + return []; // Function has no parameters + } + + // Parse parameters - handle complex types like mappings, arrays, structs + const params = []; + let currentParam = ''; + let depth = 0; + let inString = false; + let stringChar = ''; + + for (let i = 0; i < paramsStr.length; i++) { + const char = paramsStr[i]; + + // Handle string literals + if ((char === '"' || char === "'") && (i === 0 || paramsStr[i - 1] !== '\\')) { + if (!inString) { + inString = true; + stringChar = char; + } else if (char === stringChar) { + inString = false; + } + currentParam += char; + continue; + } + + if (inString) { + currentParam += char; + continue; + } + + // Track nesting depth for generics, arrays, mappings + if (char === '<' || char === '[' || char === '(') { + depth++; + currentParam += char; + } else if (char === '>' || char === ']' || char === ')') { + depth--; + currentParam += char; + } else if (char === ',' && depth === 0) { + // Found a parameter boundary + const trimmed = currentParam.trim(); + if (trimmed) { + const parsed = parseParameter(trimmed); + if (parsed) { + params.push(parsed); + } + } + currentParam = ''; + } else { + currentParam += char; + } + } + + // Handle last parameter + const trimmed = currentParam.trim(); + if (trimmed) { + const parsed = parseParameter(trimmed); + if (parsed) { + params.push(parsed); + } + } + + if (process.env.DEBUG_PARAMS) { + console.log(`[DEBUG] Extracted ${params.length} params from source for ${functionName}:`, JSON.stringify(params, null, 2)); + } + + return params; +} + +/** + * Parse a single parameter string into name and type + * @param {string} paramStr - Parameter string (e.g., "uint256 amount" or "address") + * @returns {object|null} Object with name and type, or null if invalid + */ +function parseParameter(paramStr) { + if (!paramStr || !paramStr.trim()) return null; + + // Remove storage location keywords + const cleaned = paramStr + .replace(/\b(memory|storage|calldata)\b/g, '') + .replace(/\s+/g, ' ') + .trim(); + + // Split by whitespace - last token is usually the name, rest is type + const parts = cleaned.split(/\s+/); + + if (parts.length === 0) return null; + + // If only one part, it's just a type (unnamed parameter) + if (parts.length === 1) { + return { name: '', type: parts[0], description: '' }; + } + + // Last part is the name, everything before is the type + const name = parts[parts.length - 1]; + const type = parts.slice(0, -1).join(' '); + + // Validate: name should be a valid identifier + if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) { + // If name doesn't look valid, treat the whole thing as a type + return { name: '', type: cleaned, description: '' }; + } + + return { name, type, description: '' }; +} + +/** + * Extract parameters from function signature string + * @param {string} signature - Function signature string + * @returns {Array} Array of parameter objects with name and type + */ +function extractParamsFromSignature(signature) { + if (!signature || typeof signature !== 'string') return []; + + // Match function parameters: function name(params) or just (params) + const paramMatch = signature.match(/\(([^)]*)\)/); + if (!paramMatch || !paramMatch[1]) return []; + + const paramsStr = paramMatch[1].trim(); + if (!paramsStr) return []; + + // Split by comma, but be careful with nested generics + const params = []; + let currentParam = ''; + let depth = 0; + + for (let i = 0; i < paramsStr.length; i++) { + const char = paramsStr[i]; + if (char === '<') depth++; + else if (char === '>') depth--; + else if (char === ',' && depth === 0) { + const trimmed = currentParam.trim(); + if (trimmed) { + // Parse "type name" or just "type" + const parts = trimmed.split(/\s+/); + if (parts.length >= 2) { + // Has both type and name + const type = parts.slice(0, -1).join(' '); + const name = parts[parts.length - 1]; + params.push({ name, type, description: '' }); + } else if (parts.length === 1) { + // Just type, no name + params.push({ name: '', type: parts[0], description: '' }); + } + } + currentParam = ''; + continue; + } + currentParam += char; + } + + // Handle last parameter + const trimmed = currentParam.trim(); + if (trimmed) { + const parts = trimmed.split(/\s+/); + if (parts.length >= 2) { + const type = parts.slice(0, -1).join(' '); + const name = parts[parts.length - 1]; + params.push({ name, type, description: '' }); + } else if (parts.length === 1) { + params.push({ name: '', type: parts[0], description: '' }); + } + } + + return params; +} + +/** + * Filter function parameters, removing invalid entries + * Invalid parameters include: empty names or names matching the function name (parsing error) + * @param {Array} params - Raw parameters array + * @param {string} functionName - Name of the function (to detect parsing errors) + * @returns {Array} Filtered and normalized parameters + */ +function filterAndNormalizeParams(params, functionName) { + return (params || []) + .filter(p => { + // Handle different possible data structures + const paramName = (p && (p.name || p.param || p.parameter || '')).trim(); + const paramType = (p && (p.type || p.paramType || '')).trim(); + + // Filter out parameters with empty or missing names + if (!paramName) return false; + // Filter out parameters where name matches function name (indicates parsing error) + if (paramName === functionName) { + if (process.env.DEBUG_PARAMS) { + console.log(`[DEBUG] Filtered out invalid param: name="${paramName}" matches function name`); + } + return false; + } + // Filter out if type is empty AND name looks like it might be a function name (starts with lowercase, no underscore) + if (!paramType && /^[a-z]/.test(paramName) && !paramName.includes('_')) { + if (process.env.DEBUG_PARAMS) { + console.log(`[DEBUG] Filtered out suspicious param: name="${paramName}" has no type`); + } + return false; + } + return true; + }) + .map(p => ({ + name: (p.name || p.param || p.parameter || '').trim(), + type: (p.type || p.paramType || '').trim(), + description: (p.description || p.desc || '').trim(), + })); +} + +/** + * Check if a function is internal by examining its signature + * @param {object} fn - Function data with signature property + * @returns {boolean} True if function is internal + */ +function isInternalFunction(fn) { + if (!fn || !fn.signature) return false; + + // Check if signature contains "internal" as a whole word + // Use word boundary regex to avoid matching "internalTransferFrom" etc. + const internalPattern = /\binternal\b/; + return internalPattern.test(fn.signature); +} + +/** + * Prepare function data for template rendering (shared between facet and module) + * @param {object} fn - Function data + * @param {string} sourceFilePath - Path to the Solidity source file + * @param {boolean} useSourceExtraction - Whether to try extracting params from source file (for modules) + * @returns {object} Prepared function data + */ +function prepareFunctionData(fn, sourceFilePath, useSourceExtraction = false) { + // Debug: log the raw function data + if (process.env.DEBUG_PARAMS) { + console.log(`\n[DEBUG] Function: ${fn.name}`); + console.log(`[DEBUG] Raw params:`, JSON.stringify(fn.params, null, 2)); + console.log(`[DEBUG] Signature:`, fn.signature); + } + + // Build parameters array, filtering out invalid parameters + let paramsArray = filterAndNormalizeParams(fn.params, fn.name); + + // If no valid parameters found, try extracting from source file (for modules) or signature + if (paramsArray.length === 0) { + // Try source file extraction for modules + if (useSourceExtraction && sourceFilePath) { + if (process.env.DEBUG_PARAMS) { + console.log(`[DEBUG] No valid params found, extracting from source file: ${sourceFilePath}`); + } + const extractedParams = extractParamsFromSource(sourceFilePath, fn.name); + if (extractedParams.length > 0) { + paramsArray = extractedParams; + } + } + + // Fallback to signature extraction if still no params + if (paramsArray.length === 0 && fn.signature) { + if (process.env.DEBUG_PARAMS) { + console.log(`[DEBUG] No valid params found, extracting from signature`); + } + const extractedParams = extractParamsFromSignature(fn.signature); + paramsArray = filterAndNormalizeParams(extractedParams, fn.name); + if (process.env.DEBUG_PARAMS) { + console.log(`[DEBUG] Extracted params from signature:`, JSON.stringify(paramsArray, null, 2)); + } + } + } + + if (process.env.DEBUG_PARAMS) { + console.log(`[DEBUG] Final paramsArray:`, JSON.stringify(paramsArray, null, 2)); + } + + // Build returns array for table rendering + const returnsArray = (fn.returns || []).map(r => ({ + name: r.name || '-', + type: r.type, + description: r.description || '', + })); + + return { + name: fn.name, + signature: fn.signature, + description: fn.notice || fn.description || '', + params: paramsArray, + returns: returnsArray, + hasReturns: returnsArray.length > 0, + hasParams: paramsArray.length > 0, + }; +} + +/** + * Prepare event data for template rendering + * @param {object} event - Event data + * @returns {object} Prepared event data + */ +function prepareEventData(event) { + return { + name: event.name, + description: event.description || '', + signature: event.signature, + params: (event.params || []).map(p => ({ + name: p.name, + type: p.type, + description: p.description || '', + })), + hasParams: (event.params || []).length > 0, + }; +} + +/** + * Prepare error data for template rendering + * @param {object} error - Error data + * @returns {object} Prepared error data + */ +function prepareErrorData(error) { + return { + name: error.name, + description: error.description || '', + signature: error.signature, + }; +} + +/** + * Normalize struct definition indentation + * Ensures consistent 4-space indentation for struct body content + * @param {string} definition - Struct definition code + * @returns {string} Normalized struct definition with proper indentation + */ +function normalizeStructIndentation(definition) { + if (!definition) return definition; + + const lines = definition.split('\n'); + if (lines.length === 0) return definition; + + // Find the struct opening line (contains "struct" keyword) + let structStartIndex = -1; + let openingBraceOnSameLine = false; + + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes('struct')) { + structStartIndex = i; + openingBraceOnSameLine = lines[i].includes('{'); + break; + } + } + + if (structStartIndex === -1) return definition; + + // Get the indentation of the struct declaration line + const structLine = lines[structStartIndex]; + const structIndentMatch = structLine.match(/^(\s*)/); + const structIndent = structIndentMatch ? structIndentMatch[1] : ''; + + // Normalize all lines + const normalized = []; + let inStructBody = openingBraceOnSameLine; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + + if (i === structStartIndex) { + // Keep struct declaration line as-is + normalized.push(line); + if (openingBraceOnSameLine) { + inStructBody = true; + } + continue; + } + + // Handle opening brace on separate line + if (!openingBraceOnSameLine && trimmed === '{') { + normalized.push(structIndent + '{'); + inStructBody = true; + continue; + } + + // Handle closing brace + if (trimmed === '}') { + normalized.push(structIndent + '}'); + inStructBody = false; + continue; + } + + // Skip empty lines + if (trimmed === '') { + normalized.push(''); + continue; + } + + // For struct body content, ensure 4-space indentation relative to struct declaration + if (inStructBody) { + // Remove any existing indentation and add proper indentation + const bodyIndent = structIndent + ' '; // 4 spaces + normalized.push(bodyIndent + trimmed); + } else { + // Keep lines outside struct body as-is + normalized.push(line); + } + } + + return normalized.join('\n'); +} + +/** + * Prepare struct data for template rendering + * @param {object} struct - Struct data + * @returns {object} Prepared struct data + */ +function prepareStructData(struct) { + return { + name: struct.name, + description: struct.description || '', + definition: normalizeStructIndentation(struct.definition), + }; +} + +/** + * Validate documentation data + * @param {object} data - Documentation data to validate + * @throws {Error} If data is invalid + */ +function validateData(data) { + if (!data || typeof data !== 'object') { + throw new Error('Invalid data: expected an object'); + } + if (!data.title || typeof data.title !== 'string') { + throw new Error('Invalid data: missing or invalid title'); + } +} + +/** + * Generate fallback description for state variables/constants based on naming patterns + * @param {string} name - Variable name (e.g., "STORAGE_POSITION", "DEFAULT_ADMIN_ROLE") + * @param {string} moduleName - Name of the module/contract for context + * @returns {string} Generated description or empty string + */ +function generateStateVariableDescription(name, moduleName) { + if (!name) return ''; + + const upperName = name.toUpperCase(); + + // Common patterns for diamond/ERC contracts + const patterns = { + // Storage position patterns + 'STORAGE_POSITION': 'Diamond storage slot position for this module', + 'STORAGE_SLOT': 'Diamond storage slot identifier', + '_STORAGE_POSITION': 'Diamond storage slot position', + '_STORAGE_SLOT': 'Diamond storage slot identifier', + + // Role patterns + 'DEFAULT_ADMIN_ROLE': 'Default administrative role identifier (bytes32(0))', + 'ADMIN_ROLE': 'Administrative role identifier', + 'MINTER_ROLE': 'Minter role identifier', + 'PAUSER_ROLE': 'Pauser role identifier', + 'BURNER_ROLE': 'Burner role identifier', + + // ERC patterns + 'INTERFACE_ID': 'ERC-165 interface identifier', + 'EIP712_DOMAIN': 'EIP-712 domain separator', + 'PERMIT_TYPEHASH': 'EIP-2612 permit type hash', + + // Reentrancy patterns + 'NON_REENTRANT_SLOT': 'Reentrancy guard storage slot', + '_NOT_ENTERED': 'Reentrancy status: not entered', + '_ENTERED': 'Reentrancy status: entered', + }; + + // Check exact matches first + if (patterns[upperName]) { + return patterns[upperName]; + } + + // Check partial matches + if (upperName.includes('STORAGE') && (upperName.includes('POSITION') || upperName.includes('SLOT'))) { + return 'Diamond storage slot position for this module'; + } + if (upperName.includes('_ROLE')) { + const roleName = name.replace(/_ROLE$/i, '').replace(/_/g, ' ').toLowerCase(); + return `${roleName.charAt(0).toUpperCase() + roleName.slice(1)} role identifier`; + } + if (upperName.includes('TYPEHASH')) { + return 'Type hash for EIP-712 structured data'; + } + if (upperName.includes('INTERFACE')) { + return 'ERC-165 interface identifier'; + } + + // Generic fallback + return ''; +} + +/** + * Prepare base data common to both facet and module templates + * @param {object} data - Documentation data + * @param {number} position - Sidebar position + * @param {object} [options] - Additional context (contract type, category, etc.) + * @returns {object} Base prepared data + */ +function prepareBaseData(data, position = 99, options = {}) { + validateData(data); + const { contractType, category } = options || {}; + + const description = data.description || `Contract documentation for ${data.title}`; + const subtitle = data.subtitle || data.description || `Contract documentation for ${data.title}`; + const overview = data.overview || data.description || `Documentation for ${data.title}.`; + + // Optional sidebar label override (Facet / Module, or diamond-specific e.g. "Inspect Facet") + const sidebarLabel = getSidebarLabel(contractType, category, data.title); + + return { + position, + title: formatDisplayTitle(data.title), + sidebarLabel: sidebarLabel || null, + description, + subtitle, + overview, + generatedDate: data.generatedDate || new Date().toISOString(), + gitSource: data.gitSource || '', + keyFeatures: data.keyFeatures || '', + usageExample: data.usageExample || '', + bestPractices: (data.bestPractices && data.bestPractices.trim()) ? data.bestPractices : null, + securityConsiderations: (data.securityConsiderations && data.securityConsiderations.trim()) ? data.securityConsiderations : null, + integrationNotes: (data.integrationNotes && data.integrationNotes.trim()) ? data.integrationNotes : null, + storageInfo: data.storageInfo || '', + + // Events + events: (data.events || []).map(prepareEventData), + hasEvents: (data.events || []).length > 0, + + // Errors + errors: (data.errors || []).map(prepareErrorData), + hasErrors: (data.errors || []).length > 0, + + // Structs + structs: (data.structs || []).map(prepareStructData), + hasStructs: (data.structs || []).length > 0, + + // State variables (for modules) - with fallback description generation + stateVariables: (data.stateVariables || []).map(v => { + const baseDescription = v.description || generateStateVariableDescription(v.name, data.title); + let description = baseDescription; + + // Append value to description if it exists and isn't already included + if (v.value && v.value.trim()) { + const valueStr = v.value.trim(); + // Check if value is already in description (case-insensitive) + // Escape special regex characters in valueStr + const escapedValue = valueStr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + // Pattern matches "(Value: `...`)" or "(Value: ...)" format + const valuePattern = new RegExp('\\(Value:\\s*[`]?[^`)]*' + escapedValue + '[^`)]*[`]?\\)', 'i'); + if (!valuePattern.test(description)) { + // Format the value for display with backticks + // Use string concatenation to avoid template literal backtick issues + const valuePart = '(Value: `' + valueStr + '`)'; + description = baseDescription ? baseDescription + ' ' + valuePart : valuePart; + } + } + + return { + name: v.name, + type: v.type || '', + value: v.value || '', + description: description, + }; + }), + hasStateVariables: (data.stateVariables || []).length > 0, + hasStorage: Boolean(data.storageInfo || (data.stateVariables && data.stateVariables.length > 0)), + }; +} + +/** + * Prepare data for facet template rendering + * @param {object} data - Documentation data + * @param {number} position - Sidebar position + * @param {object} pathInfo - Output path information (optional) + * @param {object} registry - Contract registry (optional) + * @returns {object} Prepared data for facet template + */ +function prepareFacetData(data, position = 99, pathInfo = null, registry = null) { + const baseData = prepareBaseData(data, position, { + contractType: 'facet', + category: pathInfo && pathInfo.category, + }); + const sourceFilePath = data.sourceFilePath; + + // Filter out internal functions for facets (they act as pre-deploy logic blocks) + const publicFunctions = (data.functions || []).filter(fn => !isInternalFunction(fn)); + + const preparedData = { + ...baseData, + // Contract type flags for unified template + isFacet: true, + isModule: false, + contractType: 'facet', + // Functions with APIReference-compatible format (no source extraction for facets) + // Only include non-internal functions since facets are pre-deploy logic blocks + functions: publicFunctions.map(fn => prepareFunctionData(fn, sourceFilePath, false)), + hasFunctions: publicFunctions.length > 0, + }; + + // Enrich with relationships if registry and pathInfo provided + if (registry && pathInfo) { + return enrichWithRelationships(preparedData, pathInfo, registry); + } + + return preparedData; +} + +/** + * Prepare data for module template rendering + * @param {object} data - Documentation data + * @param {number} position - Sidebar position + * @param {object} pathInfo - Output path information (optional) + * @param {object} registry - Contract registry (optional) + * @returns {object} Prepared data for module template + */ +function prepareModuleData(data, position = 99, pathInfo = null, registry = null) { + const baseData = prepareBaseData(data, position, { + contractType: 'module', + category: pathInfo && pathInfo.category, + }); + const sourceFilePath = data.sourceFilePath; + + const preparedData = { + ...baseData, + // Contract type flags for unified template + isFacet: false, + isModule: true, + contractType: 'module', + // Functions with table-compatible format (with source extraction for modules) + functions: (data.functions || []).map(fn => prepareFunctionData(fn, sourceFilePath, true)), + hasFunctions: (data.functions || []).length > 0, + }; + + // Enrich with relationships if registry and pathInfo provided + if (registry && pathInfo) { + return enrichWithRelationships(preparedData, pathInfo, registry); + } + + return preparedData; +} + +/** + * Generate complete facet documentation + * Uses the unified contract template with isFacet=true + * @param {object} data - Documentation data + * @param {number} position - Sidebar position + * @param {object} pathInfo - Output path information (optional) + * @param {object} registry - Contract registry (optional) + * @returns {string} Complete MDX document + */ +function generateFacetDoc(data, position = 99, pathInfo = null, registry = null) { + const preparedData = prepareFacetData(data, position, pathInfo, registry); + return loadAndRenderTemplate('contract', preparedData); +} + +/** + * Generate complete module documentation + * Uses the unified contract template with isModule=true + * @param {object} data - Documentation data + * @param {number} position - Sidebar position + * @param {object} pathInfo - Output path information (optional) + * @param {object} registry - Contract registry (optional) + * @returns {string} Complete MDX document + */ +function generateModuleDoc(data, position = 99, pathInfo = null, registry = null) { + const preparedData = prepareModuleData(data, position, pathInfo, registry); + return loadAndRenderTemplate('contract', preparedData); +} + +module.exports = { + generateFacetDoc, + generateModuleDoc, +}; diff --git a/.github/scripts/generate-docs-utils/tracking/summary-tracker.js b/.github/scripts/generate-docs-utils/tracking/summary-tracker.js new file mode 100644 index 00000000..c1d34ee0 --- /dev/null +++ b/.github/scripts/generate-docs-utils/tracking/summary-tracker.js @@ -0,0 +1,134 @@ +/** + * Summary Tracker + * + * Tracks processing results and generates summary reports. + * Replaces global processedFiles object with a class-based approach. + */ + +const path = require('path'); +const { writeFileSafe } = require('../../workflow-utils'); + +/** + * Tracks processed files and generates summary reports + */ +class SummaryTracker { + constructor() { + this.facets = []; + this.modules = []; + this.skipped = []; + this.errors = []; + this.fallbackFiles = []; + } + + /** + * Record a successfully processed facet + * @param {string} title - Facet title + * @param {string} file - Output file path + */ + recordFacet(title, file) { + this.facets.push({ title, file }); + } + + /** + * Record a successfully processed module + * @param {string} title - Module title + * @param {string} file - Output file path + */ + recordModule(title, file) { + this.modules.push({ title, file }); + } + + /** + * Record a skipped file + * @param {string} file - File path + * @param {string} reason - Reason for skipping + */ + recordSkipped(file, reason) { + this.skipped.push({ file, reason }); + } + + /** + * Record an error + * @param {string} file - File path + * @param {string} error - Error message + */ + recordError(file, error) { + this.errors.push({ file, error }); + } + + /** + * Record a file that used fallback content + * @param {string} title - Contract title + * @param {string} file - Output file path + * @param {string} error - Error message + */ + recordFallback(title, file, error) { + this.fallbackFiles.push({ title, file, error }); + } + + /** + * Print processing summary to console + */ + printSummary() { + console.log('\n' + '='.repeat(50)); + console.log('Documentation Generation Summary'); + console.log('='.repeat(50)); + + console.log(`\nFacets generated: ${this.facets.length}`); + for (const f of this.facets) { + console.log(` - ${f.title}`); + } + + console.log(`\nModules generated: ${this.modules.length}`); + for (const m of this.modules) { + console.log(` - ${m.title}`); + } + + if (this.skipped.length > 0) { + console.log(`\nSkipped: ${this.skipped.length}`); + for (const s of this.skipped) { + console.log(` - ${path.basename(s.file)}: ${s.reason}`); + } + } + + if (this.errors.length > 0) { + console.log(`\nErrors: ${this.errors.length}`); + for (const e of this.errors) { + console.log(` - ${path.basename(e.file)}: ${e.error}`); + } + } + + if (this.fallbackFiles.length > 0) { + console.log(`\n⚠️ Files using fallback due to AI errors: ${this.fallbackFiles.length}`); + for (const f of this.fallbackFiles) { + console.log(` - ${f.title}: ${f.error}`); + } + } + + const total = this.facets.length + this.modules.length; + console.log(`\nTotal generated: ${total} documentation files`); + console.log('='.repeat(50) + '\n'); + } + + /** + * Write summary to file for GitHub Action + */ + writeSummaryFile() { + const summary = { + timestamp: new Date().toISOString(), + facets: this.facets, + modules: this.modules, + skipped: this.skipped, + errors: this.errors, + fallbackFiles: this.fallbackFiles, + totalGenerated: this.facets.length + this.modules.length, + }; + + writeFileSafe('docgen-summary.json', JSON.stringify(summary, null, 2)); + } +} + +module.exports = { + SummaryTracker, +}; + diff --git a/.github/scripts/generate-docs-utils/utils/contract-classifier.js b/.github/scripts/generate-docs-utils/utils/contract-classifier.js new file mode 100644 index 00000000..698df50a --- /dev/null +++ b/.github/scripts/generate-docs-utils/utils/contract-classifier.js @@ -0,0 +1,69 @@ +/** + * Contract Classifier + * + * Functions for detecting contract types (interface, module, facet). + */ + +const path = require('path'); + +/** + * Determine if a contract is an interface + * Interfaces should be skipped from documentation generation + * Only checks the naming pattern (I[A-Z]) to avoid false positives + * @param {string} title - Contract title/name + * @param {string} content - File content (forge doc markdown) - unused but kept for API compatibility + * @returns {boolean} True if this is an interface + */ +function isInterface(title, content) { + // Only check if title follows interface naming convention: starts with "I" followed by uppercase + // This is the most reliable indicator and avoids false positives from content that mentions "interface" + if (title && /^I[A-Z]/.test(title)) { + return true; + } + + // Removed content-based check to avoid false positives + // Facets and contracts often mention "interface" in their descriptions + // (e.g., "ERC-165 Standard Interface Detection Facet") which would incorrectly filter them + + return false; +} + +/** + * Determine if a contract is a module or facet + * @param {string} filePath - Path to the file + * @param {string} content - File content + * @returns {'module' | 'facet'} Contract type + */ +function getContractType(filePath, content) { + const lowerPath = filePath.toLowerCase(); + const normalizedPath = lowerPath.replace(/\\/g, '/'); + const baseName = path.basename(filePath, path.extname(filePath)).toLowerCase(); + + // Explicit modules folder + if (normalizedPath.includes('/modules/')) { + return 'module'; + } + + // File naming conventions (e.g., AccessControlMod.sol, NonReentrancyModule.sol) + if (baseName.endsWith('mod') || baseName.endsWith('module')) { + return 'module'; + } + + if (lowerPath.includes('facet')) { + return 'facet'; + } + + // Libraries folder typically contains modules + if (normalizedPath.includes('/libraries/')) { + return 'module'; + } + + // Default to facet for contracts + return 'facet'; +} + +module.exports = { + isInterface, + getContractType, +}; + diff --git a/.github/scripts/generate-docs-utils/utils/file-finder.js b/.github/scripts/generate-docs-utils/utils/file-finder.js new file mode 100644 index 00000000..6ee50dd2 --- /dev/null +++ b/.github/scripts/generate-docs-utils/utils/file-finder.js @@ -0,0 +1,38 @@ +/** + * File Finder + * + * Functions for finding forge doc output files. + */ + +const fs = require('fs'); +const path = require('path'); +const CONFIG = require('../config'); + +/** + * Find forge doc output files for a given source file + * @param {string} solFilePath - Path to .sol file (e.g., 'src/access/AccessControl/AccessControlMod.sol') + * @returns {string[]} Array of markdown file paths from forge doc output + */ +function findForgeDocFiles(solFilePath) { + // Transform: src/access/AccessControl/AccessControlMod.sol + // To: docs/src/src/access/AccessControl/AccessControlMod.sol/ + const relativePath = solFilePath.replace(/^src\//, ''); + const docsDir = path.join(CONFIG.forgeDocsDir, relativePath); + + if (!fs.existsSync(docsDir)) { + return []; + } + + try { + const files = fs.readdirSync(docsDir); + return files.filter((f) => f.endsWith('.md')).map((f) => path.join(docsDir, f)); + } catch (error) { + console.error(`Error reading docs dir ${docsDir}:`, error.message); + return []; + } +} + +module.exports = { + findForgeDocFiles, +}; + diff --git a/.github/scripts/generate-docs-utils/utils/git-utils.js b/.github/scripts/generate-docs-utils/utils/git-utils.js new file mode 100644 index 00000000..7f905f0a --- /dev/null +++ b/.github/scripts/generate-docs-utils/utils/git-utils.js @@ -0,0 +1,70 @@ +/** + * Git Utilities + * + * Functions for interacting with git to find changed files. + */ + +const { execSync } = require('child_process'); +const { readFileSafe } = require('../../workflow-utils'); + +/** + * Get list of changed Solidity files from git diff + * @param {string} baseBranch - Base branch to compare against + * @returns {string[]} Array of changed .sol file paths + */ +function getChangedSolFiles(baseBranch = 'HEAD~1') { + try { + const output = execSync(`git diff --name-only ${baseBranch} HEAD -- 'src/**/*.sol'`, { + encoding: 'utf8', + }); + return output + .trim() + .split('\n') + .filter((f) => f.endsWith('.sol')); + } catch (error) { + console.error('Error getting changed files:', error.message); + return []; + } +} + +/** + * Get all Solidity files in src directory + * @returns {string[]} Array of .sol file paths + */ +function getAllSolFiles() { + try { + const output = execSync('find src -name "*.sol" -type f', { + encoding: 'utf8', + }); + return output + .trim() + .split('\n') + .filter((f) => f); + } catch (error) { + console.error('Error getting all sol files:', error.message); + return []; + } +} + +/** + * Read changed files from a file (used in CI) + * @param {string} filePath - Path to file containing list of changed files + * @returns {string[]} Array of file paths + */ +function readChangedFilesFromFile(filePath) { + const content = readFileSafe(filePath); + if (!content) { + return []; + } + return content + .trim() + .split('\n') + .filter((f) => f.endsWith('.sol')); +} + +module.exports = { + getChangedSolFiles, + getAllSolFiles, + readChangedFilesFromFile, +}; + diff --git a/.github/scripts/generate-docs-utils/utils/path-computer.js b/.github/scripts/generate-docs-utils/utils/path-computer.js new file mode 100644 index 00000000..cdb6ad4c --- /dev/null +++ b/.github/scripts/generate-docs-utils/utils/path-computer.js @@ -0,0 +1,33 @@ +/** + * Path Computer + * + * Functions for computing output paths for documentation files. + */ + +const { + computeOutputPath, + ensureCategoryFiles, +} = require('../category/category-generator'); + +/** + * Get output directory and file path based on source file path + * Mirrors the src/ structure in website/docs/contracts/ + * + * @param {string} solFilePath - Path to the source .sol file + * @param {'module' | 'facet'} contractType - Type of contract (for logging) + * @returns {object} { outputDir, outputFile, relativePath, fileName, category } + */ +function getOutputPath(solFilePath, contractType) { + // Compute path using the new structure-mirroring logic + const pathInfo = computeOutputPath(solFilePath); + + // Ensure all parent category files exist + ensureCategoryFiles(pathInfo.outputDir); + + return pathInfo; +} + +module.exports = { + getOutputPath, +}; + diff --git a/.github/scripts/generate-docs-utils/utils/sidebar-position-calculator.js b/.github/scripts/generate-docs-utils/utils/sidebar-position-calculator.js new file mode 100644 index 00000000..d14d9a19 --- /dev/null +++ b/.github/scripts/generate-docs-utils/utils/sidebar-position-calculator.js @@ -0,0 +1,66 @@ +/** + * Sidebar Position Calculator + * + * Calculates sidebar positions for contracts in documentation. + */ + +const CONFIG = require('../config'); +const { getContractRegistry } = require('../core/contract-registry'); + +/** + * Get sidebar position for a contract + * @param {string} contractName - Name of the contract + * @param {string} contractType - Type of contract ('module' or 'facet') + * @param {string} category - Category of the contract + * @param {object} registry - Contract registry (optional, uses global if not provided) + * @returns {number} Sidebar position + */ +function getSidebarPosition(contractName, contractType = null, category = null, registry = null) { + // First check explicit config + if (CONFIG.contractPositions && CONFIG.contractPositions[contractName] !== undefined) { + return CONFIG.contractPositions[contractName]; + } + + // If we don't have enough info, use default + if (!contractType || !category) { + return CONFIG.defaultSidebarPosition || 50; + } + + // Calculate smart position based on: + // 1. Category base offset + const categoryOffsets = { + diamond: 0, + access: 100, + token: 200, + utils: 300, + interfaceDetection: 400 + }; + + let basePosition = categoryOffsets[category] || 500; + + // 2. Contract type offset (facets before modules in sidebar) + const typeOffset = contractType === 'facet' ? 0 : 10; + basePosition += typeOffset; + + // 3. Position within category based on dependencies + const reg = registry || getContractRegistry(); + if (reg && reg.byCategory.has(category)) { + const categoryContracts = reg.byCategory.get(category) || []; + const sameTypeContracts = categoryContracts.filter(c => c.type === contractType); + + // Sort by name for consistent ordering + sameTypeContracts.sort((a, b) => a.name.localeCompare(b.name)); + + const index = sameTypeContracts.findIndex(c => c.name === contractName); + if (index !== -1) { + basePosition += index; + } + } + + return basePosition; +} + +module.exports = { + getSidebarPosition, +}; + diff --git a/.github/scripts/generate-docs-utils/utils/source-parser.js b/.github/scripts/generate-docs-utils/utils/source-parser.js new file mode 100644 index 00000000..89b588f8 --- /dev/null +++ b/.github/scripts/generate-docs-utils/utils/source-parser.js @@ -0,0 +1,174 @@ +/** + * Source Parser + * + * Functions for parsing Solidity source files to extract information. + */ + +const path = require('path'); +const { readFileSafe } = require('../../workflow-utils'); + +/** + * Extract module name from file path + * @param {string} filePath - Path to the file + * @returns {string} Module name + */ +function extractModuleNameFromPath(filePath) { + // If it's a constants file, extract from filename + const basename = path.basename(filePath); + if (basename.startsWith('constants.')) { + const match = basename.match(/^constants\.(.+)\.md$/); + if (match) { + return match[1]; + } + } + + // Extract from .sol file path + if (filePath.endsWith('.sol')) { + return path.basename(filePath, '.sol'); + } + + // Extract from directory structure + const parts = filePath.split(path.sep); + for (let i = parts.length - 1; i >= 0; i--) { + if (parts[i].endsWith('.sol')) { + return path.basename(parts[i], '.sol'); + } + } + + // Fallback: use basename without extension + return path.basename(filePath, path.extname(filePath)); +} + +/** + * Check if a line is a code element declaration + * @param {string} line - Trimmed line to check + * @returns {boolean} True if line is a code element declaration + */ +function isCodeElementDeclaration(line) { + if (!line) return false; + return ( + line.startsWith('function ') || + line.startsWith('error ') || + line.startsWith('event ') || + line.startsWith('struct ') || + line.startsWith('enum ') || + line.startsWith('contract ') || + line.startsWith('library ') || + line.startsWith('interface ') || + line.startsWith('modifier ') || + /^\w+\s+(constant|immutable)\s/.test(line) || + /^(bytes32|uint\d*|int\d*|address|bool|string)\s+constant\s/.test(line) + ); +} + +/** + * Extract module description from source file NatSpec comments + * @param {string} solFilePath - Path to the Solidity source file + * @returns {string} Description extracted from @title and @notice tags + */ +function extractModuleDescriptionFromSource(solFilePath) { + const content = readFileSafe(solFilePath); + if (!content) { + return ''; + } + + const lines = content.split('\n'); + let inComment = false; + let commentBuffer = []; + let title = ''; + let notice = ''; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + + // Skip SPDX and pragma lines + if (trimmed.startsWith('// SPDX') || trimmed.startsWith('pragma ')) { + continue; + } + + // Check if we've reached a code element without finding a file-level comment + if (!inComment && isCodeElementDeclaration(trimmed)) { + break; + } + + // Start of block comment + if (trimmed.startsWith('/**') || trimmed.startsWith('/*')) { + inComment = true; + commentBuffer = []; + continue; + } + + // End of block comment + if (inComment && trimmed.includes('*/')) { + inComment = false; + const commentText = commentBuffer.join(' '); + + // Look ahead to see if next non-empty line is a code element + let nextCodeLine = ''; + for (let j = i + 1; j < lines.length && j < i + 5; j++) { + const nextTrimmed = lines[j].trim(); + if (nextTrimmed && !nextTrimmed.startsWith('//') && !nextTrimmed.startsWith('/*')) { + nextCodeLine = nextTrimmed; + break; + } + } + + // If the comment has @title, it's a file-level comment + const titleMatch = commentText.match(/@title\s+(.+?)(?:\s+@|\s*$)/); + if (titleMatch) { + title = titleMatch[1].trim(); + const noticeMatch = commentText.match(/@notice\s+(.+?)(?:\s+@|\s*$)/); + if (noticeMatch) { + notice = noticeMatch[1].trim(); + } + break; + } + + // If next line is a code element, this comment belongs to that element + if (isCodeElementDeclaration(nextCodeLine)) { + commentBuffer = []; + continue; + } + + // Standalone comment with @notice + const standaloneNotice = commentText.match(/@notice\s+(.+?)(?:\s+@|\s*$)/); + if (standaloneNotice && !isCodeElementDeclaration(nextCodeLine)) { + notice = standaloneNotice[1].trim(); + break; + } + + commentBuffer = []; + continue; + } + + // Collect comment lines + if (inComment) { + let cleanLine = trimmed + .replace(/^\*\s*/, '') + .replace(/^\s*\*/, '') + .trim(); + if (cleanLine && !cleanLine.startsWith('*/')) { + commentBuffer.push(cleanLine); + } + } + } + + // Combine title and notice + if (title && notice) { + return `${title} - ${notice}`; + } else if (notice) { + return notice; + } else if (title) { + return title; + } + + return ''; +} + +module.exports = { + extractModuleNameFromPath, + extractModuleDescriptionFromSource, + isCodeElementDeclaration, +}; + diff --git a/.github/scripts/generate-docs.js b/.github/scripts/generate-docs.js new file mode 100644 index 00000000..50245f4f --- /dev/null +++ b/.github/scripts/generate-docs.js @@ -0,0 +1,82 @@ +/** + * Docusaurus Documentation Generator + * + * Converts forge doc output to Docusaurus MDX format + * with optional AI enhancement. + * + * Features: + * - Mirrors src/ folder structure in documentation + * - Auto-generates category navigation files + * - AI-enhanced content generation + * + * Environment variables: + * GITHUB_TOKEN - GitHub token for AI API (optional) + * SKIP_ENHANCEMENT - Set to 'true' to skip AI enhancement + */ + +const { clearContractRegistry } = require('./generate-docs-utils/core/contract-registry'); +const { syncDocsStructure, regenerateAllIndexFiles } = require('./generate-docs-utils/category/category-generator'); +const { processSolFile } = require('./generate-docs-utils/core/file-processor'); +const { getFilesToProcess } = require('./generate-docs-utils/core/file-selector'); +const { SummaryTracker } = require('./generate-docs-utils/tracking/summary-tracker'); + + +// ============================================================================ +// Main Entry Point +// ============================================================================ + +/** + * Main entry point + */ +async function main() { + console.log('Compose Documentation Generator\n'); + + // Initialize tracker + const tracker = new SummaryTracker(); + + // Step 0: Clear contract registry + clearContractRegistry(); + + // Step 1: Sync docs structure with src structure + console.log('📁 Syncing documentation structure with source...'); + const syncResult = syncDocsStructure(); + + if (syncResult.created.length > 0) { + console.log(` Created ${syncResult.created.length} new categories:`); + syncResult.created.forEach((c) => console.log(` ✅ ${c}`)); + } + console.log(` Total categories: ${syncResult.total}\n`); + + // Step 2: Determine which files to process + const args = process.argv.slice(2); + const solFiles = getFilesToProcess(args); + + if (solFiles.length === 0) { + console.log('No Solidity files to process'); + return; + } + + console.log(`Found ${solFiles.length} Solidity file(s) to process\n`); + + // Step 3: Process each file + for (const solFile of solFiles) { + await processSolFile(solFile, tracker); + } + + // Step 4: Regenerate all index pages now that docs are created + console.log('📄 Regenerating category index pages...'); + const indexResult = regenerateAllIndexFiles(true); + if (indexResult.regenerated.length > 0) { + console.log(` Regenerated ${indexResult.regenerated.length} index pages`); + } + console.log(''); + + // Step 5: Print summary + tracker.printSummary(); + tracker.writeSummaryFile(); +} + +main().catch((error) => { + console.error(`Fatal error: ${error}`); + process.exit(1); +}); 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/.github/workflows/docs-generate.yml b/.github/workflows/docs-generate.yml new file mode 100644 index 00000000..b4357737 --- /dev/null +++ b/.github/workflows/docs-generate.yml @@ -0,0 +1,183 @@ +name: Generate Docs + +on: + workflow_dispatch: + inputs: + target_file: + description: 'Process ONLY the specified Solidity file(s) (relative path, e.g. src/contracts/MyFacet.sol or src/facets/A.sol,src/facets/B.sol)' + required: false + type: string + process_all: + description: 'Process ALL Solidity files' + required: false + default: false + type: boolean + skip_enhancement: + description: 'Skip AI Documentation Enhancement' + required: false + default: false + type: boolean + +permissions: + contents: write + pull-requests: write + models: read # Required for GitHub Models API (AI enhancement) + +jobs: + generate-docs: + name: Generate Pages + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Get changed Solidity files + id: changed-files + run: | + # Prefer explicit target_file when provided via manual dispatch. + # You can pass a single file or a comma/space-separated list, e.g.: + # src/facets/A.sol,src/facets/B.sol + # src/facets/A.sol src/facets/B.sol + if [ -n "${{ github.event.inputs.target_file }}" ]; then + echo "Processing Solidity file(s) from input:" + echo "${{ github.event.inputs.target_file }}" + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "process_all=false" >> $GITHUB_OUTPUT + # Normalize comma/space-separated list into one file path per line + echo "${{ github.event.inputs.target_file }}" \ + | tr ',' '\n' \ + | tr ' ' '\n' \ + | sed '/^$/d' \ + > /tmp/changed_sol_files.txt + elif [ "${{ github.event.inputs.process_all }}" == "true" ]; then + echo "Processing all Solidity files (manual trigger)" + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "process_all=true" >> $GITHUB_OUTPUT + else + # Get list of changed .sol files compared to previous commit + CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD -- 'src/**/*.sol' 2>/dev/null || echo "") + + if [ -z "$CHANGED_FILES" ]; then + echo "No Solidity files changed" + echo "has_changes=false" >> $GITHUB_OUTPUT + else + echo "Changed files:" + echo "$CHANGED_FILES" + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "process_all=false" >> $GITHUB_OUTPUT + + # Save to file for script + echo "$CHANGED_FILES" > /tmp/changed_sol_files.txt + fi + fi + + - name: Setup Node.js + if: steps.changed-files.outputs.has_changes == 'true' + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Foundry + if: steps.changed-files.outputs.has_changes == 'true' + uses: foundry-rs/foundry-toolchain@v1 + + - name: Generate forge documentation + if: steps.changed-files.outputs.has_changes == 'true' + run: forge doc + + - name: Install template dependencies + if: steps.changed-files.outputs.has_changes == 'true' + working-directory: .github/scripts/generate-docs-utils/templates + run: npm install + + - name: Run documentation generator + if: steps.changed-files.outputs.has_changes == 'true' + env: + # AI Provider Configuration + GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SKIP_ENHANCEMENT: ${{ github.event.inputs.skip_enhancement || 'false' }} + run: | + if [ "${{ steps.changed-files.outputs.process_all }}" == "true" ]; then + node .github/scripts/generate-docs.js --all + else + node .github/scripts/generate-docs.js /tmp/changed_sol_files.txt + fi + + - name: Check for generated files + if: steps.changed-files.outputs.has_changes == 'true' + id: check-generated + run: | + # Check if any files were generated + if [ -f "docgen-summary.json" ]; then + TOTAL=$(cat docgen-summary.json | jq -r '.totalGenerated // 0' 2>/dev/null || echo "0") + if [ -n "$TOTAL" ] && [ "$TOTAL" -gt "0" ]; then + echo "has_generated=true" >> $GITHUB_OUTPUT + echo "Generated $TOTAL documentation files" + else + echo "has_generated=false" >> $GITHUB_OUTPUT + echo "No documentation files generated" + fi + else + echo "has_generated=false" >> $GITHUB_OUTPUT + fi + + - name: Verify documentation site build + if: steps.check-generated.outputs.has_generated == 'true' + working-directory: website + run: | + npm ci + npm run build + env: + ALGOLIA_APP_ID: 'dummy' + ALGOLIA_API_KEY: 'dummy' + ALGOLIA_INDEX_NAME: 'dummy' + POSTHOG_API_KEY: 'dummy' + continue-on-error: false + + - name: Generate PR body + if: steps.check-generated.outputs.has_generated == 'true' + id: pr-body + run: | + node .github/scripts/generate-docs-utils/pr-body-generator.js docgen-summary.json >> $GITHUB_OUTPUT + + - name: Clean up tmp files and stage website pages + if: steps.check-generated.outputs.has_generated == 'true' + run: | + # Remove forge docs folder (if it exists) + if [ -d "docs" ]; then + rm -rf docs + fi + + # Remove summary file (if it exists) + if [ -f "docgen-summary.json" ]; then + rm -f docgen-summary.json + fi + + # Reset any staged changes + git reset + + # Only stage website documentation files (force add in case they're ignored) + # Use library directory (the actual output directory) instead of contracts + if [ -d "website/docs/library" ]; then + git add -f website/docs/library/ + fi + + - name: Create Pull Request + if: steps.check-generated.outputs.has_generated == 'true' + uses: peter-evans/create-pull-request@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + title: '[DOCS] Auto-generated Docs Pages' + commit-message: 'docs: auto-generate docs pages from NatSpec' + branch: docs/auto-generated-${{ github.run_number }} + body: ${{ steps.pr-body.outputs.body }} + labels: | + documentation + auto-generated + delete-branch: true + draft: true diff --git a/.gitignore b/.gitignore index 42f88272..521146a4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,24 +13,10 @@ out/ # Docusaurus # Dependencies -docs/node_modules - -# Production -docs/build - -# Generated files -docs/.docusaurus -docs/.cache-loader - -# Misc -docs/.DS_Store -docs/.env -docs/.env.local -docs/.env.development.local -docs/.env.test.local -docs/.env.production.local - -docs/npm-debug.log* -docs/yarn-debug.log* -docs/yarn-error.log* +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/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 64c8af64..5283da54 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. @@ -51,7 +52,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 4859a2e3..de081ef2 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. @@ -60,7 +61,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 b16802e2..32926f3b 100644 --- a/website/docs/getting-started/installation.md +++ b/website/docs/getting-started/installation.md @@ -2,6 +2,8 @@ sidebar_position: 1 --- +import Callout from '@site/src/components/ui/Callout'; + # Installation Get up and running with Compose in just a few minutes. @@ -123,7 +125,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 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 index 318fd0e6..3e3b4956 100644 --- a/website/docs/getting-started/quick-start.md +++ b/website/docs/getting-started/quick-start.md @@ -3,6 +3,8 @@ sidebar_position: 2 draft: true --- +import ExpandableCode from '@site/src/components/code/ExpandableCode'; + # Quick Start Let's build your first diamond using Compose facets in under 5 minutes! 🚀 @@ -52,8 +54,8 @@ contract MyTokenDiamond is Diamond { Create `script/DeployMyDiamond.s.sol`: -```solidity -// SPDX-License-Identifier: MIT + +{`// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Script} from "forge-std/Script.sol"; @@ -108,8 +110,8 @@ contract DeployMyDiamond is Script { console.log("Diamond deployed at:", address(diamond)); console.log("ERC20Facet deployed at:", address(erc20Facet)); } -} -``` +}`} + ## Step 4: Create Initialization Facet @@ -150,8 +152,8 @@ contract TokenInitFacet { Create `test/MyTokenDiamond.t.sol`: -```solidity -// SPDX-License-Identifier: MIT + +{`// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; @@ -201,8 +203,8 @@ contract MyTokenDiamondTest is Test { // Assert assertEq(token.balanceOf(user2), 50 ether); } -} -``` +}`} + ## Step 6: Run and Deploy diff --git a/website/docs/getting-started/your-first-diamond.md b/website/docs/getting-started/your-first-diamond.md index 73b91dbb..2a87a5a5 100644 --- a/website/docs/getting-started/your-first-diamond.md +++ b/website/docs/getting-started/your-first-diamond.md @@ -3,6 +3,9 @@ sidebar_position: 3 draft: true --- +import Callout from '@site/src/components/ui/Callout'; +import ExpandableCode from '@site/src/components/code/ExpandableCode'; + # Your First Diamond In this guide, you'll learn how to create a diamond from scratch and understand every piece of the architecture. @@ -251,8 +254,8 @@ The diamond uses these selectors to route calls to the correct facet. Here's a complete deployment script: -```solidity -// SPDX-License-Identifier: MIT + +{`// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "forge-std/Script.sol"; @@ -321,8 +324,8 @@ contract DeployDiamond is Script { selectors[8] = ERC20Facet.transferFrom.selector; return selectors; } -} -``` +}`} + ## Testing Your Diamond @@ -365,7 +368,7 @@ You now understand how to build a diamond from scratch! Continue learning: - **[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/access/AccessControl/Admin/AccessControlAdminFacet.mdx b/website/docs/library/access/AccessControl/Admin/AccessControlAdminFacet.mdx new file mode 100644 index 00000000..a5de6c8e --- /dev/null +++ b/website/docs/library/access/AccessControl/Admin/AccessControlAdminFacet.mdx @@ -0,0 +1,231 @@ +--- +sidebar_position: 100 +title: "Access Control Admin Facet" +description: "Manages roles and their administrative roles" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Admin/AccessControlAdminFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages roles and their administrative roles + + + +- Manages role-to-admin-role mappings in diamond storage. +- Exposes functions for role administration and selector export. +- Reverts with `AccessControlUnauthorizedAccount` if caller lacks permissions. +- Compatible with ERC-2535 diamond standard. + + +## Overview + +This facet provides administrative functions for managing roles within a diamond's access control system. It allows setting administrative roles for specific roles and exporting the facet's selectors. Calls are routed through the diamond proxy, accessing shared storage for role configurations. + +## Storage + +### AccessControlStorage + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### setRoleAdmin + +Sets the admin role for a role. Emits a RoleAdminChanged event. Reverts with AccessControlUnauthorizedAccount If the caller is not the current admin of the role. + + +{`function setRoleAdmin(bytes32 _role, bytes32 _adminRole) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the selectors that are exposed by the facet. + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when the admin role for a role is changed. +
+ +
+ Signature: + +{`event RoleAdminChanged(bytes32 indexed _role, bytes32 indexed _previousAdminRole, bytes32 indexed _newAdminRole);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Initialize role admin configurations during diamond deployment. +- Ensure the caller has the necessary permissions to set role administrators. +- Verify storage compatibility before upgrading facets to prevent state corruption. + + +## Security Considerations + + +All state-changing functions, such as `setRoleAdmin`, are protected by access control checks, reverting with `AccessControlUnauthorizedAccount` if the caller is not the current admin of the role. Input validation is performed by the underlying access control logic. Follow standard Solidity security practices for external calls and state management. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Admin/AccessControlAdminMod.mdx b/website/docs/library/access/AccessControl/Admin/AccessControlAdminMod.mdx new file mode 100644 index 00000000..caf2284d --- /dev/null +++ b/website/docs/library/access/AccessControl/Admin/AccessControlAdminMod.mdx @@ -0,0 +1,235 @@ +--- +sidebar_position: 110 +title: "Access Control Admin Module" +description: "Manage role administrators using diamond storage" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Admin/AccessControlAdminMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manage role administrators using diamond storage + + + +- Internal functions for role administration. +- Uses the diamond storage pattern for shared state. +- Emits `RoleAdminChanged` event upon successful administration changes. +- Reverts with `AccessControlUnauthorizedAccount` for unauthorized calls. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for managing role administrators within a diamond. Facets can import this module to set and query role administration relationships, leveraging shared diamond storage. Changes are immediately visible to all facets accessing the same storage. + +## Storage + +### AccessControlStorage + +Storage struct for the AccessControl. storage-location: erc8042:compose.accesscontrol + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the storage for the AccessControl. + + +{`function getStorage() pure returns (AccessControlStorage storage s);`} + + +**Returns:** + + + +--- +### setRoleAdmin + +Sets the admin role for a role. Emits a {RoleAdminChanged} event. Reverts with AccessControlUnauthorizedAccount If the caller is not the current admin of the role. + + +{`function setRoleAdmin(bytes32 _role, bytes32 _adminRole) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when the admin role for a role is changed. +
+ +
+ Signature: + +{`event RoleAdminChanged(bytes32 indexed _role, bytes32 indexed _previousAdminRole, bytes32 indexed _newAdminRole);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Ensure the caller has the necessary permissions before calling `setRoleAdmin`. +- Verify that the `AccessControlStorage` struct layout remains compatible across diamond upgrades. +- Handle the `AccessControlUnauthorizedAccount` error when the caller lacks administrative privileges. + + +## Integration Notes + + +This module interacts with the diamond's shared storage at the position identified by `keccak2535("compose.accesscontrol")`. The `AccessControlStorage` struct, though empty in definition, dictates the layout for access control data. Any changes made to role administrators via `setRoleAdmin` are immediately reflected in this shared storage and thus visible to all facets operating on the same diamond storage. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Admin/_category_.json b/website/docs/library/access/AccessControl/Admin/_category_.json new file mode 100644 index 00000000..cd6e04f6 --- /dev/null +++ b/website/docs/library/access/AccessControl/Admin/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Admin", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/Admin/index" + } +} diff --git a/website/docs/library/access/AccessControl/Admin/index.mdx b/website/docs/library/access/AccessControl/Admin/index.mdx new file mode 100644 index 00000000..fb44c714 --- /dev/null +++ b/website/docs/library/access/AccessControl/Admin/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Admin" +description: "Admin components for Compose 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'; + + + Admin components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/AccessControl/Batch/Grant/AccessControlGrantBatchFacet.mdx b/website/docs/library/access/AccessControl/Batch/Grant/AccessControlGrantBatchFacet.mdx new file mode 100644 index 00000000..bf4dba26 --- /dev/null +++ b/website/docs/library/access/AccessControl/Batch/Grant/AccessControlGrantBatchFacet.mdx @@ -0,0 +1,219 @@ +--- +sidebar_position: 100 +title: "Access Control Grant Batch Facet" +description: "Grants roles to multiple accounts in batch" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Batch/Grant/AccessControlGrantBatchFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Grants roles to multiple accounts in batch + + + +- Grants roles to multiple accounts in a single atomic operation. +- Emits `RoleGranted` events for each account granted a role. +- Uses diamond storage for role management, ensuring state consistency across facets. +- Provides `exportSelectors` for easy integration into diamond upgrade processes. + + +## Overview + +This facet exposes an external function to grant a specific role to multiple accounts efficiently within a single transaction. It enhances the diamond's access control capabilities by reducing gas costs and complexity for bulk role assignments. Calls are routed through the diamond proxy, interacting with shared diamond storage. + +## Storage + +### AccessControlStorage + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### grantRoleBatch + +Grants a role to multiple accounts in a single transaction. Emits a RoleGranted event for each newly granted account. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function grantRoleBatch(bytes32 _role, address[] calldata _accounts) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the selectors that are exposed by the facet. + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when a role is granted to an account. +
+ +
+ Signature: + +{`event RoleGranted(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Ensure the caller has the necessary administrative privileges to grant the specified role before invoking `grantRoleBatch`. +- Verify that the `AccessControlGrantBatchFacet` is correctly added to the diamond with the appropriate selectors. +- Consider the gas implications for very large `_accounts` arrays; for extremely large lists, multiple calls might be more gas-efficient. + + +## Security Considerations + + +The `grantRoleBatch` function is protected by access control, reverting with `AccessControlUnauthorizedAccount` if the caller does not possess the required administrative role for the target role. Input validation is performed by the underlying access control mechanism. Follow standard Solidity security practices for managing roles and account permissions. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Batch/Grant/AccessControlGrantBatchMod.mdx b/website/docs/library/access/AccessControl/Batch/Grant/AccessControlGrantBatchMod.mdx new file mode 100644 index 00000000..3a8f1382 --- /dev/null +++ b/website/docs/library/access/AccessControl/Batch/Grant/AccessControlGrantBatchMod.mdx @@ -0,0 +1,232 @@ +--- +sidebar_position: 110 +title: "Access Control Grant Batch Module" +description: "Grant roles to multiple accounts efficiently" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Batch/Grant/AccessControlGrantBatchMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Grant roles to multiple accounts efficiently + + + +- Internal functions for composability within facets. +- Efficient batch granting of roles to multiple accounts. +- Utilizes diamond storage for shared state management. +- No external dependencies, promoting a self-contained design. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides an internal function for granting roles to multiple accounts in a single transaction, reducing gas costs and complexity. Facets can import this module to manage role assignments using shared diamond storage. Changes made through this module are immediately visible to all facets interacting with the same access control storage. + +## Storage + +### AccessControlStorage + +storage struct for the AccessControl. storage-location: erc8042:compose.accesscontrol + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the storage for the AccessControl. + + +{`function getStorage() pure returns (AccessControlStorage storage _s);`} + + +**Returns:** + + + +--- +### grantRoleBatch + +function to grant a role to multiple accounts in a single transaction. Emits a {RoleGranted} event for each newly granted account. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function grantRoleBatch(bytes32 _role, address[] calldata _accounts) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when a role is granted to an account. +
+ +
+ Signature: + +{`event RoleGranted(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Ensure the caller has the necessary permissions to grant roles before invoking `grantRoleBatch`. +- Verify that the `AccessControlStorage` struct definition in your diamond is compatible with this module. +- Handle the `AccessControlUnauthorizedAccount` error, which is reverted if the caller lacks the required role. + + +## Integration Notes + + +This module interacts with diamond storage at the position identified by `keccak2535(\"compose.accesscontrol\")`. The `AccessControlStorage` struct is accessed and modified by the `grantRoleBatch` function. Any updates to role assignments are immediately reflected across all facets that read from this shared storage position. The `getStorage` function provides direct access to this storage for inspection or compatibility checks. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Batch/Grant/_category_.json b/website/docs/library/access/AccessControl/Batch/Grant/_category_.json new file mode 100644 index 00000000..fcf8c58c --- /dev/null +++ b/website/docs/library/access/AccessControl/Batch/Grant/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Grant", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/Batch/Grant/index" + } +} diff --git a/website/docs/library/access/AccessControl/Batch/Grant/index.mdx b/website/docs/library/access/AccessControl/Batch/Grant/index.mdx new file mode 100644 index 00000000..5c610599 --- /dev/null +++ b/website/docs/library/access/AccessControl/Batch/Grant/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Grant" +description: "Grant components for Compose 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'; + + + Grant components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchFacet.mdx b/website/docs/library/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchFacet.mdx new file mode 100644 index 00000000..3f67a250 --- /dev/null +++ b/website/docs/library/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchFacet.mdx @@ -0,0 +1,229 @@ +--- +sidebar_position: 100 +title: "Access Control Revoke Batch Facet" +description: "Revoke roles from multiple accounts efficiently" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Revoke roles from multiple accounts efficiently + + + +- Enables batch revocation of roles for improved gas efficiency. +- Integrates with diamond storage via `STORAGE_POSITION`. +- Emits `RoleRevoked` event for each revoked account. +- Includes `exportSelectors` for facet discovery. + + +## Overview + +This facet provides an efficient way to revoke a specified role from multiple accounts in a single transaction. It integrates with the diamond's shared storage to manage access control state. Developers can add this facet to their diamond to streamline batch role revocations. + +## Storage + +### AccessControlStorage + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### revokeRoleBatch + +Revokes a role from multiple accounts in a single transaction. Emits a RoleRevoked event for each account the role is revoked from. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function revokeRoleBatch(bytes32 _role, address[] calldata _accounts) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the selectors that are exposed by the facet. + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when a role is revoked from an account. +
+ +
+ Signature: + +{`event RoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Ensure the caller has the necessary administrative privileges to revoke the specified role. +- Verify the `AccessControlStorage` struct in diamond storage is correctly initialized before using this facet. +- Use `exportSelectors` to discover the facet's capabilities during diamond upgrades. + + +## Security Considerations + + +Access to `revokeRoleBatch` is restricted to authorized callers, enforced by the `AccessControlUnauthorizedAccount` error. Ensure the caller possesses the administrative rights for the target role. The function operates on shared diamond storage; maintain invariants of the `AccessControlStorage` struct. Follow standard Solidity security practices for input validation and reentrancy. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchMod.mdx b/website/docs/library/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchMod.mdx new file mode 100644 index 00000000..f8fd9105 --- /dev/null +++ b/website/docs/library/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchMod.mdx @@ -0,0 +1,226 @@ +--- +sidebar_position: 110 +title: "Access Control Revoke Batch Module" +description: "Revoke roles from multiple accounts efficiently" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Revoke roles from multiple accounts efficiently + + + +- Provides an `internal` function `revokeRoleBatch` for batch role revocation. +- Operates on shared diamond storage, ensuring state consistency across facets. +- Emits `RoleRevoked` events for each account processed. +- Reverts with `AccessControlUnauthorizedAccount` if the caller lacks administrative rights for the role. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides an internal function to revoke a specified role from multiple accounts in a single transaction. By batching these operations, it reduces gas costs and simplifies the process for administrators managing access control within a diamond. Changes are immediately reflected across all facets interacting with the shared access control storage. + +## Storage + +### AccessControlStorage + +storage struct for the AccessControl. storage-location: erc8042:compose.accesscontrol + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the storage for the AccessControl. + + +{`function getStorage() pure returns (AccessControlStorage storage _s);`} + + +**Returns:** + + + +--- +### revokeRoleBatch + +function to revoke a role from multiple accounts in a single transaction. Emits a {RoleRevoked} event for each account the role is revoked from. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function revokeRoleBatch(bytes32 _role, address[] calldata _accounts) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when a role is revoked from an account. +
+ +
+ Signature: + +{`event RoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Ensure the caller of `revokeRolesFromAccounts` within your facet has the necessary permissions (e.g., admin role) to revoke the specified role, as enforced by the diamond's access control mechanism. +- Verify that the `AccessControlRevokeBatchMod` module is correctly initialized and accessible via its address. +- Handle the `AccessControlUnauthorizedAccount` error if the caller lacks the required administrative privileges for the role. + + +## Integration Notes + + +This module interacts with diamond storage at the `STORAGE_POSITION` slot, identified by `keccak256(\"compose.accesscontrol\")`. It reads and writes to the `AccessControlStorage` struct. The `revokeRoleBatch` function modifies role assignments within this shared storage. Any facet that subsequently reads from this storage will see the updated role assignments immediately, ensuring consistency across the diamond. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Batch/Revoke/_category_.json b/website/docs/library/access/AccessControl/Batch/Revoke/_category_.json new file mode 100644 index 00000000..c6d817ec --- /dev/null +++ b/website/docs/library/access/AccessControl/Batch/Revoke/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Revoke", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/Batch/Revoke/index" + } +} diff --git a/website/docs/library/access/AccessControl/Batch/Revoke/index.mdx b/website/docs/library/access/AccessControl/Batch/Revoke/index.mdx new file mode 100644 index 00000000..6bdb76e3 --- /dev/null +++ b/website/docs/library/access/AccessControl/Batch/Revoke/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Revoke" +description: "Revoke components for Compose 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'; + + + Revoke components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/AccessControl/Batch/_category_.json b/website/docs/library/access/AccessControl/Batch/_category_.json new file mode 100644 index 00000000..d45d0c24 --- /dev/null +++ b/website/docs/library/access/AccessControl/Batch/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Batch", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/Batch/index" + } +} diff --git a/website/docs/library/access/AccessControl/Batch/index.mdx b/website/docs/library/access/AccessControl/Batch/index.mdx new file mode 100644 index 00000000..d10533e0 --- /dev/null +++ b/website/docs/library/access/AccessControl/Batch/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Batch" +description: "Batch components for Compose 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'; + + + Batch components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/AccessControl/Data/AccessControlDataFacet.mdx b/website/docs/library/access/AccessControl/Data/AccessControlDataFacet.mdx new file mode 100644 index 00000000..b08d2191 --- /dev/null +++ b/website/docs/library/access/AccessControl/Data/AccessControlDataFacet.mdx @@ -0,0 +1,261 @@ +--- +sidebar_position: 100 +title: "Access Control Data Facet" +description: "Manages roles and permissions within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Data/AccessControlDataFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages roles and permissions within a diamond + + + +- Exposes external view functions for role checks. +- Utilizes diamond storage for role data. +- Includes a custom error `AccessControlUnauthorizedAccount` for failed role checks. +- Provides `exportSelectors` to identify its exposed functions. + + +## Overview + +This facet provides core access control data and validation functions for a diamond. It exposes external view functions to check role assignments and role hierarchies, enabling other facets or off-chain applications to query permissions. It accesses shared diamond storage to retrieve role information. + +## Storage + +### AccessControlStorage + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### hasRole + +Returns if an account has a role. + + +{`function hasRole(bytes32 _role, address _account) external view returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### requireRole + +Checks if an account has a required role. Reverts with AccessControlUnauthorizedAccount If the account does not have the role. + + +{`function requireRole(bytes32 _role, address _account) external view;`} + + +**Parameters:** + + + +--- +### getRoleAdmin + +Returns the admin role for a role. + + +{`function getRoleAdmin(bytes32 _role) external view returns (bytes32);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### exportSelectors + +Exports the selectors that are exposed by the facet. + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Query role information using the external view functions exposed by the diamond. +- Use `requireRole` to enforce access control within other facets before executing sensitive operations. +- Understand the role hierarchy by calling `getRoleAdmin`. + + +## Security Considerations + + +The `hasRole`, `requireRole`, and `getRoleAdmin` functions are view functions and do not pose reentrancy risks. `requireRole` reverts with `AccessControlUnauthorizedAccount` if the specified account does not possess the required role, providing input validation for access control checks. Developers should ensure correct role assignments in diamond initialization. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Data/AccessControlDataMod.mdx b/website/docs/library/access/AccessControl/Data/AccessControlDataMod.mdx new file mode 100644 index 00000000..a4a812a8 --- /dev/null +++ b/website/docs/library/access/AccessControl/Data/AccessControlDataMod.mdx @@ -0,0 +1,248 @@ +--- +sidebar_position: 110 +title: "Access Control Data Module" +description: "Manage access control roles and accounts" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Data/AccessControlDataMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manage access control roles and accounts + + + +- Provides `internal` functions for role checking. +- Leverages diamond storage pattern for shared state. +- Utilizes custom error `AccessControlUnauthorizedAccount` for revert reasons. +- No external dependencies, promoting composability. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for checking and enforcing access control roles within a diamond. Facets import this module to interact with shared diamond storage, enabling role-based permissions. Changes to role assignments are immediately visible to all facets accessing the same storage. + +## Storage + +### AccessControlStorage + +storage struct for the AccessControl. storage-location: erc8042:compose.accesscontrol + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the storage for the AccessControl. + + +{`function getStorage() pure returns (AccessControlStorage storage _s);`} + + +**Returns:** + + + +--- +### hasRole + +function to check if an account has a role. + + +{`function hasRole(bytes32 _role, address _account) view returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### requireRole + +function to check if an account has a required role. Reverts with AccessControlUnauthorizedAccount If the account does not have the role. + + +{`function requireRole(bytes32 _role, address _account) view;`} + + +**Parameters:** + + + +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Call `requireRole` to enforce access control checks before executing sensitive operations. +- Use `hasRole` for conditional logic that depends on an account's role. +- Ensure the diamond storage address is correctly initialized for `AccessControlDataMod`. + + +## Integration Notes + + +This module interacts with diamond storage at the `STORAGE_POSITION` defined by `keccak256("compose.accesscontrol")`. All functions operate on the `AccessControlStorage` struct, which is shared across all facets within the diamond. Changes to roles made by other facets or modules are immediately reflected when calling `hasRole` or `requireRole`. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Data/_category_.json b/website/docs/library/access/AccessControl/Data/_category_.json new file mode 100644 index 00000000..eaf9a298 --- /dev/null +++ b/website/docs/library/access/AccessControl/Data/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Data", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/Data/index" + } +} diff --git a/website/docs/library/access/AccessControl/Data/index.mdx b/website/docs/library/access/AccessControl/Data/index.mdx new file mode 100644 index 00000000..ee4c5138 --- /dev/null +++ b/website/docs/library/access/AccessControl/Data/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Data" +description: "Data components for Compose 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'; + + + Data components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/AccessControl/Grant/AccessControlGrantFacet.mdx b/website/docs/library/access/AccessControl/Grant/AccessControlGrantFacet.mdx new file mode 100644 index 00000000..2ce74148 --- /dev/null +++ b/website/docs/library/access/AccessControl/Grant/AccessControlGrantFacet.mdx @@ -0,0 +1,220 @@ +--- +sidebar_position: 100 +title: "Access Control Grant Facet" +description: "Grants roles to accounts within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Grant/AccessControlGrantFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Grants roles to accounts within a diamond + + + +- Exposes `grantRole` for programmatic role assignment. +- Emits `RoleGranted` event for state changes. +- Utilizes diamond storage for shared state management. +- Reverts with `AccessControlUnauthorizedAccount` for unauthorized calls. + + +## Overview + +This facet exposes functions to grant roles to specific accounts within a Compose diamond. It leverages shared diamond storage and emits events for off-chain monitoring. Developers integrate this facet to manage permissions programmatically. + +## Storage + +### AccessControlStorage + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### grantRole + +Grants a role to an account. Emits a RoleGranted event. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function grantRole(bytes32 _role, address _account) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the selectors that are exposed by the facet. + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when a role is granted to an account. +
+ +
+ Signature: + +{`event RoleGranted(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Initialize roles and grant administrative permissions during diamond deployment. +- Ensure the caller of `grantRole` has the necessary administrative privileges. +- Monitor `RoleGranted` events for auditing and off-chain state tracking. + + +## Security Considerations + + +The `grantRole` function is protected by access control, reverting if the caller is not the admin of the role. Input validation on `_role` and `_account` is implicitly handled by the diamond's call routing and the facet's internal logic. No reentrancy risks are apparent as the function performs a role grant before any external calls. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Grant/AccessControlGrantMod.mdx b/website/docs/library/access/AccessControl/Grant/AccessControlGrantMod.mdx new file mode 100644 index 00000000..bac216a3 --- /dev/null +++ b/website/docs/library/access/AccessControl/Grant/AccessControlGrantMod.mdx @@ -0,0 +1,247 @@ +--- +sidebar_position: 110 +title: "Access Control Grant Module" +description: "Grant roles to accounts within a diamond" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Grant/AccessControlGrantMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Grant roles to accounts within a diamond + + + +- Internal functions for role granting, suitable for custom facets. +- Leverages the diamond storage pattern for shared state management. +- Emits `RoleGranted` event upon successful role assignment. +- Reverts with `AccessControlUnauthorizedAccount` if caller lacks admin rights. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for granting roles to specific accounts. Facets can import and utilize this module to manage role assignments using shared diamond storage. Changes to role assignments are immediately reflected across all facets that access the same storage. + +## Storage + +### AccessControlStorage + +storage struct for the AccessControl. storage-location: erc8042:compose.accesscontrol + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the storage for the AccessControl. + + +{`function getStorage() pure returns (AccessControlStorage storage _s);`} + + +**Returns:** + + + +--- +### grantRole + +function to grant a role to an account. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function grantRole(bytes32 _role, address _account) returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +## Events + + + +
+ Emitted when a role is granted to an account. +
+ +
+ Signature: + +{`event RoleGranted(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Ensure the caller has the necessary administrative permissions before invoking `grantRole`. +- Handle the `AccessControlUnauthorizedAccount` error appropriately in calling facets. +- Verify that the `AccessControlGrantMod` module is initialized with the correct diamond storage position. + + +## Integration Notes + + +This module interacts with diamond storage at a predefined `STORAGE_POSITION` identified by `keccak2535("compose.accesscontrol")`. The `grantRole` function directly modifies the shared `AccessControlStorage` struct, making role assignments immediately visible to all facets accessing this storage. The `getStorage` function provides access to the current storage layout without modifying state. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Grant/_category_.json b/website/docs/library/access/AccessControl/Grant/_category_.json new file mode 100644 index 00000000..537ef848 --- /dev/null +++ b/website/docs/library/access/AccessControl/Grant/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Grant", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/Grant/index" + } +} diff --git a/website/docs/library/access/AccessControl/Grant/index.mdx b/website/docs/library/access/AccessControl/Grant/index.mdx new file mode 100644 index 00000000..8d6ca766 --- /dev/null +++ b/website/docs/library/access/AccessControl/Grant/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Grant" +description: "Grant components for Compose 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'; + + + Grant components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/AccessControl/Pausable/AccessControlPausableFacet.mdx b/website/docs/library/access/AccessControl/Pausable/AccessControlPausableFacet.mdx new file mode 100644 index 00000000..7dcacd32 --- /dev/null +++ b/website/docs/library/access/AccessControl/Pausable/AccessControlPausableFacet.mdx @@ -0,0 +1,365 @@ +--- +sidebar_position: 1 +title: "Access Control Pausable Facet" +description: "Manage role pausing and unpausing within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Pausable/AccessControlPausableFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manage role pausing and unpausing within a diamond + + + +- Allows temporary disabling of roles using `pauseRole`. +- Enables re-enabling of roles via `unpauseRole`. +- Provides `isRolePaused` to check role status. +- Integrates seamlessly with diamond storage and access control patterns. + + +## Overview + +This facet provides functionality to pause and unpause specific roles within a diamond, controlled by role administrators. It integrates with diamond storage to manage role states and allows for temporary suspension of role execution. Developers add this facet to enable granular control over role availability. + +## Storage + +### AccessControlStorage + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +--- +### AccessControlPausableStorage + + +{`struct AccessControlPausableStorage { + mapping(bytes32 role => bool paused) pausedRoles; +}`} + + +### State Variables + + + +## Functions + +### isRolePaused + +Returns if a role is paused. + + +{`function isRolePaused(bytes32 _role) external view returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### pauseRole + +Temporarily disables a role, preventing all accounts from using it. Only the admin of the role can pause it. Emits a RolePaused event. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function pauseRole(bytes32 _role) external;`} + + +**Parameters:** + + + +--- +### unpauseRole + +Re-enables a role that was previously paused. Only the admin of the role can unpause it. Emits a RoleUnpaused event. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function unpauseRole(bytes32 _role) external;`} + + +**Parameters:** + + + +--- +### requireRoleNotPaused + +Checks if an account has a role and if the role is not paused. - Reverts with AccessControlUnauthorizedAccount If the account does not have the role. - Reverts with AccessControlRolePaused If the role is paused. + + +{`function requireRoleNotPaused(bytes32 _role, address _account) external view;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the selectors that are exposed by the facet. + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Event emitted when a role is paused. +
+ +
+ Signature: + +{`event RolePaused(bytes32 indexed _role, address indexed _account);`} + +
+ +
+ Parameters: + +
+
+ +
+ Event emitted when a role is unpaused. +
+ +
+ Signature: + +{`event RoleUnpaused(bytes32 indexed _role, address indexed _account);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+ +
+ Thrown when a role is paused and an operation requiring that role is attempted. +
+ +
+ Signature: + +error AccessControlRolePaused(bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Grant the role administrator appropriate permissions to call `pauseRole` and `unpauseRole`. +- Use `requireRoleNotPaused` before executing critical operations tied to a specific role. +- Ensure `AccessControlPausableFacet` selectors are correctly registered with the diamond. + + +## Security Considerations + + +All state-changing functions (`pauseRole`, `unpauseRole`) are protected by access control, reverting with `AccessControlUnauthorizedAccount` if the caller is not the admin of the role. The `requireRoleNotPaused` function reverts with `AccessControlRolePaused` if the specified role is paused, preventing execution. Follow standard Solidity security practices for input validation. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Pausable/AccessControlPausableMod.mdx b/website/docs/library/access/AccessControl/Pausable/AccessControlPausableMod.mdx new file mode 100644 index 00000000..ddf07a2a --- /dev/null +++ b/website/docs/library/access/AccessControl/Pausable/AccessControlPausableMod.mdx @@ -0,0 +1,389 @@ +--- +sidebar_position: 2 +title: "Access Control Pausable Module" +description: "Manages role pausing and checks within a diamond" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Pausable/AccessControlPausableMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages role pausing and checks within a diamond + + + +- Internal functions for role pausing and status checks, intended for use within custom facets. +- Utilizes diamond storage pattern via `ACCESS_CONTROL_STORAGE_POSITION` for shared state. +- Emits `RolePaused` and `RoleUnpaused` events for off-chain monitoring. +- Reverts with custom errors `AccessControlRolePaused` and `AccessControlUnauthorizedAccount`. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions to pause and unpause specific roles, controlling whether accounts can perform actions associated with those roles. It leverages diamond storage for state management, ensuring that pausing decisions are universally visible across all facets interacting with the same storage. This enhances security by allowing temporary suspension of role-based operations. + +## Storage + +### AccessControlPausableStorage + +Storage struct for AccessControlPausable. storage-location: erc8042:compose.accesscontrol.pausable + + +{`struct AccessControlPausableStorage { + mapping(bytes32 role => bool paused) pausedRoles; +}`} + + +--- +### AccessControlStorage + +Storage struct for AccessControl (reused struct definition). Must match the struct definition in AccessControlDataFacet / AccessControlDataMod. storage-location: erc8042:compose.accesscontrol + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### getAccessControlStorage + +Returns the storage for AccessControl. + + +{`function getAccessControlStorage() pure returns (AccessControlStorage storage s);`} + + +**Returns:** + + + +--- +### getStorage + +Returns the storage for AccessControlPausable. + + +{`function getStorage() pure returns (AccessControlPausableStorage storage s);`} + + +**Returns:** + + + +--- +### isRolePaused + +function to check if a role is paused. + + +{`function isRolePaused(bytes32 _role) view returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### pauseRole + +function to pause a role. + + +{`function pauseRole(bytes32 _role) ;`} + + +**Parameters:** + + + +--- +### requireRoleNotPaused + +function to check if an account has a role and if the role is not paused. **Notes:** - Reverts with AccessControlUnauthorizedAccount If the account does not have the role. - Reverts with AccessControlRolePaused If the role is paused. + + +{`function requireRoleNotPaused(bytes32 _role, address _account) view;`} + + +**Parameters:** + + + +--- +### unpauseRole + +function to unpause a role. + + +{`function unpauseRole(bytes32 _role) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Event emitted when a role is paused. +
+ +
+ Signature: + +{`event RolePaused(bytes32 indexed _role, address indexed _account);`} + +
+ +
+ Parameters: + +
+
+ +
+ Event emitted when a role is unpaused. +
+ +
+ Signature: + +{`event RoleUnpaused(bytes32 indexed _role, address indexed _account);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when a role is paused and an operation requiring that role is attempted. +
+ +
+ Signature: + +error AccessControlRolePaused(bytes32 _role); + +
+
+ +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Call `requireRoleNotPaused` before executing role-dependent logic to enforce active role status. +- Ensure the `AccessControlPausableMod` is correctly initialized with the diamond's storage pointer. +- Handle `AccessControlRolePaused` and `AccessControlUnauthorizedAccount` errors when using `requireRoleNotPaused` in external-facing functions. + + +## Integration Notes + + +This module interacts with diamond storage at the position identified by `ACCESS_CONTROL_STORAGE_POSITION`. It manages the paused state of roles within the `AccessControlPausableStorage` struct, which is shared across all facets. Functions like `pauseRole` and `unpauseRole` modify this shared state, making the changes immediately visible to any other facet that reads from the same storage position. The `requireRoleNotPaused` function reads this state to enforce restrictions. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Pausable/_category_.json b/website/docs/library/access/AccessControl/Pausable/_category_.json new file mode 100644 index 00000000..c49784fe --- /dev/null +++ b/website/docs/library/access/AccessControl/Pausable/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Pausable", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/Pausable/index" + } +} diff --git a/website/docs/library/access/AccessControl/Pausable/index.mdx b/website/docs/library/access/AccessControl/Pausable/index.mdx new file mode 100644 index 00000000..1533b843 --- /dev/null +++ b/website/docs/library/access/AccessControl/Pausable/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Pausable" +description: "Pausable components for Compose 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'; + + + Pausable components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/AccessControl/Renounce/AccessControlRenounceFacet.mdx b/website/docs/library/access/AccessControl/Renounce/AccessControlRenounceFacet.mdx new file mode 100644 index 00000000..b9c02931 --- /dev/null +++ b/website/docs/library/access/AccessControl/Renounce/AccessControlRenounceFacet.mdx @@ -0,0 +1,210 @@ +--- +sidebar_position: 100 +title: "Access Control Renounce Facet" +description: "Renounces roles for accounts within a diamond." +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Renounce/AccessControlRenounceFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Renounces roles for accounts within a diamond. + + + +- Exposes an external `renounceRole` function for role management. +- Emits `RoleRevoked` event upon successful role renouncement. +- Reverts with `AccessControlUnauthorizedSender` if the caller is not the target account. +- Compatible with ERC-2535 diamond standard. + + +## Overview + +This facet provides functionality to renounce roles for accounts in a diamond. It exposes the `renounceRole` function, allowing authorized accounts to relinquish their assigned roles. Calls are routed through the diamond proxy, ensuring consistent access control and upgradeability. + +## Storage + +### AccessControlStorage + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### renounceRole + +Renounces a role from the caller. Emits a RoleRevoked event. Reverts with AccessControlUnauthorizedSender If the caller is not the account to renounce the role from. + + +{`function renounceRole(bytes32 _role, address _account) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the selectors that are exposed by the facet. + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when a role is revoked from an account. +
+ +
+ Signature: + +{`event RoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the sender is not the account to renounce the role from. +
+ +
+ Signature: + +error AccessControlUnauthorizedSender(address _sender, address _account); + +
+
+
+ + + + +## Best Practices + + +- Call `renounceRole` through the diamond proxy to ensure proper access control checks. +- Ensure the caller is the account from which the role is being renounced to prevent unauthorized revocations. +- Verify the `RoleRevoked` event is emitted and correctly interpreted by off-chain services. + + +## Security Considerations + + +The `renounceRole` function is protected by access control, ensuring only the account itself can renounce its role. The facet does not perform external calls, mitigating reentrancy risks. Input validation is handled by the underlying access control logic within the diamond. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Renounce/AccessControlRenounceMod.mdx b/website/docs/library/access/AccessControl/Renounce/AccessControlRenounceMod.mdx new file mode 100644 index 00000000..28aefbc6 --- /dev/null +++ b/website/docs/library/access/AccessControl/Renounce/AccessControlRenounceMod.mdx @@ -0,0 +1,228 @@ +--- +sidebar_position: 110 +title: "Access Control Renounce Module" +description: "Renounce roles for accounts within a diamond" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Renounce/AccessControlRenounceMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Renounce roles for accounts within a diamond + + + +- Provides an `internal` function `renounceRole` for relinquishing roles. +- Utilizes diamond storage (EIP-8042) for role management. +- Emits a `RoleRevoked` event upon successful role renouncement. +- Enforces sender authorization to prevent unauthorized role revokations. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides the `renounceRole` function, allowing an account to relinquish its assigned role. It leverages diamond storage to manage role assignments, ensuring that changes are immediately visible to all facets interacting with the same storage. This enables decentralized management of roles within the diamond. + +## Storage + +### AccessControlStorage + +storage struct for the AccessControl. storage-location: erc8042:compose.accesscontrol + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the storage for the AccessControl. + + +{`function getStorage() pure returns (AccessControlStorage storage _s);`} + + +**Returns:** + + + +--- +### renounceRole + +function to renounce a role from the caller. Emits a {RoleRevoked} event. Reverts with AccessControlUnauthorizedSender If the caller is not the account to renounce the role from. + + +{`function renounceRole(bytes32 _role, address _account) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when a role is revoked from an account. +
+ +
+ Signature: + +{`event RoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the sender is not the account to renounce the role from. +
+ +
+ Signature: + +error AccessControlUnauthorizedSender(address _sender, address _account); + +
+
+
+ + + + +## Best Practices + + +- Ensure that the caller is the intended account before calling `renounceRole` to prevent unauthorized role revocation. +- Handle the `AccessControlUnauthorizedSender` error to gracefully manage cases where the caller is not the account from which the role is being renounced. +- Verify that the `AccessControlStorage` struct and its associated `STORAGE_POSITION` are correctly configured within the diamond's storage layout. + + +## Integration Notes + + +This module interacts with diamond storage at the `STORAGE_POSITION` defined by `keccak256(\"compose.accesscontrol\")`. The `renounceRole` function modifies the shared `AccessControlStorage` struct. Changes made via this module are immediately visible to any other facet that reads from the same storage position, ensuring consistent state across the diamond. The `getStorage` function can be used to retrieve the storage instance if needed. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Renounce/_category_.json b/website/docs/library/access/AccessControl/Renounce/_category_.json new file mode 100644 index 00000000..ac1ccf73 --- /dev/null +++ b/website/docs/library/access/AccessControl/Renounce/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Renounce", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/Renounce/index" + } +} diff --git a/website/docs/library/access/AccessControl/Renounce/index.mdx b/website/docs/library/access/AccessControl/Renounce/index.mdx new file mode 100644 index 00000000..44691cd5 --- /dev/null +++ b/website/docs/library/access/AccessControl/Renounce/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Renounce" +description: "Renounce components for Compose 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'; + + + Renounce components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/AccessControl/Revoke/AccessControlRevokeFacet.mdx b/website/docs/library/access/AccessControl/Revoke/AccessControlRevokeFacet.mdx new file mode 100644 index 00000000..31129702 --- /dev/null +++ b/website/docs/library/access/AccessControl/Revoke/AccessControlRevokeFacet.mdx @@ -0,0 +1,222 @@ +--- +sidebar_position: 100 +title: "Access Control Revoke Facet" +description: "Revokes roles from accounts within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Revoke/AccessControlRevokeFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Revokes roles from accounts within a diamond + + + +- Revokes roles from accounts using diamond storage. +- Emits `RoleRevoked` event upon successful revocation. +- Reverts with `AccessControlUnauthorizedAccount` if the caller lacks administrative privileges for the role. +- Exports facet selectors via `exportSelectors`. + + +## Overview + +This facet provides functionality to revoke roles from specific accounts within a Compose diamond. It interacts with shared diamond storage to manage role assignments. Developers integrate this facet to enable dynamic permission management, ensuring only authorized entities can perform certain actions. + +## Storage + +### AccessControlStorage + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### revokeRole + +Revokes a role from an account. Emits a RoleRevoked event. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function revokeRole(bytes32 _role, address _account) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the selectors that are exposed by the facet. + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when a role is revoked from an account. +
+ +
+ Signature: + +{`event RoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Enforce access control on the `revokeRole` function to ensure only authorized callers can revoke roles. +- Ensure the `AccessControlRevokeMod` is properly integrated and initialized to manage role revocation logic. +- Verify that the `AccessControlStorage` struct in diamond storage is compatible before upgrading or adding this facet. + + +## Security Considerations + + +The `revokeRole` function is protected by an access control mechanism, reverting with `AccessControlUnauthorizedAccount` if the caller is not the administrator of the specified role. Follow standard Solidity security practices for input validation and state management. Ensure proper initialization of roles and accounts before attempting revocation. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Revoke/AccessControlRevokeMod.mdx b/website/docs/library/access/AccessControl/Revoke/AccessControlRevokeMod.mdx new file mode 100644 index 00000000..4ce61479 --- /dev/null +++ b/website/docs/library/access/AccessControl/Revoke/AccessControlRevokeMod.mdx @@ -0,0 +1,243 @@ +--- +sidebar_position: 110 +title: "Access Control Revoke Module" +description: "Revoke roles from accounts using diamond storage" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Revoke/AccessControlRevokeMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Revoke roles from accounts using diamond storage + + + +- Functions are `internal`, designed for use within other diamond facets. +- Leverages the diamond storage pattern for shared state management. +- Emits a `RoleRevoked` event upon successful role revocation. +- No external dependencies, ensuring composability. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions to revoke roles from accounts within a diamond. By utilizing shared diamond storage, changes made through this module are immediately visible to all facets accessing the same storage. This ensures consistent access control across the diamond. + +## Storage + +### AccessControlStorage + +storage struct for the AccessControl. storage-location: erc8042:compose.accesscontrol + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the storage for the AccessControl. + + +{`function getStorage() pure returns (AccessControlStorage storage _s);`} + + +**Returns:** + + + +--- +### revokeRole + +function to revoke a role from an account. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function revokeRole(bytes32 _role, address _account) returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +## Events + + + +
+ Emitted when a role is revoked from an account. +
+ +
+ Signature: + +{`event RoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Ensure that the caller has the necessary permissions to revoke the specified role before calling `revokeRole`. +- Verify that the `AccessControlStorage` struct layout in `AccessControlRevokeMod` is compatible with other facets accessing the same storage slot during diamond upgrades. +- Handle the `AccessControlUnauthorizedAccount` error, which is reverted if the caller is not authorized to revoke the role. + + +## Integration Notes + + +This module interacts with diamond storage at the slot identified by `STORAGE_POSITION`, keyed as `keccak2535('compose.accesscontrol')`. The `AccessControlStorage` struct, though empty in this specific definition, is managed at this slot. Functions within this module directly read from and write to this shared storage. Any changes to role assignments made via `revokeRole` are immediately reflected for all facets that access the same storage slot, ensuring data consistency across the diamond. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Revoke/_category_.json b/website/docs/library/access/AccessControl/Revoke/_category_.json new file mode 100644 index 00000000..a7f05aa9 --- /dev/null +++ b/website/docs/library/access/AccessControl/Revoke/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Revoke", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/Revoke/index" + } +} diff --git a/website/docs/library/access/AccessControl/Revoke/index.mdx b/website/docs/library/access/AccessControl/Revoke/index.mdx new file mode 100644 index 00000000..998b9891 --- /dev/null +++ b/website/docs/library/access/AccessControl/Revoke/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Revoke" +description: "Revoke components for Compose 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'; + + + Revoke components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.mdx b/website/docs/library/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.mdx new file mode 100644 index 00000000..3d69c60b --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.mdx @@ -0,0 +1,388 @@ +--- +sidebar_position: 100 +title: "Access Control Temporal Data Facet" +description: "Manages time-bound role assignments and checks for expired roles" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages time-bound role assignments and checks for expired roles + + + +- Manages temporal role assignments and checks for expiry. +- Exposes `getRoleExpiry`, `isRoleExpired`, and `requireValidRole` for role validation. +- Operates on shared diamond storage via internal `getStorage` and `getAccessControlStorage` functions. +- Exports its selectors for diamond registration. + + +## Overview + +This facet provides functionality for managing time-bound access control roles within a Compose diamond. It exposes external view functions to check role expiry and internal functions to access its specific storage layout. This facet integrates with other access control facets by operating on shared diamond storage. + +## Storage + +### AccessControlStorage + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +--- +### AccessControlTemporalStorage + + +{`struct AccessControlTemporalStorage { + mapping(address account => mapping(bytes32 role => uint256 expiryTimestamp)) roleExpiry; +}`} + + +### State Variables + + + +## Functions + +### getRoleExpiry + +Returns the expiry timestamp for a role assignment. + + +{`function getRoleExpiry(bytes32 _role, address _account) external view returns (uint256);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### isRoleExpired + +Checks if a role assignment has expired. + + +{`function isRoleExpired(bytes32 _role, address _account) external view returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### requireValidRole + +Checks if an account has a valid (non-expired) role. - Reverts with AccessControlUnauthorizedAccount If the account does not have the role. - Reverts with AccessControlRoleExpired If the role has expired. + + +{`function requireValidRole(bytes32 _role, address _account) external view;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the selectors that are exposed by the facet. + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Event emitted when a role is granted with an expiry timestamp. +
+ +
+ Signature: + +{`event RoleGrantedWithExpiry( + bytes32 indexed _role, address indexed _account, uint256 _expiresAt, address indexed _sender +);`} + +
+ +
+ Parameters: + +
+
+ +
+ Event emitted when a temporal role is revoked. +
+ +
+ Signature: + +{`event TemporalRoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+ +
+ Thrown when a role has expired. +
+ +
+ Signature: + +error AccessControlRoleExpired(bytes32 _role, address _account); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `AccessControlTemporalDataFacet` is correctly initialized with its storage slot. +- When granting roles with expiry, ensure the `_expiresAt` timestamp is set appropriately. +- Verify that `requireValidRole` is called before sensitive operations that depend on time-bound roles. + + +## Security Considerations + + +This facet exposes `requireValidRole`, which reverts with `AccessControlUnauthorizedAccount` if the account lacks the role, or `AccessControlRoleExpired` if the role has expired. Input validation for role names and account addresses is handled by the underlying logic. No reentrancy concerns are present as all exposed functions are `view` or `pure`. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Temporal/Data/AccessControlTemporalDataMod.mdx b/website/docs/library/access/AccessControl/Temporal/Data/AccessControlTemporalDataMod.mdx new file mode 100644 index 00000000..79c5a841 --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/Data/AccessControlTemporalDataMod.mdx @@ -0,0 +1,414 @@ +--- +sidebar_position: 110 +title: "Access Control Temporal Data Module" +description: "Manages temporal role assignments and their expiry" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Temporal/Data/AccessControlTemporalDataMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages temporal role assignments and their expiry + + + +- Provides internal functions for temporal role management. +- Leverages diamond storage for temporal role data. +- Reverts with specific errors for expired roles or unauthorized accounts. +- No external dependencies; designed for direct integration into facets. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions to manage temporal role assignments, including their expiry. Facets can import this module to check and enforce role validity using shared diamond storage. Changes to role expiry are immediately visible to all facets accessing the same storage. + +## Storage + +### AccessControlStorage + +Storage struct for AccessControl (reused struct definition). Must match the struct definition in AccessControlDataFacet. storage-location: erc8042:compose.accesscontrol + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +--- +### AccessControlTemporalStorage + +Storage struct for AccessControlTemporal. storage-location: erc8042:compose.accesscontrol.temporal + + +{`struct AccessControlTemporalStorage { + mapping(address account => mapping(bytes32 role => uint256 expiryTimestamp)) roleExpiry; +}`} + + +### State Variables + + + +## Functions + +### getAccessControlStorage + +Returns the storage for AccessControl. + + +{`function getAccessControlStorage() pure returns (AccessControlStorage storage s);`} + + +**Returns:** + + + +--- +### getRoleExpiry + +Returns the expiry timestamp for a role assignment. + + +{`function getRoleExpiry(bytes32 _role, address _account) view returns (uint256);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### getStorage + +Returns the storage for AccessControlTemporal. + + +{`function getStorage() pure returns (AccessControlTemporalStorage storage s);`} + + +**Returns:** + + + +--- +### isRoleExpired + +Checks if a role assignment has expired. + + +{`function isRoleExpired(bytes32 _role, address _account) view returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### requireValidRole + +Checks if an account has a valid (non-expired) role. **Notes:** - Reverts with AccessControlUnauthorizedAccount If the account does not have the role. - Reverts with AccessControlRoleExpired If the role has expired. + + +{`function requireValidRole(bytes32 _role, address _account) view;`} + + +**Parameters:** + + + +## Events + + + +
+ Event emitted when a role is granted with an expiry timestamp. +
+ +
+ Signature: + +{`event RoleGrantedWithExpiry( +bytes32 indexed _role, address indexed _account, uint256 _expiresAt, address indexed _sender +);`} + +
+ +
+ Parameters: + +
+
+ +
+ Event emitted when a temporal role is revoked. +
+ +
+ Signature: + +{`event TemporalRoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when a role has expired. +
+ +
+ Signature: + +error AccessControlRoleExpired(bytes32 _role, address _account); + +
+
+ +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Ensure temporal role expiry checks are performed before critical operations. +- Handle `AccessControlRoleExpired` and `AccessControlUnauthorizedAccount` errors explicitly when calling `requireValidRole`. +- Verify that the `AccessControlTemporalDataMod` is initialized with correct storage slot and that related modules are compatible. + + +## Integration Notes + + +This module interacts with diamond storage at the `ACCESS_CONTROL_STORAGE_POSITION`, identified by `keccak256("compose.accesscontrol")`, to store and retrieve temporal role assignment data. The `AccessControlTemporalStorage` struct is managed implicitly through this slot. Changes made via functions like `getRoleExpiry` or checks performed by `isRoleExpired` and `requireValidRole` directly access and reflect the state within the diamond's shared storage, making them immediately visible to any other facet that reads from the same storage position. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Temporal/Data/_category_.json b/website/docs/library/access/AccessControl/Temporal/Data/_category_.json new file mode 100644 index 00000000..3965bab1 --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/Data/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Data", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/Temporal/Data/index" + } +} diff --git a/website/docs/library/access/AccessControl/Temporal/Data/index.mdx b/website/docs/library/access/AccessControl/Temporal/Data/index.mdx new file mode 100644 index 00000000..0d9e23cf --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/Data/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Data" +description: "Data components for Compose 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'; + + + Data components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.mdx b/website/docs/library/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.mdx new file mode 100644 index 00000000..5de51dac --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.mdx @@ -0,0 +1,262 @@ +--- +sidebar_position: 100 +title: "Access Control Temporal Grant Facet" +description: "Grants roles with time-based expiry" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Grants roles with time-based expiry + + + +- Grants roles with a specified expiry timestamp. +- Emits `RoleGrantedWithExpiry` event upon successful role granting. +- Reverts with `AccessControlUnauthorizedAccount` if the caller lacks permission. +- Reverts with `AccessControlRoleExpired` if the role has expired. + + +## Overview + +This facet manages role assignments with time-based expiry within a Compose diamond. It provides an external function to grant roles, ensuring they automatically expire. This facet interacts with shared diamond storage to manage role assignments, making role management upgradeable and composable. + +## Storage + +### AccessControlStorage + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +--- +### AccessControlTemporalStorage + + +{`struct AccessControlTemporalStorage { + mapping(address account => mapping(bytes32 role => uint256 expiryTimestamp)) roleExpiry; +}`} + + +### State Variables + + + +## Functions + +### grantRoleWithExpiry + +Grants a role to an account with an expiry timestamp. Only the admin of the role can grant it with expiry. Emits a RoleGrantedWithExpiry event. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function grantRoleWithExpiry(bytes32 _role, address _account, uint256 _expiresAt) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the selectors that are exposed by the facet. + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Event emitted when a role is granted with an expiry timestamp. +
+ +
+ Signature: + +{`event RoleGrantedWithExpiry( + bytes32 indexed _role, address indexed _account, uint256 _expiresAt, address indexed _sender +);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+ +
+ Thrown when a role has expired. +
+ +
+ Signature: + +error AccessControlRoleExpired(bytes32 _role, address _account); + +
+
+
+ + + + +## Best Practices + + +- Initialize the diamond with necessary roles and accounts before using this facet. +- Ensure the caller has the administrative privilege for the role being granted. +- Verify role expiry logic by testing with accounts that have expired roles. + + +## Security Considerations + + +State-changing functions, specifically `grantRoleWithExpiry`, are protected by access control, ensuring only authorized accounts can grant roles. The function adheres to the checks-effects-interactions pattern. Input validation for the expiry timestamp should be considered to prevent granting roles with past expiry dates, although the `AccessControlRoleExpired` error implicitly handles this for role checks. The facet relies on the diamond's internal storage mechanism for role management. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantMod.mdx b/website/docs/library/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantMod.mdx new file mode 100644 index 00000000..2cbcc7c4 --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantMod.mdx @@ -0,0 +1,309 @@ +--- +sidebar_position: 110 +title: "Access Control Temporal Grant Module" +description: "Grant roles with expiry using diamond storage" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Grant roles with expiry using diamond storage + + + +- Internal functions designed for use within custom facets. +- Manages role grants with expiry timestamps. +- Utilizes the diamond storage pattern (EIP-8042) for shared state. +- Emits `RoleGrantedWithExpiry` event upon successful role granting. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides functions to grant roles with an expiry timestamp, utilizing shared diamond storage for role management. Facets can integrate this module to enforce temporal access control, ensuring roles are automatically revoked after their expiry. Changes made via this module are immediately visible to all facets operating on the same diamond storage. + +## Storage + +### AccessControlStorage + +Storage struct for AccessControl (reused struct definition). Must match the struct definition in AccessControlDataFacet. storage-location: erc8042:compose.accesscontrol + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +--- +### AccessControlTemporalStorage + +Storage struct for AccessControlTemporal. storage-location: erc8042:compose.accesscontrol.temporal + + +{`struct AccessControlTemporalStorage { + mapping(address account => mapping(bytes32 role => uint256 expiryTimestamp)) roleExpiry; +}`} + + +### State Variables + + + +## Functions + +### getAccessControlStorage + +Returns the storage for AccessControl. + + +{`function getAccessControlStorage() pure returns (AccessControlStorage storage s);`} + + +**Returns:** + + + +--- +### getStorage + +Returns the storage for AccessControlTemporal. + + +{`function getStorage() pure returns (AccessControlTemporalStorage storage s);`} + + +**Returns:** + + + +--- +### grantRoleWithExpiry + +Grants a role to an account with an expiry timestamp. Only the admin of the role can grant it with expiry. Emits a {RoleGrantedWithExpiry} event. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function grantRoleWithExpiry(bytes32 _role, address _account, uint256 _expiresAt) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Event emitted when a role is granted with an expiry timestamp. +
+ +
+ Signature: + +{`event RoleGrantedWithExpiry( +bytes32 indexed _role, address indexed _account, uint256 _expiresAt, address indexed _sender +);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when a role has expired. +
+ +
+ Signature: + +error AccessControlRoleExpired(bytes32 _role, address _account); + +
+
+ +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Ensure the caller has the necessary administrative role before invoking `grantRoleWithExpiry`. +- Verify the `_expiresAt` timestamp is in the future to prevent immediate expiration. +- Integrate with role expiration checks in other facets to ensure timely revocation enforcement. + + +## Integration Notes + + +This module interacts with diamond storage at the `ACCESS_CONTROL_STORAGE_POSITION`, which is identified by `keccak256("compose.accesscontrol")`. It reads from and writes to the `AccessControlTemporalStorage` struct within this shared storage. Changes to roles and their expiry times are immediately reflected and visible to all facets accessing the same diamond storage. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Temporal/Grant/_category_.json b/website/docs/library/access/AccessControl/Temporal/Grant/_category_.json new file mode 100644 index 00000000..b3b4d8cf --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/Grant/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Grant", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/Temporal/Grant/index" + } +} diff --git a/website/docs/library/access/AccessControl/Temporal/Grant/index.mdx b/website/docs/library/access/AccessControl/Temporal/Grant/index.mdx new file mode 100644 index 00000000..d2b5c21a --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/Grant/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Grant" +description: "Grant components for Compose 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'; + + + Grant components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeFacet.mdx b/website/docs/library/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeFacet.mdx new file mode 100644 index 00000000..549d0e46 --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeFacet.mdx @@ -0,0 +1,241 @@ +--- +sidebar_position: 100 +title: "Access Control Temporal Revoke Facet" +description: "Revokes temporal roles from accounts within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Revokes temporal roles from accounts within a diamond + + + +- Revokes temporal roles from accounts via external functions. +- Utilizes diamond storage for state management. +- Emits `TemporalRoleRevoked` event upon successful revocation. +- Protects state-changing functions with access control checks. + + +## Overview + +This facet provides functionality to revoke temporal roles assigned to accounts within a Compose diamond. It exposes an external function that interacts with diamond storage to manage role revocations, ensuring that only authorized callers can perform these actions. This facet enables dynamic management of permissions in an upgradeable diamond architecture. + +## Storage + +### AccessControlStorage + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +--- +### AccessControlTemporalStorage + + +{`struct AccessControlTemporalStorage { + mapping(address account => mapping(bytes32 role => uint256 expiryTimestamp)) roleExpiry; +}`} + + +### State Variables + + + +## Functions + +### revokeTemporalRole + +Revokes a temporal role from an account. Only the admin of the role can revoke it. Emits a TemporalRoleRevoked event. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function revokeTemporalRole(bytes32 _role, address _account) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the selectors that are exposed by the facet. + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Event emitted when a temporal role is revoked. +
+ +
+ Signature: + +{`event TemporalRoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `AccessControlTemporalRevokeFacet` is correctly initialized within the diamond's deployment process. +- Verify that only the designated admin for a temporal role can call `revokeTemporalRole`. +- Use `exportSelectors` to understand the facet's ABI and integrate it with diamond facets management. + + +## Security Considerations + + +The `revokeTemporalRole` function is protected by access control, reverting with `AccessControlUnauthorizedAccount` if the caller is not the admin of the specified role. Input validation for `_role` and `_account` should be handled by the caller or within the diamond's overall access control strategy. No reentrancy guards are explicitly implemented; follow standard Solidity security practices for external interactions. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeMod.mdx b/website/docs/library/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeMod.mdx new file mode 100644 index 00000000..d1eac65b --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeMod.mdx @@ -0,0 +1,262 @@ +--- +sidebar_position: 110 +title: "Access Control Temporal Revoke Module" +description: "Revoke temporal roles with admin authorization" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Revoke temporal roles with admin authorization + + + +- Provides an `internal` function `revokeTemporalRole` for revoking temporal roles. +- Enforces authorization, allowing only the role's admin to revoke. +- Emits a `TemporalRoleRevoked` event upon successful revocation. +- Operates using the diamond storage pattern for shared state management. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides functions to revoke temporal roles, ensuring that only authorized administrators can perform this action. It integrates with diamond storage to manage role revocation state, making changes immediately visible to all facets accessing the same storage. Use this module to implement time-limited access control within your diamond. + +## Storage + +### AccessControlStorage + +Storage struct for AccessControl (reused struct definition). Must match the struct definition in AccessControlDataFacet. storage-location: erc8042:compose.accesscontrol + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; + mapping(bytes32 role => bytes32 adminRole) adminRole; +}`} + + +--- +### AccessControlTemporalStorage + +Storage struct for AccessControlTemporal. storage-location: erc8042:compose.accesscontrol.temporal + + +{`struct AccessControlTemporalStorage { + mapping(address account => mapping(bytes32 role => uint256 expiryTimestamp)) roleExpiry; +}`} + + +### State Variables + + + +## Functions + +### getAccessControlStorage + +Returns the storage for AccessControl. + + +{`function getAccessControlStorage() pure returns (AccessControlStorage storage s);`} + + +**Returns:** + + + +--- +### getStorage + +Returns the storage for AccessControlTemporal. + + +{`function getStorage() pure returns (AccessControlTemporalStorage storage s);`} + + +**Returns:** + + + +--- +### revokeTemporalRole + +Revokes a temporal role from an account. Only the admin of the role can revoke it. Emits a {TemporalRoleRevoked} event. Reverts with AccessControlUnauthorizedAccount If the caller is not the admin of the role. + + +{`function revokeTemporalRole(bytes32 _role, address _account) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Event emitted when a temporal role is revoked. +
+ +
+ Signature: + +{`event TemporalRoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+
+ + + + +## Best Practices + + +- Ensure the caller possesses the necessary administrative privileges for the role before invoking `revokeTemporalRole`. +- Verify that the `AccessControlTemporalRevokeMod` has been correctly initialized with its diamond storage address. +- Handle the `AccessControlUnauthorizedAccount` error to gracefully manage unauthorized revocation attempts. + + +## Integration Notes + + +This module operates on shared diamond storage, specifically utilizing the `ACCESS_CONTROL_STORAGE_POSITION` (keccak256("compose.accesscontrol")) for its `AccessControlStorage` and `AccessControlTemporalStorage` structures. All modifications made via `revokeTemporalRole` are immediately reflected in the diamond's storage and are visible to any other facet that reads from these storage locations. The module's functions are `internal`, implying they are intended to be called by other facets within the same diamond. + + +
+ +
+ + diff --git a/website/docs/library/access/AccessControl/Temporal/Revoke/_category_.json b/website/docs/library/access/AccessControl/Temporal/Revoke/_category_.json new file mode 100644 index 00000000..6aabbe26 --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/Revoke/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Revoke", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/Temporal/Revoke/index" + } +} diff --git a/website/docs/library/access/AccessControl/Temporal/Revoke/index.mdx b/website/docs/library/access/AccessControl/Temporal/Revoke/index.mdx new file mode 100644 index 00000000..7513b82c --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/Revoke/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Revoke" +description: "Revoke components for Compose 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'; + + + Revoke components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/AccessControl/Temporal/_category_.json b/website/docs/library/access/AccessControl/Temporal/_category_.json new file mode 100644 index 00000000..3180f19b --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Temporal", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/Temporal/index" + } +} diff --git a/website/docs/library/access/AccessControl/Temporal/index.mdx b/website/docs/library/access/AccessControl/Temporal/index.mdx new file mode 100644 index 00000000..1af2d458 --- /dev/null +++ b/website/docs/library/access/AccessControl/Temporal/index.mdx @@ -0,0 +1,36 @@ +--- +title: "Temporal" +description: "Temporal components for Compose 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'; + + + Temporal components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/AccessControl/_category_.json b/website/docs/library/access/AccessControl/_category_.json new file mode 100644 index 00000000..1504700a --- /dev/null +++ b/website/docs/library/access/AccessControl/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Access Control", + "position": 3, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/AccessControl/index" + } +} diff --git a/website/docs/library/access/AccessControl/index.mdx b/website/docs/library/access/AccessControl/index.mdx new file mode 100644 index 00000000..3144c28c --- /dev/null +++ b/website/docs/library/access/AccessControl/index.mdx @@ -0,0 +1,71 @@ +--- +title: "Access Control" +description: "Role-based access control (RBAC) pattern." +--- + +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'; + + + Role-based access control (RBAC) pattern. + + + + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/Owner/Data/OwnerDataFacet.mdx b/website/docs/library/access/Owner/Data/OwnerDataFacet.mdx new file mode 100644 index 00000000..342f7751 --- /dev/null +++ b/website/docs/library/access/Owner/Data/OwnerDataFacet.mdx @@ -0,0 +1,162 @@ +--- +sidebar_position: 100 +title: "Owner Data Facet" +description: "Manages owner address and facet selectors" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/Owner/Data/OwnerDataFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages owner address and facet selectors + + + +- Exposes owner address via an external `owner()` function. +- Provides `exportSelectors()` for facet function discovery. +- Utilizes diamond storage for owner data. +- Compliant with ERC-2535 diamond standard. + + +## Overview + +This facet provides external access to the owner's address and exposes its own function selectors. It interacts with diamond storage to retrieve owner information and uses the diamond pattern for selector discovery. Developers add this facet to expose owner management and facilitate diamond upgrades. + +## Storage + +### OwnerStorage + + +{`struct OwnerStorage { + address owner; +}`} + + +### State Variables + + + +## Functions + +### owner + +Get the address of the owner + + +{`function owner() external view returns (address);`} + + +**Returns:** + + + +--- +### exportSelectors + +Exports the function selectors of the OwnerDataFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + + + + +## Best Practices + + +- Ensure the OwnerDataFacet is correctly initialized with an owner address during diamond deployment. +- Access owner information only through the diamond proxy to maintain upgradeability. +- Use `exportSelectors` to discover facet functions for integration or upgrade processes. + + +## Security Considerations + + +The `owner()` function is a `view` function and does not modify state. The `exportSelectors()` function is `pure` and does not access state. Access control to these functions is managed by the diamond proxy itself. Follow standard Solidity security practices. + + +
+ +
+ + diff --git a/website/docs/library/access/Owner/Data/OwnerDataMod.mdx b/website/docs/library/access/Owner/Data/OwnerDataMod.mdx new file mode 100644 index 00000000..b5340e2f --- /dev/null +++ b/website/docs/library/access/Owner/Data/OwnerDataMod.mdx @@ -0,0 +1,240 @@ +--- +sidebar_position: 110 +title: "Owner Data Module" +description: "Manages ERC-173 contract ownership using diamond storage" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/Owner/Data/OwnerDataMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages ERC-173 contract ownership using diamond storage + + + +- Provides internal functions for ERC-173 ownership management. +- Utilizes diamond storage pattern for shared state. +- No external dependencies, promoting composability. +- Functions are `internal` and designed for use within custom facets. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for managing ERC-173 contract ownership. Facets can import this module to check and set the contract owner using shared diamond storage. Changes to ownership are immediately visible to all facets accessing the same storage slot. + +## Storage + +### OwnerStorage + +storage-location: erc8042:erc173.owner + + +{`struct OwnerStorage { + address owner; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns a pointer to the ERC-173 storage struct. Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + + +{`function getStorage() pure returns (OwnerStorage storage s);`} + + +**Returns:** + + + +--- +### owner + +Get the address of the owner + + +{`function owner() view returns (address);`} + + +**Returns:** + + + +--- +### requireOwner + +Reverts if the caller is not the owner. + + +{`function requireOwner() view;`} + + +--- +### setContractOwner + + +{`function setContractOwner(address _initialOwner) ;`} + + +**Parameters:** + + + +## Events + + + +
+ This emits when ownership of a contract changes. +
+ +
+ Signature: + +{`event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error OwnerAlreadyRenounced(); + +
+
+ + +
+ Signature: + +error OwnerUnauthorizedAccount(); + +
+
+
+ + + + +## Best Practices + + +- Ensure access control is enforced by the calling facet before invoking `setContractOwner`. +- Verify that the `OwnerStorage` struct and its storage position are correctly configured within the diamond's storage layout. +- Handle `OwnerUnauthorizedAccount` and `OwnerAlreadyRenounced` errors if applicable to your facet's logic. + + +## Integration Notes + + +This module manages ownership state within the diamond's shared storage. It uses the `OwnerStorage` struct, located at the `STORAGE_POSITION` slot, identified by `keccak256("erc173.owner")`. The `owner` address is stored here. Any facet that accesses this storage position will see the current owner address. Functions like `owner()` and `requireOwner()` directly read from this slot, ensuring consistent ownership information across the diamond. + + +
+ +
+ + diff --git a/website/docs/library/access/Owner/Data/_category_.json b/website/docs/library/access/Owner/Data/_category_.json new file mode 100644 index 00000000..87431689 --- /dev/null +++ b/website/docs/library/access/Owner/Data/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Data", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/Owner/Data/index" + } +} diff --git a/website/docs/library/access/Owner/Data/index.mdx b/website/docs/library/access/Owner/Data/index.mdx new file mode 100644 index 00000000..5a0c8d2e --- /dev/null +++ b/website/docs/library/access/Owner/Data/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Data" +description: "Data components for Compose 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'; + + + Data components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/Owner/Renounce/OwnerRenounceFacet.mdx b/website/docs/library/access/Owner/Renounce/OwnerRenounceFacet.mdx new file mode 100644 index 00000000..2e4b5171 --- /dev/null +++ b/website/docs/library/access/Owner/Renounce/OwnerRenounceFacet.mdx @@ -0,0 +1,161 @@ +--- +sidebar_position: 100 +title: "Owner Renounce Facet" +description: "Renounce diamond ownership" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/Owner/Renounce/OwnerRenounceFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Renounce diamond ownership + + + +- Renounces diamond ownership via an external function. +- Emits `OwnershipTransferred` event upon successful renouncement. +- Interacts with shared diamond storage for owner management. +- Exports selectors for facet discovery. + + +## Overview + +This facet provides functionality to renounce ownership of a diamond. It allows the current owner to permanently relinquish their ownership rights, making the diamond effectively unowned. This facet interacts with diamond storage to manage the owner state. + +## Storage + +### OwnerStorage + + +{`struct OwnerStorage { + address owner; +}`} + + +### State Variables + + + +## Functions + +### renounceOwnership + + +{`function renounceOwnership() external;`} + + +--- +### exportSelectors + +Exports the function selectors of the OwnerRenounceFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + + +
+ Signature: + +{`event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error OwnerUnauthorizedAccount(); + +
+
+
+ + + + +## Best Practices + + +- Call `renounceOwnership` only when permanent relinquishment of ownership is intended. +- Ensure the caller is the current owner before executing `renounceOwnership`. +- Verify that the `OwnershipTransferred` event is emitted after successful execution. + + +## Security Considerations + + +The `renounceOwnership` function can only be called by the current owner. If called, ownership is permanently relinquished, and the owner address in storage becomes zero. There is no mechanism to reclaim ownership after this action. The `OwnerUnauthorizedAccount` error is reverted if the caller is not the owner. Follow standard Solidity security practices for input validation and access control. + + +
+ +
+ + diff --git a/website/docs/library/access/Owner/Renounce/OwnerRenounceMod.mdx b/website/docs/library/access/Owner/Renounce/OwnerRenounceMod.mdx new file mode 100644 index 00000000..c44c3f2e --- /dev/null +++ b/website/docs/library/access/Owner/Renounce/OwnerRenounceMod.mdx @@ -0,0 +1,169 @@ +--- +sidebar_position: 110 +title: "Owner Renounce Module" +description: "Renounces contract ownership to address(0)" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/Owner/Renounce/OwnerRenounceMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Renounces contract ownership to address(0) + + + +- Provides ERC-173 ownership renouncement functionality. +- Sets owner to address(0) to disable owner-specific access. +- Uses diamond storage pattern via `STORAGE_POSITION`. +- Internal functions for facet integration. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides logic to renounce ownership of a contract, setting the owner to address(0). This action disables all functions restricted to the owner. Facets import this module to manage ownership transitions within the diamond's shared storage. + +## Storage + +### OwnerStorage + +storage-location: erc8042:erc173.owner + + +{`struct OwnerStorage { + address owner; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns a pointer to the ERC-173 storage struct. Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + + +{`function getStorage() pure returns (OwnerStorage storage s);`} + + +**Returns:** + + + +--- +### renounceOwnership + +Renounce ownership of the contract. Sets the owner to address(0), disabling all functions restricted to the owner. + + +{`function renounceOwnership() ;`} + + +## Events + + + +
+ This emits when ownership of a contract changes. +
+ +
+ Signature: + +{`event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error OwnerUnauthorizedAccount(); + +
+
+
+ + + + +## Best Practices + + +- Call `renounceOwnership` only when a permanent transfer of control is intended. +- Ensure all critical owner-only functions are migrated or disabled before renouncing. +- Verify that the diamond's storage layout for ownership is compatible with ERC-173 semantics. + + +## Integration Notes + + +This module interacts with diamond storage at the `STORAGE_POSITION` defined for ERC-173 ownership. It reads and writes the `owner` field within the `OwnerStorage` struct. When `renounceOwnership` is called, the `owner` state variable in this storage slot is updated to `address(0)`, making it immediately visible to all facets accessing the same storage slot. This action effectively revokes all owner privileges. + + +
+ +
+ + diff --git a/website/docs/library/access/Owner/Renounce/_category_.json b/website/docs/library/access/Owner/Renounce/_category_.json new file mode 100644 index 00000000..eb22f744 --- /dev/null +++ b/website/docs/library/access/Owner/Renounce/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Renounce", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/Owner/Renounce/index" + } +} diff --git a/website/docs/library/access/Owner/Renounce/index.mdx b/website/docs/library/access/Owner/Renounce/index.mdx new file mode 100644 index 00000000..d97c338d --- /dev/null +++ b/website/docs/library/access/Owner/Renounce/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Renounce" +description: "Renounce components for Compose 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'; + + + Renounce components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/Owner/Transfer/OwnerTransferFacet.mdx b/website/docs/library/access/Owner/Transfer/OwnerTransferFacet.mdx new file mode 100644 index 00000000..2292315b --- /dev/null +++ b/website/docs/library/access/Owner/Transfer/OwnerTransferFacet.mdx @@ -0,0 +1,193 @@ +--- +sidebar_position: 100 +title: "Owner Transfer Facet" +description: "Manages diamond ownership and transfers" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/Owner/Transfer/OwnerTransferFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages diamond ownership and transfers + + + +- Manages diamond ownership via external functions. +- Leverages diamond storage for owner state. +- Provides `exportSelectors` for facet discovery. +- Compatible with ERC-2535 diamond standard. + + +## Overview + +This facet provides external functions for managing the ownership of a Compose diamond. It enables transferring ownership and renouncing it, interacting with diamond storage for owner information. Developers integrate this facet to establish clear ownership and control within their diamond architecture. + +## Storage + +### OwnerStorage + + +{`struct OwnerStorage { + address owner; +}`} + + +### State Variables + + + +## Functions + +### transferOwnership + +Set the address of the new owner of the contract. Set _newOwner to address(0) to renounce any ownership. + + +{`function transferOwnership(address _newOwner) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the function selectors of the OwnerTransferFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + + +
+ Signature: + +{`event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error OwnerUnauthorizedAccount(); + +
+
+
+ + + + +## Best Practices + + +- Initialize the owner address during diamond deployment. +- Enforce access control for state-changing functions like `transferOwnership`. +- Verify compatibility with `OwnerDataFacet` and `OwnerRenounceFacet` when composing access control functionalities. + + +## Security Considerations + + +State-changing functions, particularly `transferOwnership`, must be protected by appropriate access control mechanisms (e.g., `Ownable` or role-based access control) to prevent unauthorized ownership changes. Ensure the caller has the necessary permissions before executing `transferOwnership`. Renouncing ownership by setting the new owner to `address(0)` is irreversible. Follow standard Solidity security practices for input validation and reentrancy. + + +
+ +
+ + diff --git a/website/docs/library/access/Owner/Transfer/OwnerTransferMod.mdx b/website/docs/library/access/Owner/Transfer/OwnerTransferMod.mdx new file mode 100644 index 00000000..f690a567 --- /dev/null +++ b/website/docs/library/access/Owner/Transfer/OwnerTransferMod.mdx @@ -0,0 +1,206 @@ +--- +sidebar_position: 110 +title: "Owner Transfer Module" +description: "Manages ERC-173 ownership transfer and renouncement" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/Owner/Transfer/OwnerTransferMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages ERC-173 ownership transfer and renouncement + + + +- Manages ERC-173 ownership transfers and renouncements. +- Operates on shared diamond storage, ensuring cross-facet visibility. +- Provides `internal` functions for integration within custom facets. +- Reverts with `OwnerUnauthorizedAccount` if ownership rules are violated. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides core ERC-173 ownership transfer logic, enabling diamonds to manage ownership securely. Facets can use its internal functions to transfer ownership or renounce it, ensuring clear accountability within the diamond. Changes to ownership are immediately reflected across all facets sharing the same diamond storage. + +## Storage + +### OwnerStorage + +storage-location: erc8042:erc173.owner + + +{`struct OwnerStorage { + address owner; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns a pointer to the ERC-173 storage struct. Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + + +{`function getStorage() pure returns (OwnerStorage storage s);`} + + +**Returns:** + + + +--- +### transferOwnership + +Set the address of the new owner of the contract. Set _newOwner to address(0) to renounce any ownership. + + +{`function transferOwnership(address _newOwner) ;`} + + +**Parameters:** + + + +## Events + + + +
+ This emits when ownership of a contract changes. +
+ +
+ Signature: + +{`event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error OwnerUnauthorizedAccount(); + +
+
+
+ + + + +## Best Practices + + +- Ensure ownership is transferred or renounced only by the current owner. +- Call `transferOwnership` with `address(0)` to safely renounce ownership. +- Use `getStorage` to inspect ownership state, ensuring consistency across facets. + + +## Integration Notes + + +This module utilizes diamond storage at the `STORAGE_POSITION` slot, identified by `keccak256("erc173.owner")`, to manage the `OwnerStorage` struct. The `owner` field within this struct represents the diamond's owner. All functions operate internally and directly on this shared storage, making ownership changes immediately visible to any facet that reads from the same storage slot. + + +
+ +
+ + diff --git a/website/docs/library/access/Owner/Transfer/_category_.json b/website/docs/library/access/Owner/Transfer/_category_.json new file mode 100644 index 00000000..ba44c5d8 --- /dev/null +++ b/website/docs/library/access/Owner/Transfer/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Transfer", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/Owner/Transfer/index" + } +} diff --git a/website/docs/library/access/Owner/Transfer/index.mdx b/website/docs/library/access/Owner/Transfer/index.mdx new file mode 100644 index 00000000..6b4a6fba --- /dev/null +++ b/website/docs/library/access/Owner/Transfer/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Transfer" +description: "Transfer components for Compose 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'; + + + Transfer components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.mdx b/website/docs/library/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.mdx new file mode 100644 index 00000000..cb66ac3b --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.mdx @@ -0,0 +1,176 @@ +--- +sidebar_position: 100 +title: "Owner Two Step Data Facet" +description: "Manages pending owner state for two-step ownership transfers" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages pending owner state for two-step ownership transfers + + + +- Exposes external `pendingOwner` function for read-only access to ownership state. +- Provides internal `getStorage` function for facets to access shared storage. +- Includes `exportSelectors` for diamond discovery and management. +- Operates within the Compose diamond standard, utilizing shared diamond storage. + + +## Overview + +This facet provides access to the pending owner state within a diamond. It exposes functions to retrieve the pending owner address and access internal storage structures. Developers integrate this facet to manage ownership transitions in a two-step process, ensuring clarity and security. + +## Storage + +### PendingOwnerStorage + + +{`struct PendingOwnerStorage { + address pendingOwner; +}`} + + +### State Variables + + + +## Functions + +### pendingOwner + +Get the address of the pending owner + + +{`function pendingOwner() external view returns (address);`} + + +**Returns:** + + + +--- +### exportSelectors + +Exports the function selectors of the OwnerTwoStepDataFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + + + + +## Best Practices + + +- Initialize the `pendingOwner` to the zero address before the first transfer. +- Ensure other facets that manage ownership (e.g., transfer or renounce) properly interact with the state managed by this facet. +- When upgrading, verify that the `STORAGE_POSITION` for `PendingOwnerStorage` remains consistent or is handled correctly during the upgrade process. + + +## Security Considerations + + +The `pendingOwner` function is a `view` function and poses no direct reentrancy risk. The `exportSelectors` function is `pure` and also poses no risk. The `getStorage` function uses inline assembly to access storage; ensure the `STORAGE_POSITION` defined for `PendingOwnerStorage` is correctly managed and does not conflict with other storage slots within the diamond. + + +
+ +
+ + diff --git a/website/docs/library/access/Owner/TwoSteps/Data/OwnerTwoStepDataMod.mdx b/website/docs/library/access/Owner/TwoSteps/Data/OwnerTwoStepDataMod.mdx new file mode 100644 index 00000000..f2426a75 --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/Data/OwnerTwoStepDataMod.mdx @@ -0,0 +1,169 @@ +--- +sidebar_position: 110 +title: "Owner Two Step Data Module" +description: "Provides data for ERC-173 two-step ownership transfers" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/Owner/TwoSteps/Data/OwnerTwoStepDataMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Provides data for ERC-173 two-step ownership transfers + + + +- Exposes `internal` functions for managing ownership data. +- Utilizes diamond storage at a specific `STORAGE_POSITION` for shared state. +- Provides access to the `PendingOwnerStorage` struct. +- No external dependencies, allowing for straightforward integration into any diamond. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module exposes internal functions to manage the data for ERC-173 two-step ownership transfers. Facets can import this module to access and modify pending ownership states using shared diamond storage. Changes made through this module are immediately visible to all facets using the same storage pattern, ensuring consistent ownership state across the diamond. + +## Storage + +### PendingOwnerStorage + +storage-location: erc8042:erc173.owner.pending + + +{`struct PendingOwnerStorage { + address pendingOwner; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns a pointer to the PendingOwner storage struct. Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + + +{`function getStorage() pure returns (PendingOwnerStorage storage s);`} + + +**Returns:** + + + +--- +### pendingOwner + +Get the address of the pending owner + + +{`function pendingOwner() view returns (address);`} + + +**Returns:** + + + + + + +## Best Practices + + +- Ensure that ownership-related facets correctly initialize this module's storage slot. +- Always call `pendingOwner()` to retrieve the current pending owner address for read operations. +- When implementing ownership transfer logic, coordinate with `OwnerTransferMod` and `OwnerRenounceMod` to manage the complete two-step process. + + +## Integration Notes + + +This module interacts with diamond storage at the slot identified by `STORAGE_POSITION`, which is deterministically set using `keccak256(\"erc173.owner.pending\")`. The `getStorage()` function returns a pointer to the `PendingOwnerStorage` struct, allowing other facets to read or write to this shared state. The `pendingOwner()` function directly accesses this storage to return the address of the pending owner. All state changes made via this module are immediately visible to any other facet that references the same storage position. + + +
+ +
+ + diff --git a/website/docs/library/access/Owner/TwoSteps/Data/_category_.json b/website/docs/library/access/Owner/TwoSteps/Data/_category_.json new file mode 100644 index 00000000..92faf1e4 --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/Data/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Data", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/Owner/TwoSteps/Data/index" + } +} diff --git a/website/docs/library/access/Owner/TwoSteps/Data/index.mdx b/website/docs/library/access/Owner/TwoSteps/Data/index.mdx new file mode 100644 index 00000000..5423f8d7 --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/Data/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Data" +description: "Data components for Compose 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'; + + + Data components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceFacet.mdx b/website/docs/library/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceFacet.mdx new file mode 100644 index 00000000..2ec5b6fe --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceFacet.mdx @@ -0,0 +1,181 @@ +--- +sidebar_position: 100 +title: "Owner Two Step Renounce Facet" +description: "Two-step ownership renouncement for diamonds" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Two-step ownership renouncement for diamonds + + + +- Implements a two-step ownership renouncement process. +- Manages owner and pending owner state via diamond storage. +- Provides `exportSelectors` for diamond integration. +- No external dependencies beyond diamond storage access. + + +## Overview + +This facet provides a two-step process for renouncing ownership of a diamond contract. It interacts with shared diamond storage to manage the owner and pending owner states. Developers integrate this facet to enable secure ownership transfer management within a diamond. + +## Storage + +### OwnerStorage + + +{`struct OwnerStorage { + address owner; +}`} + + +--- +### PendingOwnerStorage + + +{`struct PendingOwnerStorage { + address pendingOwner; +}`} + + +### State Variables + + + +## Functions + +### renounceOwnership + + +{`function renounceOwnership() external;`} + + +--- +### exportSelectors + +Exports the function selectors of the OwnerTwoStepRenounceFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + + +
+ Signature: + +{`event OwnershipTransferred(address indexed _previousOwner, address indexed _newOwner);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error OwnerUnauthorizedAccount(); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `OwnerTwoStepRenounceFacet` is correctly deployed and added to the diamond before calling its functions. +- The `renounceOwnership` function should only be called by the current owner. +- Use `exportSelectors` to programmatically discover the facet's supported functions. + + +## Security Considerations + + +The `renounceOwnership` function is protected by an implicit access control mechanism tied to the diamond's owner. Calling `renounceOwnership` will transfer ownership to a zero address after a delay, making the contract effectively un-ownable. Ensure the current owner calls this function only when intended. Reentrancy is not a concern as there are no external calls. Input validation is minimal, relying on the EVM's address type. The function uses inline assembly to access specific storage slots for owner and pending owner data. + + +
+ +
+ + diff --git a/website/docs/library/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceMod.mdx b/website/docs/library/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceMod.mdx new file mode 100644 index 00000000..475bf845 --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceMod.mdx @@ -0,0 +1,241 @@ +--- +sidebar_position: 110 +title: "Owner Two Step Renounce Module" +description: "Two-step ownership renouncement with ERC-173 logic" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Two-step ownership renouncement with ERC-173 logic + + + +- Implements a two-step ownership renouncement pattern. +- Uses internal functions for direct interaction with diamond storage. +- Emits an `OwnershipTransferred` event upon successful renouncement. +- Compatible with ERC-2535 diamonds and the shared storage pattern. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module implements a two-step ownership renouncement process, adhering to ERC-173 semantics. It provides internal functions to manage ownership transitions, ensuring that functions restricted to the owner are disabled only after a confirmed two-step process. This approach enhances security by preventing accidental or malicious immediate ownership loss. + +## Storage + +### OwnerStorage + +storage-location: erc8042:erc173.owner + + +{`struct OwnerStorage { + address owner; +}`} + + +--- +### PendingOwnerStorage + +storage-location: erc8042:erc173.owner.pending + + +{`struct PendingOwnerStorage { + address pendingOwner; +}`} + + +### State Variables + + + +## Functions + +### getOwnerStorage + +Returns a pointer to the ERC-173 storage struct. Uses inline assembly to access the storage slot defined by OWNER_STORAGE_POSITION. + + +{`function getOwnerStorage() pure returns (OwnerStorage storage s);`} + + +**Returns:** + + + +--- +### getPendingOwnerStorage + +Returns a pointer to the PendingOwner storage struct. Uses inline assembly to access the storage slot defined by PENDING_OWNER_STORAGE_POSITION. + + +{`function getPendingOwnerStorage() pure returns (PendingOwnerStorage storage s);`} + + +**Returns:** + + + +--- +### renounceOwnership + +Renounce ownership of the contract. Sets the owner to address(0) and clears any pending owner, disabling all functions restricted to the owner. + + +{`function renounceOwnership() ;`} + + +## Events + + + +
+ This emits when ownership of a contract changes. +
+ +
+ Signature: + +{`event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error OwnerUnauthorizedAccount(); + +
+
+
+ + + + +## Best Practices + + +- Ensure the caller has the appropriate permissions before invoking state-changing functions. +- Verify that the `OWNER_STORAGE_POSITION` and `PENDING_OWNER_STORAGE_POSITION` are correctly configured in the diamond's storage layout. +- Handle the `OwnerUnauthorizedAccount` error if the caller is not the authorized account for ownership operations. + + +## Integration Notes + + +This module interacts with diamond storage at the slots defined by `OWNER_STORAGE_POSITION` and `PENDING_OWNER_STORAGE_POSITION`. The `renounceOwnership` function directly modifies the `owner` field within the `OwnerStorage` struct located at `OWNER_STORAGE_POSITION`, setting it to `address(0)` and clearing any pending owner. These changes are immediately visible to all facets that access the same storage slots. + + +
+ +
+ + diff --git a/website/docs/library/access/Owner/TwoSteps/Renounce/_category_.json b/website/docs/library/access/Owner/TwoSteps/Renounce/_category_.json new file mode 100644 index 00000000..73650c3c --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/Renounce/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Renounce", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/Owner/TwoSteps/Renounce/index" + } +} diff --git a/website/docs/library/access/Owner/TwoSteps/Renounce/index.mdx b/website/docs/library/access/Owner/TwoSteps/Renounce/index.mdx new file mode 100644 index 00000000..587fc29b --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/Renounce/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Renounce" +description: "Renounce components for Compose 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'; + + + Renounce components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferFacet.mdx b/website/docs/library/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferFacet.mdx new file mode 100644 index 00000000..76f7ceb8 --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferFacet.mdx @@ -0,0 +1,214 @@ +--- +sidebar_position: 100 +title: "Owner Two Step Transfer Facet" +description: "Manages ownership transfer via a two-step process" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages ownership transfer via a two-step process + + + +- Exposes external functions for diamond routing. +- Manages ownership transfer through a two-step verification process. +- Operates on shared diamond storage using inline assembly. +- Provides a mechanism to export facet selectors. + + +## Overview + +This facet implements a two-step ownership transfer mechanism for a diamond. It exposes external functions for initiating and accepting ownership changes, operating on shared diamond storage. Developers integrate this facet to manage diamond ownership securely and upgradeably. + +## Storage + +### OwnerStorage + + +{`struct OwnerStorage { + address owner; +}`} + + +--- +### PendingOwnerStorage + + +{`struct PendingOwnerStorage { + address pendingOwner; +}`} + + +### State Variables + + + +## Functions + +### transferOwnership + +Set the address of the new owner of the contract + + +{`function transferOwnership(address _newOwner) external;`} + + +**Parameters:** + + + +--- +### acceptOwnership + + +{`function acceptOwnership() external;`} + + +--- +### exportSelectors + +Exports the function selectors of the OwnerTwoStepTransferFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + + +
+ Signature: + +{`event OwnershipTransferStarted(address indexed _previousOwner, address indexed _newOwner);`} + +
+ +
+ + +
+ Signature: + +{`event OwnershipTransferred(address indexed _previousOwner, address indexed _newOwner);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error OwnerUnauthorizedAccount(); + +
+
+
+ + + + +## Best Practices + + +- Initialize the owner address during diamond deployment. +- Ensure the `transferOwnership` function is called by the current owner. +- Verify that the `acceptOwnership` function is called by the pending owner address. + + +## Security Considerations + + +The `transferOwnership` function is restricted to the current owner via internal access control checks within the facet. The `acceptOwnership` function requires the caller to be the pending owner. No reentrancy guards are explicitly present; follow standard Solidity reentrancy best practices for external calls within the diamond. + + +
+ +
+ + diff --git a/website/docs/library/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferMod.mdx b/website/docs/library/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferMod.mdx new file mode 100644 index 00000000..cee3d0e3 --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferMod.mdx @@ -0,0 +1,309 @@ +--- +sidebar_position: 110 +title: "Owner Two Step Transfer Module" +description: "Two-step ERC-173 ownership transfer logic" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Two-step ERC-173 ownership transfer logic + + + +- Implements ERC-173 two-step ownership transfer logic. +- All functions are `internal`, intended for use by other facets within a diamond. +- Utilizes diamond storage pattern for ownership state management. +- Emits `OwnershipTransferStarted` and `OwnershipTransferred` events for off-chain monitoring. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for initiating and accepting ERC-173 ownership transfers. Facets can integrate this module to manage ownership changes within the diamond. Ownership changes are finalized through a two-step process, ensuring that the new owner explicitly accepts the transfer. + +## Storage + +### OwnerStorage + +storage-location: erc8042:erc173.owner + + +{`struct OwnerStorage { + address owner; +}`} + + +--- +### PendingOwnerStorage + +storage-location: erc8042:erc173.owner.pending + + +{`struct PendingOwnerStorage { + address pendingOwner; +}`} + + +### State Variables + + + +## Functions + +### acceptOwnership + +Finalizes ownership transfer. Only the pending owner can call this function. + + +{`function acceptOwnership() ;`} + + +--- +### getOwnerStorage + +Returns a pointer to the Owner storage struct. Uses inline assembly to access the storage slot defined by OWNER_STORAGE_POSITION. + + +{`function getOwnerStorage() pure returns (OwnerStorage storage s);`} + + +**Returns:** + + + +--- +### getPendingOwnerStorage + +Returns a pointer to the PendingOwner storage struct. Uses inline assembly to access the storage slot defined by PENDING_OWNER_STORAGE_POSITION. + + +{`function getPendingOwnerStorage() pure returns (PendingOwnerStorage storage s);`} + + +**Returns:** + + + +--- +### transferOwnership + +Initiates a two-step ownership transfer. + + +{`function transferOwnership(address _newOwner) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when ownership transfer is initiated (pending owner set). +
+ +
+ Signature: + +{`event OwnershipTransferStarted(address indexed _previousOwner, address indexed _newOwner);`} + +
+ +
+ +
+ Emitted when ownership transfer is finalized. +
+ +
+ Signature: + +{`event OwnershipTransferred(address indexed _previousOwner, address indexed _newOwner);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error OwnerUnauthorizedAccount(); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `transferOwnership` function is called only by the current owner to prevent unauthorized transfer initiation. +- Verify that `acceptOwnership` is called by the correct pending owner before finalizing the transfer. +- Handle the `OwnerUnauthorizedAccount` error, which is emitted when unauthorized accounts attempt to perform ownership-related actions. + + +## Integration Notes + + +This module interacts with two primary storage slots within the diamond's shared storage: `OWNER_STORAGE_POSITION` for the current owner and `PENDING_OWNER_STORAGE_POSITION` for the pending owner. The `getOwnerStorage` and `getPendingOwnerStorage` functions provide direct access pointers to these storage locations using inline assembly. Changes made via `transferOwnership` and `acceptOwnership` modify these shared storage slots, making them immediately visible to any other facet that accesses the same storage positions. + + +
+ +
+ + diff --git a/website/docs/library/access/Owner/TwoSteps/Transfer/_category_.json b/website/docs/library/access/Owner/TwoSteps/Transfer/_category_.json new file mode 100644 index 00000000..0c84aa06 --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/Transfer/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Transfer", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/Owner/TwoSteps/Transfer/index" + } +} diff --git a/website/docs/library/access/Owner/TwoSteps/Transfer/index.mdx b/website/docs/library/access/Owner/TwoSteps/Transfer/index.mdx new file mode 100644 index 00000000..59ac2337 --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/Transfer/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Transfer" +description: "Transfer components for Compose 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'; + + + Transfer components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/Owner/TwoSteps/_category_.json b/website/docs/library/access/Owner/TwoSteps/_category_.json new file mode 100644 index 00000000..e26b43cf --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Two Steps", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/Owner/TwoSteps/index" + } +} diff --git a/website/docs/library/access/Owner/TwoSteps/index.mdx b/website/docs/library/access/Owner/TwoSteps/index.mdx new file mode 100644 index 00000000..0eb4f4e0 --- /dev/null +++ b/website/docs/library/access/Owner/TwoSteps/index.mdx @@ -0,0 +1,36 @@ +--- +title: "Two Steps" +description: "Two Steps components for Compose 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'; + + + Two Steps components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/Owner/_category_.json b/website/docs/library/access/Owner/_category_.json new file mode 100644 index 00000000..2ddf56c9 --- /dev/null +++ b/website/docs/library/access/Owner/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Owner", + "position": 1, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/Owner/index" + } +} diff --git a/website/docs/library/access/Owner/index.mdx b/website/docs/library/access/Owner/index.mdx new file mode 100644 index 00000000..9d2e1951 --- /dev/null +++ b/website/docs/library/access/Owner/index.mdx @@ -0,0 +1,43 @@ +--- +title: "Owner" +description: "Single-owner access control pattern." +--- + +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'; + + + Single-owner access control pattern. + + + + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/access/_category_.json b/website/docs/library/access/_category_.json new file mode 100644 index 00000000..cbc9d5ba --- /dev/null +++ b/website/docs/library/access/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Access Control", + "position": 2, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/access/index" + } +} diff --git a/website/docs/library/access/index.mdx b/website/docs/library/access/index.mdx new file mode 100644 index 00000000..6bc84f0d --- /dev/null +++ b/website/docs/library/access/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Access Control" +description: "Access control patterns for permission management in Compose 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'; + + + Access control patterns for permission management in Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/diamond/DiamondInspectFacet.mdx b/website/docs/library/diamond/DiamondInspectFacet.mdx new file mode 100644 index 00000000..3a49044d --- /dev/null +++ b/website/docs/library/diamond/DiamondInspectFacet.mdx @@ -0,0 +1,292 @@ +--- +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 Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Inspect diamond facets and selectors + + + +- Provides functions for querying facet addresses by selector. +- Enables retrieval of all registered facet addresses and their selectors. +- Includes `exportSelectors` for mechanism discovery. +- Operates on shared diamond storage via `DIAMOND_STORAGE_POSITION`. + + +## Overview + +This facet provides essential introspection capabilities for diamonds. It exposes functions to query facet addresses, their associated function selectors, and a comprehensive list of all registered facets. Developers integrate this facet to understand the diamond's internal structure and composition programmatically. + +## Storage + +### FacetNode + + +{`struct FacetNode { + address facet; + bytes4 prevFacetNodeId; + bytes4 nextFacetNodeId; +}`} + + +--- +### FacetList + + +{`struct FacetList { + bytes4 headFacetNodeId; + bytes4 tailFacetNodeId; + uint32 facetCount; + uint32 selectorCount; +}`} + + +--- +### DiamondStorage + + +{`struct DiamondStorage { + mapping(bytes4 functionSelector => FacetNode) facetNodes; + FacetList facetList; +}`} + + +--- +### Facet + + +{`struct Facet { + address facet; + bytes4[] functionSelectors; +}`} + + +### State Variables + + + +## 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 that are handled by the given facet. If facet is not found return empty array. + + +{`function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetSelectors);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### facetAddresses + +Gets the facet addresses used by the diamond. If no facets are registered return 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:** + + + +--- +### exportSelectors + +Exports the function selectors of the DiamondInspectFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + + + + +## Best Practices + + +- Integrate this facet to enable external inspection of diamond facet mappings. +- Use `facetAddress` to determine which facet handles a specific function selector. +- Utilize `facets` and `facetFunctionSelectors` for comprehensive diamond structure analysis. + + +## Security Considerations + + +All functions in this facet are view or pure, posing no direct reentrancy or state-changing risks. Input validation for `facetAddress` and `facetFunctionSelectors` is handled by the diamond proxy's dispatch mechanism. Follow standard Solidity security practices. + + +
+ +
+ + diff --git a/website/docs/library/diamond/DiamondMod.mdx b/website/docs/library/diamond/DiamondMod.mdx new file mode 100644 index 00000000..3a11efc4 --- /dev/null +++ b/website/docs/library/diamond/DiamondMod.mdx @@ -0,0 +1,442 @@ +--- +sidebar_position: 1 +title: "Diamond Module" +description: "Internal functions and storage for diamond proxy functionality." +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/diamond/DiamondMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Internal functions and storage for diamond proxy functionality. + + + +- Exposes internal functions for diamond proxy operations. +- Manages diamond storage using a dedicated storage slot (`DIAMOND_STORAGE_POSITION`). +- Supports facet registration and retrieval through internal mechanisms. +- No external dependencies, ensuring minimal on-chain footprint. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides core internal functions and storage management for diamond proxies. It enables facets to interact with shared diamond storage and manage facet registrations. Changes made through this module are immediately visible to all facets utilizing the diamond storage pattern. + +## Storage + +### DiamondStorage + +storage-location: erc8042:erc8153.diamond + + +{`struct DiamondStorage { + mapping(bytes4 functionSelector => FacetNode) facetNodes; + FacetList facetList; +}`} + + +--- +### FacetList + + +{`struct FacetList { + bytes4 headFacetNodeId; + bytes4 tailFacetNodeId; + uint32 facetCount; + uint32 selectorCount; +}`} + + +--- +### FacetNode + + +{`struct FacetNode { + address facet; + bytes4 prevFacetNodeId; + bytes4 nextFacetNodeId; +}`} + + +### State Variables + + + +## Functions + +### addFacets + + +{`function addFacets(address[] memory _facets) ;`} + + +**Parameters:** + + + +--- +### at + + +{`function at(bytes memory selectors, uint256 index) pure returns (bytes4 selector);`} + + +**Parameters:** + + + +--- +### diamondFallback + +Find facet for function that is called and execute the function if a facet is found and return any value. + + +{`function diamondFallback() ;`} + + +--- +### getDiamondStorage + + +{`function getDiamondStorage() pure returns (DiamondStorage storage s);`} + + +--- +### importSelectors + + +{`function importSelectors(address _facet) view returns (bytes memory selectors);`} + + +**Parameters:** + + + +## Events + + + +
+ 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: + +
+
+ +
+ 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: + +
+
+
+ +## Errors + + + + +
+ Signature: + +error CannotAddFunctionToDiamondThatAlreadyExists(bytes4 _selector); + +
+
+ + +
+ Signature: + +error FunctionNotFound(bytes4 _selector); + +
+
+ + +
+ Signature: + +error FunctionSelectorsCallFailed(address _facet); + +
+
+ + +
+ Signature: + +error IncorrectSelectorsEncoding(address _facet); + +
+
+ + +
+ Signature: + +error NoBytecodeAtAddress(address _contractAddress); + +
+
+ +
+ The upgradeDiamond function below detects and reverts with the following errors. +
+ +
+ 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 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")`. The `DiamondStorage` struct, which includes a `FacetList` field, resides here. 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..404eb8a0 --- /dev/null +++ b/website/docs/library/diamond/DiamondUpgradeFacet.mdx @@ -0,0 +1,505 @@ +--- +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 Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Diamond upgrade and management facet + + + +- Manages facet lifecycle (add, replace, remove) within a diamond. +- Supports optional `delegatecall` for post-upgrade operations. +- Emits events for all facet changes and delegate calls. +- Provides selector discovery mechanism via `exportSelectors`. + + +## Overview + +This facet provides core diamond upgrade functionality, enabling the addition, replacement, and removal of facets. It orchestrates these operations through the diamond proxy and can optionally perform delegate calls for initialization or state modification. The facet also supports exporting its selectors for discovery. + +## Storage + +### OwnerStorage + + +{`struct OwnerStorage { + address owner; +}`} + + +--- +### FacetNode + + +{`struct FacetNode { + address facet; + bytes4 prevFacetNodeId; + bytes4 nextFacetNodeId; +}`} + + +--- +### FacetList + + +{`struct FacetList { + bytes4 headFacetNodeId; + bytes4 tailFacetNodeId; + uint32 facetCount; + uint32 selectorCount; +}`} + + +--- +### DiamondStorage + + +{`struct DiamondStorage { + mapping(bytes4 functionSelector => FacetNode) facetNodes; + FacetList facetList; +}`} + + +--- +### FacetReplacement + + +{`struct FacetReplacement { + address oldFacet; + address newFacet; +}`} + + +### State Variables + + + +## Functions + +### upgradeDiamond + +Upgrade the diamond by adding, replacing, or removing facets. Facets are added first, then replaced, then removed. These events are emitted to record changes to facets: - `FacetAdded(address indexed _facet)` - `FacetReplaced(address indexed _oldFacet, address indexed _newFacet)` - `FacetRemoved(address indexed _facet)` If `_delegate` is non-zero, the diamond performs a `delegatecall` to `_delegate` using `_delegateCalldata`. The `DiamondDelegateCall` event is emitted. The `delegatecall` is done to alter a diamond's state or to initialize, modify, or remove state after an upgrade. However, if `_delegate` is zero, no `delegatecall` is made and no `DiamondDelegateCall` event is emitted. If _tag is non-zero or if _metadata.length > 0 then the `DiamondMetadata` event is emitted. + + +{`function upgradeDiamond( + address[] calldata _addFacets, + FacetReplacement[] calldata _replaceFacets, + address[] calldata _removeFacets, + address _delegate, + bytes calldata _delegateCalldata, + bytes32 _tag, + bytes calldata _metadata +) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the function selectors of the DiamondUpgradeFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**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 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 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 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 + + + + +
+ Signature: + +error OwnerUnauthorizedAccount(); + +
+
+ + +
+ Signature: + +error NoSelectorsForFacet(address _facet); + +
+
+ + +
+ Signature: + +error NoBytecodeAtAddress(address _contractAddress); + +
+
+ + +
+ Signature: + +error CannotAddFunctionToDiamondThatAlreadyExists(bytes4 _selector); + +
+
+ + +
+ Signature: + +error CannotRemoveFacetThatDoesNotExist(address _facet); + +
+
+ + +
+ Signature: + +error CannotReplaceFacetWithSameFacet(address _facet); + +
+
+ + +
+ Signature: + +error FacetToReplaceDoesNotExist(address _oldFacet); + +
+
+ + +
+ Signature: + +error DelegateCallReverted(address _delegate, bytes _delegateCalldata); + +
+
+ + +
+ Signature: + +error ExportSelectorsCallFailed(address _facet); + +
+
+ + +
+ Signature: + +error IncorrectSelectorsEncoding(address _facet); + +
+
+ + +
+ Signature: + +error CannotReplaceFunctionFromNonReplacementFacet(bytes4 _selector); + +
+
+
+ + + + +## Best Practices + + +- Ensure all diamond upgrade operations are performed by authorized accounts. +- Verify facet logic contracts have correct bytecode before adding or replacing. +- Safely manage the `delegatecall` functionality for initialization or state modification after an upgrade. + + +## Security Considerations + + +The `upgradeDiamond` function is critical and must be protected by robust access control to prevent unauthorized modifications. The facet uses `delegatecall`, which requires careful validation of the `_delegate` address and `_delegateCalldata` to prevent reentrancy or unintended state changes. Input validation for facet addresses and selector data is crucial. Potential risks include `OwnerUnauthorizedAccount`, `NoSelectorsForFacet`, `NoBytecodeAtAddress`, and `DelegateCallReverted` errors if inputs are invalid or delegate calls fail. + + +
+ +
+ + diff --git a/website/docs/library/diamond/DiamondUpgradeMod.mdx b/website/docs/library/diamond/DiamondUpgradeMod.mdx new file mode 100644 index 00000000..726e5392 --- /dev/null +++ b/website/docs/library/diamond/DiamondUpgradeMod.mdx @@ -0,0 +1,592 @@ +--- +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 Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Upgrade diamond by adding, replacing, or removing facets + + + +- Supports adding, replacing, and removing facets on a diamond. +- Emits `FacetAdded`, `FacetReplaced`, and `FacetRemoved` events for upgrade tracking. +- Optionally executes a `delegatecall` for complex initialization or state manipulation. +- Emits `DiamondMetadata` for tagged upgrades and associated data. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides core functions for managing diamond facets, enabling upgrades according to ERC-2535 and ERC-8153. It allows adding new facets, replacing existing ones, and removing facets. State changes are managed through diamond storage, ensuring visibility across all facets. Optionally, it supports delegate calls for complex initialization or state modifications during upgrades. + +## Storage + +### DiamondStorage + +storage-location: erc8042:erc8153.diamond + + +{`struct DiamondStorage { + mapping(bytes4 functionSelector => FacetNode) facetNodes; + FacetList facetList; +}`} + + +--- +### FacetList + + +{`struct FacetList { + bytes4 headFacetNodeId; + bytes4 tailFacetNodeId; + uint32 facetCount; + uint32 selectorCount; +}`} + + +--- +### FacetNode + + +{`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; +}`} + + +### State Variables + + + +## Functions + +### addFacets + + +{`function addFacets(address[] calldata _facets) ;`} + + +**Parameters:** + + + +--- +### at + + +{`function at(bytes memory selectors, uint256 index) pure returns (bytes4 selector);`} + + +**Parameters:** + + + +--- +### getDiamondStorage + + +{`function getDiamondStorage() pure returns (DiamondStorage storage s);`} + + +--- +### importSelectors + + +{`function importSelectors(address _facet) view returns (bytes memory selectors);`} + + +**Parameters:** + + + +--- +### removeFacets + + +{`function removeFacets(address[] calldata _facets) ;`} + + +**Parameters:** + + + +--- +### replaceFacets + + +{`function replaceFacets(FacetReplacement[] calldata _replaceFacets) ;`} + + +**Parameters:** + + + +--- +### upgradeDiamond + +Upgrade the diamond by adding, replacing, or removing facets. Facets are added first, then replaced, then removed. These events are emitted to record changes to facets: - `FacetAdded(address indexed _facet)` - `FacetReplaced(address indexed _oldFacet, address indexed _newFacet)` - `FacetRemoved(address indexed _facet)` If `_delegate` is non-zero, the diamond performs a `delegatecall` to `_delegate` using `_delegateCalldata`. The `DiamondDelegateCall` event is emitted. The `delegatecall` is done to alter a diamond's state or to initialize, modify, or remove state after an upgrade. However, if `_delegate` is zero, no `delegatecall` is made and no `DiamondDelegateCall` event is emitted. If _tag is non-zero or if _metadata.length > 0 then the `DiamondMetadata` event is emitted. + + +{`function upgradeDiamond( +address[] calldata _addFacets, +FacetReplacement[] calldata _replaceFacets, +address[] calldata _removeFacets, +address _delegate, +bytes calldata _delegateCalldata, +bytes32 _tag, +bytes calldata _metadata +) ;`} + + +**Parameters:** + + + +## Events + + + +
+ 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: + +
+
+ +
+ 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: + +
+
+
+ +## Errors + + + + +
+ Signature: + +error CannotAddFunctionToDiamondThatAlreadyExists(bytes4 _selector); + +
+
+ + +
+ Signature: + +error CannotRemoveFacetThatDoesNotExist(address _facet); + +
+
+ + +
+ Signature: + +error CannotReplaceFacetWithSameFacet(address _facet); + +
+
+ +
+ This error means that a function to replace exists in a facet other than the facet that was given to be replaced. +
+ +
+ Signature: + +error CannotReplaceFunctionFromNonReplacementFacet(bytes4 _selector); + +
+
+ + +
+ Signature: + +error DelegateCallReverted(address _delegate, bytes _delegateCalldata); + +
+
+ + +
+ Signature: + +error ExportSelectorsCallFailed(address _facet); + +
+
+ + +
+ Signature: + +error FacetToReplaceDoesNotExist(address _oldFacet); + +
+
+ + +
+ Signature: + +error IncorrectSelectorsEncoding(address _facet); + +
+
+ + +
+ Signature: + +error NoBytecodeAtAddress(address _contractAddress); + +
+
+ +
+ The upgradeDiamond function below detects and reverts with the following errors. +
+ +
+ Signature: + +error NoSelectorsForFacet(address _facet); + +
+
+
+ + + + +## Best Practices + + +- Ensure that the caller possesses the necessary permissions to perform diamond upgrades. +- Carefully review the `_delegate` and `_delegateCalldata` parameters to prevent unintended state changes or reentrancy risks during delegate calls. +- Verify that facet bytecode exists at the provided addresses before attempting to add or replace them to avoid `NoBytecodeAtAddress` errors. + + +## Integration Notes + + +This module interacts with the diamond's central storage, identified by `DIAMOND_STORAGE_POSITION` and conceptually mapped to `keccak256(\"erc8153.diamond\")`. It modifies the `facetList` within the `DiamondStorage` struct. Changes made through `upgradeDiamond`, `addFacets`, `replaceFacets`, and `removeFacets` are immediately visible to all facets that access this shared storage. The `upgradeDiamond` function orchestrates the sequence of operations: additions, then replacements, then removals. + + +
+ +
+ + 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/example/ExampleDiamond.mdx b/website/docs/library/diamond/example/ExampleDiamond.mdx new file mode 100644 index 00000000..ae6b532a --- /dev/null +++ b/website/docs/library/diamond/example/ExampleDiamond.mdx @@ -0,0 +1,133 @@ +--- +sidebar_position: 500 +title: "Example Diamond" +description: "Initializes a diamond contract with facets and owner" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/diamond/example/ExampleDiamond.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Initializes a diamond contract with facets and owner + + + +- Registers facet function selectors for diamond routing. +- Sets the initial diamond owner. +- Facilitates diamond initialization pattern. +- Requires explicit facet addresses and owner during deployment. + + +## Overview + +This contract initializes a diamond with provided facets and an owner address. It registers function selectors from each facet to enable delegatecall routing through the diamond proxy. Use this during diamond deployment to set up its initial functional capabilities. + +## Storage + +## Functions + +### constructor + +Initializes the diamond contract with facets, owner and other data. Adds all provided facets to the diamond's function selector mapping and sets the contract owner. Each facet in the array will have its function selectors registered to enable delegatecall routing. + + +{`constructor(address[] memory _facets, address _diamondOwner) ;`} + + +**Parameters:** + + + +--- +### fallback + + +{`fallback() external payable;`} + + +--- +### receive + + +{`receive() external payable;`} + + + + + +## Best Practices + + +- Call the `constructor` only once during diamond deployment. +- Ensure the `_facets` array contains valid, deployed facet contract addresses. +- The `_diamondOwner` should be a trusted address responsible for diamond management. + + +## Security Considerations + + +This contract is an initializer and is typically deployed once. Ensure the `_facets` array is populated correctly to avoid missing functionality. The `_diamondOwner` address should be secured, as it controls diamond upgrades and management. + + +
+ +
+ + diff --git a/website/docs/library/diamond/example/_category_.json b/website/docs/library/diamond/example/_category_.json new file mode 100644 index 00000000..7461e412 --- /dev/null +++ b/website/docs/library/diamond/example/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Examples", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/diamond/example/index" + } +} diff --git a/website/docs/library/diamond/example/index.mdx b/website/docs/library/diamond/example/index.mdx new file mode 100644 index 00000000..84652aa2 --- /dev/null +++ b/website/docs/library/diamond/example/index.mdx @@ -0,0 +1,22 @@ +--- +title: "example" +description: "example components for Compose 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'; + + + example components for Compose diamonds. + + + + } + size="medium" + /> + diff --git a/website/docs/library/diamond/index.mdx b/website/docs/library/diamond/index.mdx new file mode 100644 index 00000000..2a106b6b --- /dev/null +++ b/website/docs/library/diamond/index.mdx @@ -0,0 +1,50 @@ +--- +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-2535 diamonds. + + + + } + size="medium" + /> + } + 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..a664d292 --- /dev/null +++ b/website/docs/library/index.mdx @@ -0,0 +1,51 @@ +--- +title: "Library" +description: "API reference for all Compose modules and facets." +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'; + + + API reference for all Compose modules and facets. + + + + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/interfaceDetection/ERC165/ERC165Facet.mdx b/website/docs/library/interfaceDetection/ERC165/ERC165Facet.mdx new file mode 100644 index 00000000..53b5158d --- /dev/null +++ b/website/docs/library/interfaceDetection/ERC165/ERC165Facet.mdx @@ -0,0 +1,186 @@ +--- +sidebar_position: 1 +title: "ERC-165 Facet" +description: "Interface detection for diamonds via ERC-165" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/interfaceDetection/ERC165/ERC165Facet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Interface detection for diamonds via ERC-165 + + + +- Implements ERC-165 interface detection via `supportsInterface`. +- Exposes `exportSelectors` for facet selector discovery. +- Operates using diamond storage and routing patterns. + + +## Overview + +This facet implements ERC-165 interface detection for diamonds. It exposes the `supportsInterface` function, allowing external contracts to query diamond capabilities. Calls are routed through the diamond proxy, leveraging shared storage for interface support information. + +## Storage + +### ERC165Storage + + +{`struct ERC165Storage { + /** + * @notice Mapping of interface IDs to whether they are supported + */ + mapping(bytes4 => bool) supportedInterfaces; +}`} + + +### State Variables + + + +## Functions + +### supportsInterface + +Query if a contract implements an interface This function checks if the diamond supports the given interface ID + + +{`function supportsInterface(bytes4 _interfaceId) external view returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC165Facet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + + + + +## Best Practices + + +- Ensure the `ERC165Facet` is added to the diamond, and its selectors are correctly registered. +- Call `supportsInterface` through the diamond proxy address to query the diamond's capabilities. +- Use `exportSelectors` during facet deployment to list the selectors provided by this facet. + + +## Security Considerations + + +The `supportsInterface` function is `view` and has no state-changing implications. The `exportSelectors` function is `pure`. No reentrancy risks are present in these functions. + + +
+ +
+ + diff --git a/website/docs/library/interfaceDetection/ERC165/ERC165Mod.mdx b/website/docs/library/interfaceDetection/ERC165/ERC165Mod.mdx new file mode 100644 index 00000000..dfb8fd71 --- /dev/null +++ b/website/docs/library/interfaceDetection/ERC165/ERC165Mod.mdx @@ -0,0 +1,159 @@ +--- +sidebar_position: 2 +title: "ERC-165 Module" +description: "ERC-165 standard interface detection" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/interfaceDetection/ERC165/ERC165Mod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +ERC-165 standard interface detection + + + +- Internal functions for registering ERC-165 supported interfaces. +- Utilizes diamond storage pattern via a fixed `STORAGE_POSITION`. +- No external dependencies, promoting composability. +- Compatible with ERC-2535 diamond proxy standard. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions and storage for ERC-165 interface detection within a diamond. Facets import this module to register supported interfaces during initialization using shared diamond storage. This ensures consistent interface identification across all facets of the diamond. + +## Storage + +### ERC165Storage + + +{`struct ERC165Storage { + /* + * @notice Mapping of interface IDs to whether they are supported + */ + mapping(bytes4 => bool) supportedInterfaces; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns a pointer to the ERC-165 storage struct. Uses inline assembly to bind the storage struct to the fixed storage position. + + +{`function getStorage() pure returns (ERC165Storage storage s);`} + + +**Returns:** + + + +--- +### registerInterface + +Register that a contract supports an interface Call this function during initialization to register supported interfaces. For example, in an ERC721 facet initialization, you would call: `LibERC165.registerInterface(type(IERC721).interfaceId)` + + +{`function registerInterface(bytes4 _interfaceId) ;`} + + +**Parameters:** + + + + + + +## Best Practices + + +- Call `registerInterface` during facet initialization to declare supported interfaces. +- Ensure the diamond's storage layout is compatible with ERC165Mod's storage requirements. +- Verify that the `STORAGE_POSITION` for ERC-165 is unique and not colliding with other diamond storage. + + +## Integration Notes + + +This module interacts with diamond storage at a specific, predefined slot identified by `STORAGE_POSITION` (keccak256("erc165")). The `ERC165Storage` struct is managed at this location. The `getStorage` function returns a pointer to this struct, allowing internal operations. Changes made via `registerInterface` directly update this shared storage, making them immediately visible to any other facet accessing the same storage position. + + +
+ +
+ + diff --git a/website/docs/library/interfaceDetection/ERC165/_category_.json b/website/docs/library/interfaceDetection/ERC165/_category_.json new file mode 100644 index 00000000..2396f18a --- /dev/null +++ b/website/docs/library/interfaceDetection/ERC165/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "ERC-165", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/interfaceDetection/ERC165/index" + } +} diff --git a/website/docs/library/interfaceDetection/ERC165/index.mdx b/website/docs/library/interfaceDetection/ERC165/index.mdx new file mode 100644 index 00000000..dc71c203 --- /dev/null +++ b/website/docs/library/interfaceDetection/ERC165/index.mdx @@ -0,0 +1,29 @@ +--- +title: "ERC-165" +description: "ERC-165 components for Compose 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'; + + + ERC-165 components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/interfaceDetection/_category_.json b/website/docs/library/interfaceDetection/_category_.json new file mode 100644 index 00000000..a184d836 --- /dev/null +++ b/website/docs/library/interfaceDetection/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Interface Detection", + "position": 5, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/interfaceDetection/index" + } +} diff --git a/website/docs/library/interfaceDetection/index.mdx b/website/docs/library/interfaceDetection/index.mdx new file mode 100644 index 00000000..65448bd8 --- /dev/null +++ b/website/docs/library/interfaceDetection/index.mdx @@ -0,0 +1,22 @@ +--- +title: "Interface Detection" +description: "ERC-165 interface detection support." +--- + +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'; + + + ERC-165 interface detection support. + + + + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC1155/Approve/ERC1155ApproveFacet.mdx b/website/docs/library/token/ERC1155/Approve/ERC1155ApproveFacet.mdx new file mode 100644 index 00000000..33ee122d --- /dev/null +++ b/website/docs/library/token/ERC1155/Approve/ERC1155ApproveFacet.mdx @@ -0,0 +1,215 @@ +--- +sidebar_position: 200 +title: "ERC-1155 Approve Facet" +description: "Manage ERC-1155 approvals within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC1155/Approve/ERC1155ApproveFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manage ERC-1155 approvals within a diamond + + + +- Exposes external `setApprovalForAll` function for diamond routing. +- Emits `ApprovalForAll` event upon approval changes. +- Uses inline assembly to access diamond storage. +- Provides `exportSelectors` for diamond facet discovery. + + +## Overview + +This facet provides external functions for managing ERC-1155 token approvals within a diamond proxy. It integrates with diamond storage to track approval status. Developers add this facet to enable token holders to grant operators permission to transfer their ERC-1155 tokens. + +## Storage + +### ERC1155Storage + + +{`struct ERC1155Storage { + mapping(uint256 id => mapping(address account => uint256 balance)) balanceOf; + mapping(address account => mapping(address operator => bool)) isApprovedForAll; +}`} + + +### State Variables + + + +## Functions + +### setApprovalForAll + +Grants or revokes permission to `operator` to transfer the caller's tokens. Emits an ApprovalForAll event. + + +{`function setApprovalForAll(address _operator, bool _approved) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC1155ApproveFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when `account` grants or revokes permission to `operator` to transfer their tokens. +
+ +
+ Signature: + +{`event ApprovalForAll(address indexed _account, address indexed _operator, bool _approved);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Error indicating the operator address is invalid. +
+ +
+ Signature: + +error ERC1155InvalidOperator(address _operator); + +
+
+
+ + + + +## Best Practices + + +- Grant approvals only to trusted operators. +- Ensure the ERC1155ApproveMod is correctly initialized before this facet is used. +- Verify that the diamond storage layout is compatible before upgrading. + + +## Security Considerations + + +The `setApprovalForAll` function allows any caller to approve or revoke an operator for their tokens. Callers must ensure they are approving legitimate operators. The facet includes an `ERC1155InvalidOperator` error, which is currently not used by the exposed functions, but is available for internal use or future extensions. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC1155/Approve/ERC1155ApproveMod.mdx b/website/docs/library/token/ERC1155/Approve/ERC1155ApproveMod.mdx new file mode 100644 index 00000000..3d6ad020 --- /dev/null +++ b/website/docs/library/token/ERC1155/Approve/ERC1155ApproveMod.mdx @@ -0,0 +1,226 @@ +--- +sidebar_position: 210 +title: "ERC-1155 Approve Module" +description: "Internal ERC-1155 approval management" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC1155/Approve/ERC1155ApproveMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Internal ERC-1155 approval management + + + +- All functions are `internal` for use within custom facets. +- Uses the diamond storage pattern to manage approvals. +- Emits `ApprovalForAll` events for off-chain tracking. +- No external dependencies or `using` directives within the module itself. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for managing ERC-1155 token approvals. Facets can import this module to grant or revoke operator permissions using shared diamond storage. Changes made through this module are immediately visible to all facets interacting with the same storage. + +## Storage + +### ERC1155Storage + +ERC-8042 compliant storage struct for ERC-1155 token data. storage-location: erc8042:erc1155 + + +{`struct ERC1155Storage { + mapping(uint256 id => mapping(address account => uint256 balance)) balanceOf; + mapping(address account => mapping(address operator => bool)) isApprovedForAll; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the ERC-1155 storage struct from the predefined diamond storage slot. Uses inline assembly to set the storage slot reference. + + +{`function getStorage() pure returns (ERC1155Storage storage s);`} + + +**Returns:** + + + +--- +### setApprovalForAll + +Grants or revokes permission to `operator` to transfer the user's tokens. Emits an {ApprovalForAll} event. + + +{`function setApprovalForAll(address _user, address _operator, bool _approved) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when `account` grants or revokes permission to `operator` to transfer their tokens. +
+ +
+ Signature: + +{`event ApprovalForAll(address indexed _account, address indexed _operator, bool _approved);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ **Title:** ERC-1155 Approve Module Provides internal approval functionality for ERC-1155 tokens. Error indicating the operator address is invalid. +
+ +
+ Signature: + +error ERC1155InvalidOperator(address _operator); + +
+
+
+ + + + +## Best Practices + + +- Ensure access control is enforced by the calling facet before invoking `setApprovalForAll`. +- Verify the `_operator` address is not the same as the `_user` address to prevent self-approval issues. +- Handle the `ERC1155InvalidOperator` error if the operator is invalid. + + +## Integration Notes + + +This module interacts with diamond storage at the slot identified by `keccak2535.keccak256(\"erc1155\")`. The `getStorage()` function returns a reference to the `ERC1155Storage` struct, which is managed by the diamond. Any modifications to approvals via `setApprovalForAll` are written directly to this shared storage and are immediately visible to all other facets accessing the same storage slot. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC1155/Approve/_category_.json b/website/docs/library/token/ERC1155/Approve/_category_.json new file mode 100644 index 00000000..0f2a3f36 --- /dev/null +++ b/website/docs/library/token/ERC1155/Approve/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Approve", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC1155/Approve/index" + } +} diff --git a/website/docs/library/token/ERC1155/Approve/index.mdx b/website/docs/library/token/ERC1155/Approve/index.mdx new file mode 100644 index 00000000..39b18b03 --- /dev/null +++ b/website/docs/library/token/ERC1155/Approve/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Approve" +description: "Approve components for Compose 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'; + + + Approve components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC1155/Burn/ERC1155BurnFacet.mdx b/website/docs/library/token/ERC1155/Burn/ERC1155BurnFacet.mdx new file mode 100644 index 00000000..98e1c25c --- /dev/null +++ b/website/docs/library/token/ERC1155/Burn/ERC1155BurnFacet.mdx @@ -0,0 +1,362 @@ +--- +sidebar_position: 200 +title: "ERC-1155 Burn Facet" +description: "Burn ERC-1155 tokens within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC1155/Burn/ERC1155BurnFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Burn ERC-1155 tokens within a diamond + + + +- Provides external functions for burning single and batch ERC-1155 tokens. +- Leverages diamond storage for token balances and operator approvals. +- Emits `TransferSingle` and `TransferBatch` events upon successful burns. +- Includes `exportSelectors` for facet discovery within a diamond. + + +## Overview + +This facet provides external functions for burning ERC-1155 tokens within a Compose diamond. It routes calls through the diamond proxy, accesses shared storage via `getStorage`, and emits standard ERC-1155 transfer events. Developers add this facet to enable token burning functionality while maintaining diamond upgradeability. + +## Storage + +### ERC1155Storage + + +{`struct ERC1155Storage { + mapping(uint256 id => mapping(address account => uint256 balance)) balanceOf; + mapping(address account => mapping(address operator => bool)) isApprovedForAll; +}`} + + +### State Variables + + + +## Functions + +### burn + +Burns a single token type from an address. Emits a TransferSingle event. Caller must be the owner or an approved operator. + + +{`function burn(address _from, uint256 _id, uint256 _value) external;`} + + +**Parameters:** + + + +--- +### burnBatch + +Burns multiple token types from an address in a single transaction. Emits a TransferBatch event. Caller must be the owner or an approved operator. + + +{`function burnBatch(address _from, uint256[] calldata _ids, uint256[] calldata _values) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC1155BurnFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`. +
+ +
+ Signature: + +{`event TransferSingle( + address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value +);`} + +
+ +
+ Parameters: + +
+
+ +
+ Equivalent to multiple TransferSingle events, where `operator`, `from` and `to` are the same for all transfers. +
+ +
+ Signature: + +{`event TransferBatch( + address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values +);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Error indicating insufficient balance for a burn operation. +
+ +
+ Signature: + +error ERC1155InsufficientBalance(address _sender, uint256 _balance, uint256 _needed, uint256 _tokenId); + +
+
+ +
+ Error indicating the sender address is invalid. +
+ +
+ Signature: + +error ERC1155InvalidSender(address _sender); + +
+
+ +
+ Error indicating array length mismatch in batch operations. +
+ +
+ Signature: + +error ERC1155InvalidArrayLength(uint256 _idsLength, uint256 _valuesLength); + +
+
+ +
+ Error indicating missing approval for an operator. +
+ +
+ Signature: + +error ERC1155MissingApprovalForAll(address _operator, address _owner); + +
+
+
+ + + + +## Best Practices + + +- Ensure the ERC1155BurnFacet is properly initialized with correct storage slot. +- Caller must possess the token or be an approved operator before calling burn functions. +- Verify that `_ids` and `_values` arrays have matching lengths before calling `burnBatch`. + + +## Security Considerations + + +The `burn` and `burnBatch` functions require the caller to be the owner of the tokens or an approved operator. Insufficient balance will revert with `ERC1155InsufficientBalance`. Mismatched array lengths in `burnBatch` will revert with `ERC1155InvalidArrayLength`. Ensure that the diamond's access control mechanism correctly routes calls and that token ownership logic is consistent across all relevant facets. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC1155/Burn/ERC1155BurnMod.mdx b/website/docs/library/token/ERC1155/Burn/ERC1155BurnMod.mdx new file mode 100644 index 00000000..9849061c --- /dev/null +++ b/website/docs/library/token/ERC1155/Burn/ERC1155BurnMod.mdx @@ -0,0 +1,347 @@ +--- +sidebar_position: 210 +title: "ERC-1155 Burn Module" +description: "Internal ERC-1155 token burning functionality" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC1155/Burn/ERC1155BurnMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Internal ERC-1155 token burning functionality + + + +- All functions are `internal` for use within custom facets. +- Utilizes the diamond storage pattern for shared state management. +- Emits `TransferSingle` and `TransferBatch` events upon successful burns. +- Reverts with custom errors for insufficient balance or invalid array lengths. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module exposes internal functions for burning ERC-1155 tokens. Facets can import this module to decrease token balances using shared diamond storage. Changes made through this module are immediately visible to all facets using the same storage pattern. + +## Storage + +### ERC1155Storage + +ERC-8042 compliant storage struct for ERC-1155 token data. storage-location: erc8042:erc1155 + + +{`struct ERC1155Storage { + mapping(uint256 id => mapping(address account => uint256 balance)) balanceOf; + mapping(address account => mapping(address operator => bool)) isApprovedForAll; +}`} + + +### State Variables + + + +## Functions + +### burn + +Burns a single token type from an address. Decreases the balance and emits a TransferSingle event. Reverts if the account has insufficient balance. This module does not perform approval checks. Ensure proper ownership or approval validation before calling this function. + + +{`function burn(address _from, uint256 _id, uint256 _value) ;`} + + +**Parameters:** + + + +--- +### burnBatch + +Burns multiple token types from an address in a single transaction. Decreases balances for each token type and emits a TransferBatch event. Reverts if the account has insufficient balance for any token type. This module does not perform approval checks. Ensure proper ownership or approval validation before calling this function. + + +{`function burnBatch(address _from, uint256[] memory _ids, uint256[] memory _values) ;`} + + +**Parameters:** + + + +--- +### getStorage + +Returns the ERC-1155 storage struct from the predefined diamond storage slot. Uses inline assembly to set the storage slot reference. + + +{`function getStorage() pure returns (ERC1155Storage storage s);`} + + +**Returns:** + + + +## Events + + + +
+ Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all transfers. +
+ +
+ Signature: + +{`event TransferBatch( +address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values +);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`. +
+ +
+ Signature: + +{`event TransferSingle( +address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value +);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ **Title:** ERC-1155 Burn Module Provides internal burn functionality for ERC-1155 tokens. Error indicating insufficient balance for a burn operation. +
+ +
+ Signature: + +error ERC1155InsufficientBalance(address _sender, uint256 _balance, uint256 _needed, uint256 _tokenId); + +
+
+ +
+ Error indicating array length mismatch in batch operations. +
+ +
+ Signature: + +error ERC1155InvalidArrayLength(uint256 _idsLength, uint256 _valuesLength); + +
+
+ +
+ Error indicating the sender address is invalid. +
+ +
+ Signature: + +error ERC1155InvalidSender(address _sender); + +
+
+
+ + + + +## Best Practices + + +- Ensure caller has necessary permissions (e.g., ownership, operator approval) before calling burn functions. +- Verify that `_ids` and `_values` arrays have matching lengths when calling `burnBatch`. +- Handle the `ERC1155InsufficientBalance` error that may be returned if the sender does not possess enough tokens. + + +## Integration Notes + + +This module interacts with diamond storage at the position identified by `keccak256("erc1155")`. The `ERC1155Storage` struct, though empty in its definition, resides at this slot. All state modifications (balance reductions) are performed directly on this shared storage, making them immediately visible to any other facet accessing the same storage slot. The `getStorage` function can be used to retrieve a reference to this storage struct. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC1155/Burn/_category_.json b/website/docs/library/token/ERC1155/Burn/_category_.json new file mode 100644 index 00000000..f890c570 --- /dev/null +++ b/website/docs/library/token/ERC1155/Burn/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Burn", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC1155/Burn/index" + } +} diff --git a/website/docs/library/token/ERC1155/Burn/index.mdx b/website/docs/library/token/ERC1155/Burn/index.mdx new file mode 100644 index 00000000..831887fb --- /dev/null +++ b/website/docs/library/token/ERC1155/Burn/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Burn" +description: "Burn components for Compose 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'; + + + Burn components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC1155/Data/ERC1155DataFacet.mdx b/website/docs/library/token/ERC1155/Data/ERC1155DataFacet.mdx new file mode 100644 index 00000000..2ebed023 --- /dev/null +++ b/website/docs/library/token/ERC1155/Data/ERC1155DataFacet.mdx @@ -0,0 +1,278 @@ +--- +sidebar_position: 200 +title: "ERC-1155 Data Facet" +description: "View balances and approvals for ERC-1155 tokens" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC1155/Data/ERC1155DataFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +View balances and approvals for ERC-1155 tokens + + + +- Exposes external view functions for ERC-1155 data retrieval. +- Reads from shared diamond storage for token balances and approvals. +- Supports batched queries for efficiency. +- Includes `exportSelectors` for diamond discovery. + + +## Overview + +This facet provides external read-only functions for ERC-1155 token balances and operator approvals within a diamond. It accesses shared diamond storage to retrieve this information, enabling consistent data retrieval across different facets. Developers integrate this facet to expose ERC-1155 data querying capabilities through the diamond proxy. + +## Storage + +### ERC1155Storage + + +{`struct ERC1155Storage { + mapping(uint256 id => mapping(address account => uint256 balance)) balanceOf; + mapping(address account => mapping(address operator => bool)) isApprovedForAll; +}`} + + +### State Variables + + + +## Functions + +### balanceOf + +Returns the amount of tokens of token type `id` owned by `account`. + + +{`function balanceOf(address _account, uint256 _id) external view returns (uint256);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### balanceOfBatch + +Batched version of balanceOf. + + +{`function balanceOfBatch(address[] calldata _accounts, uint256[] calldata _ids) + external + view + returns (uint256[] memory balances);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### isApprovedForAll + +Returns true if `operator` is approved to transfer `account`'s tokens. + + +{`function isApprovedForAll(address _account, address _operator) external view returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC1155DataFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Errors + + + +
+ Error indicating array length mismatch in batch operations. +
+ +
+ Signature: + +error ERC1155InvalidArrayLength(uint256 _idsLength, uint256 _valuesLength); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `ERC1155DataFacet` is added to the diamond with the correct selectors. +- Use `balanceOfBatch` for efficient retrieval of multiple balances to minimize gas costs. +- Verify that the `ERC1155ApproveFacet` or equivalent is also deployed to manage approvals. + + +## Security Considerations + + +This facet only exposes read-only functions. Input validation for array lengths is handled by the `ERC1155InvalidArrayLength` error in `balanceOfBatch`. Follow standard Solidity security practices for all interactions with the diamond. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC1155/Data/_category_.json b/website/docs/library/token/ERC1155/Data/_category_.json new file mode 100644 index 00000000..f9cd7a45 --- /dev/null +++ b/website/docs/library/token/ERC1155/Data/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Data", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC1155/Data/index" + } +} diff --git a/website/docs/library/token/ERC1155/Data/index.mdx b/website/docs/library/token/ERC1155/Data/index.mdx new file mode 100644 index 00000000..c647e654 --- /dev/null +++ b/website/docs/library/token/ERC1155/Data/index.mdx @@ -0,0 +1,22 @@ +--- +title: "Data" +description: "Data components for Compose 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'; + + + Data components for Compose diamonds. + + + + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC1155/Metadata/ERC1155MetadataFacet.mdx b/website/docs/library/token/ERC1155/Metadata/ERC1155MetadataFacet.mdx new file mode 100644 index 00000000..1daff033 --- /dev/null +++ b/website/docs/library/token/ERC1155/Metadata/ERC1155MetadataFacet.mdx @@ -0,0 +1,213 @@ +--- +sidebar_position: 200 +title: "ERC-1155 Metadata Facet" +description: "ERC-1155 token metadata and URI management" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC1155/Metadata/ERC1155MetadataFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +ERC-1155 token metadata and URI management + + + +- Exposes external `uri` function for token metadata retrieval. +- Reads from and writes to shared diamond storage for URI management. +- Compatible with ERC-2535 diamond standard for upgradeability. +- Exports its selectors for diamond discovery. + + +## Overview + +This facet provides external functions for managing and retrieving ERC-1155 token metadata URIs within a diamond. It accesses shared diamond storage to store and retrieve base URIs and token-specific URIs. Developers integrate this facet to expose ERC-1155 metadata functionality while maintaining diamond upgradeability. + +## Storage + +### ERC1155MetadataStorage + + +{`struct ERC1155MetadataStorage { + string uri; + string baseURI; + mapping(uint256 tokenId => string) tokenURIs; +}`} + + +### State Variables + + + +## Functions + +### uri + +Returns the URI for token type `_id`. If a token-specific URI is set in tokenURIs[_id], returns the concatenation of baseURI and tokenURIs[_id]. Note that baseURI is empty by default and must be set explicitly if concatenation is desired. If no token-specific URI is set, returns the default URI which applies to all token types. The default URI may contain the substring `{id}` which clients should replace with the actual token ID. + + +{`function uri(uint256 _id) external view returns (string memory);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC1155MetadataFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when the URI for token type `_id` changes to `_value`. +
+ +
+ Signature: + +{`event URI(string _value, uint256 indexed _id);`} + +
+ +
+ Parameters: + +
+
+
+ + + + +## Best Practices + + +- Ensure the `ERC1155MetadataFacet` is correctly added to the diamond during initialization. +- Access URIs using the `uri(uint256 _id)` function through the diamond proxy. +- If using the `baseURI` concatenation feature, ensure `baseURI` is set via an appropriate admin facet. + + +## Security Considerations + + +The `uri` function is a read-only operation and does not pose reentrancy risks. Input validation for `_id` is handled by the caller or other facets interacting with this facet. Ensure that any facet responsible for setting URIs enforces appropriate access control to prevent unauthorized modifications. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC1155/Metadata/ERC1155MetadataMod.mdx b/website/docs/library/token/ERC1155/Metadata/ERC1155MetadataMod.mdx new file mode 100644 index 00000000..4d02313f --- /dev/null +++ b/website/docs/library/token/ERC1155/Metadata/ERC1155MetadataMod.mdx @@ -0,0 +1,273 @@ +--- +sidebar_position: 210 +title: "ERC-1155 Metadata Module" +description: "Manages ERC-1155 token metadata URIs" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC1155/Metadata/ERC1155MetadataMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages ERC-1155 token metadata URIs + + + +- Provides internal functions for ERC-1155 metadata URI management. +- Uses diamond storage (EIP-8042) for shared state persistence. +- Emits a `URI` event upon setting token-specific URIs. +- Functions are `internal` and intended for use within custom diamond facets. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for managing ERC-1155 token metadata URIs within a diamond. It allows facets to set a base URI, token-specific URIs, and a default URI, all stored in shared diamond storage. Changes are immediately visible to all facets accessing the same storage slot. + +## Storage + +### ERC1155MetadataStorage + +ERC-8042 compliant storage struct for ERC-1155 metadata. storage-location: erc8042:erc1155.metadata + + +{`struct ERC1155MetadataStorage { + string uri; + string baseURI; + mapping(uint256 tokenId => string) tokenURIs; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the ERC-1155 metadata storage struct from the predefined diamond storage slot. Uses inline assembly to set the storage slot reference. + + +{`function getStorage() pure returns (ERC1155MetadataStorage storage s);`} + + +**Returns:** + + + +--- +### setBaseURI + +Sets the base URI prefix for token-specific URIs. The base URI is concatenated with token-specific URIs set via setTokenURI. Does not affect the default URI used when no token-specific URI is set. + + +{`function setBaseURI(string memory _baseURI) ;`} + + +**Parameters:** + + + +--- +### setTokenURI + +Sets the token-specific URI for a given token ID. Sets tokenURIs[_tokenId] to the provided string and emits a URI event with the full computed URI. The emitted URI is the concatenation of baseURI and the token-specific URI. + + +{`function setTokenURI(uint256 _tokenId, string memory _tokenURI) ;`} + + +**Parameters:** + + + +--- +### setURI + +Sets the default URI for all token types. This URI is used when no token-specific URI is set. + + +{`function setURI(string memory _uri) ;`} + + +**Parameters:** + + + +## Events + + + +
+ **Title:** ERC-1155 Metadata Module Provides internal metadata functionality for ERC-1155 tokens. Emitted when the URI for token type `_id` changes to `_value`. +
+ +
+ Signature: + +{`event URI(string _value, uint256 indexed _id);`} + +
+ +
+ Parameters: + +
+
+
+ + + + +## Best Practices + + +- Ensure access control is enforced by the calling facet before invoking `setBaseURI`, `setTokenURI`, or `setURI`. +- Verify that the `ERC1155MetadataStorage` struct layout remains compatible across diamond upgrades. +- Handle the `URI` event emitted by `setTokenURI` for off-chain indexing or notifications. + + +## Integration Notes + + +This module interacts with diamond storage via the `STORAGE_POSITION` slot, identified by `keccak256("erc1155.metadata")`. It utilizes inline assembly to access the `ERC1155MetadataStorage` struct, which contains `uri` and `baseURI` fields. Any facet that reads from or writes to this storage position will observe changes made by this module immediately, adhering to the diamond storage pattern. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC1155/Metadata/_category_.json b/website/docs/library/token/ERC1155/Metadata/_category_.json new file mode 100644 index 00000000..144e512a --- /dev/null +++ b/website/docs/library/token/ERC1155/Metadata/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Metadata", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC1155/Metadata/index" + } +} diff --git a/website/docs/library/token/ERC1155/Metadata/index.mdx b/website/docs/library/token/ERC1155/Metadata/index.mdx new file mode 100644 index 00000000..a13d9ad4 --- /dev/null +++ b/website/docs/library/token/ERC1155/Metadata/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Metadata" +description: "Metadata components for Compose 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'; + + + Metadata components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC1155/Mint/ERC1155MintMod.mdx b/website/docs/library/token/ERC1155/Mint/ERC1155MintMod.mdx new file mode 100644 index 00000000..b08de907 --- /dev/null +++ b/website/docs/library/token/ERC1155/Mint/ERC1155MintMod.mdx @@ -0,0 +1,354 @@ +--- +sidebar_position: 210 +title: "ERC-1155 Mint Module" +description: "ERC-1155 token minting and batch minting" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC1155/Mint/ERC1155MintMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +ERC-1155 token minting and batch minting + + + +- Internal functions for direct integration into custom facets. +- Utilizes the diamond storage pattern (EIP-8042) for shared state. +- Emits `TransferSingle` and `TransferBatch` events for off-chain tracking. +- Includes recipient validation for contract addresses. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for minting ERC-1155 tokens. Facets can import this module to manage token creation directly within the diamond, leveraging shared diamond storage for balances. Changes to token balances are immediately visible to all facets accessing the same storage slot. + +## Storage + +### ERC1155Storage + +ERC-8042 compliant storage struct for ERC-1155 token data. storage-location: erc8042:erc1155 + + +{`struct ERC1155Storage { + mapping(uint256 id => mapping(address account => uint256 balance)) balanceOf; + mapping(address account => mapping(address operator => bool)) isApprovedForAll; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the ERC-1155 storage struct from the predefined diamond storage slot. Uses inline assembly to set the storage slot reference. + + +{`function getStorage() pure returns (ERC1155Storage storage s);`} + + +**Returns:** + + + +--- +### mint + +Mints a single token type to an address. Increases the balance and emits a TransferSingle event. Performs receiver validation if recipient is a contract. + + +{`function mint(address _to, uint256 _id, uint256 _value, bytes memory _data) ;`} + + +**Parameters:** + + + +--- +### mintBatch + +Mints multiple token types to an address in a single transaction. Increases balances for each token type and emits a TransferBatch event. Performs receiver validation if recipient is a contract. + + +{`function mintBatch(address _to, uint256[] memory _ids, uint256[] memory _values, bytes memory _data) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all transfers. +
+ +
+ Signature: + +{`event TransferBatch( +address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values +);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`. +
+ +
+ Signature: + +{`event TransferSingle( +address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value +);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Error indicating array length mismatch in batch operations. +
+ +
+ Signature: + +error ERC1155InvalidArrayLength(uint256 _idsLength, uint256 _valuesLength); + +
+
+ +
+ **Title:** ERC-1155 Mint Module Provides internal mint functionality for ERC-1155 tokens. Error indicating the receiver address is invalid. +
+ +
+ Signature: + +error ERC1155InvalidReceiver(address _receiver); + +
+
+
+ + + + +## Best Practices + + +- Ensure the recipient address is validated for contract calls to prevent reentrancy issues. +- Verify that the array lengths for `mintBatch` match to avoid `ERC1155InvalidArrayLength` errors. +- Handle potential `ERC1155InvalidReceiver` errors when minting to contract addresses. + + +## Integration Notes + + +This module interacts with diamond storage at the slot identified by `keccak256(\"erc1155\")`. The `getStorage()` function returns a reference to the `ERC1155Storage` struct. All minting operations directly modify balances within this shared storage, making changes immediately visible to other facets that access the same storage slot. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC1155/Mint/_category_.json b/website/docs/library/token/ERC1155/Mint/_category_.json new file mode 100644 index 00000000..ed19ab10 --- /dev/null +++ b/website/docs/library/token/ERC1155/Mint/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Mint", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC1155/Mint/index" + } +} diff --git a/website/docs/library/token/ERC1155/Mint/index.mdx b/website/docs/library/token/ERC1155/Mint/index.mdx new file mode 100644 index 00000000..1873d90d --- /dev/null +++ b/website/docs/library/token/ERC1155/Mint/index.mdx @@ -0,0 +1,22 @@ +--- +title: "Mint" +description: "Mint components for Compose 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'; + + + Mint components for Compose diamonds. + + + + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC1155/Transfer/ERC1155TransferFacet.mdx b/website/docs/library/token/ERC1155/Transfer/ERC1155TransferFacet.mdx new file mode 100644 index 00000000..9a933202 --- /dev/null +++ b/website/docs/library/token/ERC1155/Transfer/ERC1155TransferFacet.mdx @@ -0,0 +1,394 @@ +--- +sidebar_position: 200 +title: "ERC-1155 Transfer Facet" +description: "ERC-1155 token transfers within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC1155/Transfer/ERC1155TransferFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +ERC-1155 token transfers within a diamond + + + +- Implements `safeTransferFrom` and `safeBatchTransferFrom` for ERC-1155 token handling. +- Accesses shared diamond storage via `getStorage()` for balance management. +- Emits `TransferSingle` and `TransferBatch` events for off-chain tracking. +- Exports its function selectors via `exportSelectors()` for diamond integration. + + +## Overview + +This facet implements ERC-1155 token transfers as external functions callable through a diamond proxy. It accesses shared diamond storage to manage token balances and emits standard events for off-chain consumption. Developers integrate this facet to provide core ERC-1155 transfer capabilities while retaining diamond's upgradeability. + +## Storage + +### ERC1155Storage + + +{`struct ERC1155Storage { + mapping(uint256 id => mapping(address account => uint256 balance)) balanceOf; + mapping(address account => mapping(address operator => bool)) isApprovedForAll; +}`} + + +### State Variables + + + +## Functions + +### safeTransferFrom + +Transfers `value` amount of token type `id` from `from` to `to`. Emits a TransferSingle event. + + +{`function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;`} + + +**Parameters:** + + + +--- +### safeBatchTransferFrom + +Batched version of safeTransferFrom. Emits a TransferBatch event. + + +{`function safeBatchTransferFrom( + address _from, + address _to, + uint256[] calldata _ids, + uint256[] calldata _values, + bytes calldata _data +) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC1155TransferFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`. +
+ +
+ Signature: + +{`event TransferSingle( + address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value +);`} + +
+ +
+ Parameters: + +
+
+ +
+ Equivalent to multiple TransferSingle events, where `operator`, `from` and `to` are the same for all transfers. +
+ +
+ Signature: + +{`event TransferBatch( + address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values +);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Error indicating insufficient balance for a transfer. +
+ +
+ Signature: + +error ERC1155InsufficientBalance(address _sender, uint256 _balance, uint256 _needed, uint256 _tokenId); + +
+
+ +
+ Error indicating the sender address is invalid. +
+ +
+ Signature: + +error ERC1155InvalidSender(address _sender); + +
+
+ +
+ Error indicating the receiver address is invalid. +
+ +
+ Signature: + +error ERC1155InvalidReceiver(address _receiver); + +
+
+ +
+ Error indicating missing approval for an operator. +
+ +
+ Signature: + +error ERC1155MissingApprovalForAll(address _operator, address _owner); + +
+
+ +
+ Error indicating array length mismatch in batch operations. +
+ +
+ Signature: + +error ERC1155InvalidArrayLength(uint256 _idsLength, uint256 _valuesLength); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `ERC1155Storage` struct is correctly initialized in diamond storage before deploying this facet. +- Verify that the `ERC1155ApproveFacet` is also deployed to manage approvals required for transfers. +- Call `exportSelectors()` during diamond deployment to register the facet's functions. + + +## Security Considerations + + +The `safeTransferFrom` and `safeBatchTransferFrom` functions perform checks before state changes and external interactions, mitigating reentrancy risks. Input validation is performed for sender, receiver, and array lengths, reverting with custom errors like `ERC1155InvalidSender` and `ERC1155InvalidArrayLength`. Ensure that the caller has the necessary approvals via `ERC1155ApproveFacet` before invoking transfer functions. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC1155/Transfer/ERC1155TransferMod.mdx b/website/docs/library/token/ERC1155/Transfer/ERC1155TransferMod.mdx new file mode 100644 index 00000000..fcfae218 --- /dev/null +++ b/website/docs/library/token/ERC1155/Transfer/ERC1155TransferMod.mdx @@ -0,0 +1,416 @@ +--- +sidebar_position: 210 +title: "ERC-1155 Transfer Module" +description: "Manages ERC-1155 token transfers within a diamond" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC1155/Transfer/ERC1155TransferMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages ERC-1155 token transfers within a diamond + + + +- Implements `safeTransferFrom` and `safeBatchTransferFrom` adhering to EIP-1155. +- Uses diamond storage for persistent token balance management. +- Emits `TransferSingle` and `TransferBatch` events upon successful transfers. +- Includes robust error checking for invalid inputs and state conditions. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module implements core ERC-1155 transfer logic, enabling facets to move multiple token types between addresses. It leverages diamond storage for token balances, ensuring consistency across all facets. By adhering to EIP-1155 safe transfer requirements, it provides a secure foundation for complex token interactions within a diamond. + +## Storage + +### ERC1155Storage + +ERC-8042 compliant storage struct for ERC-1155 token data. storage-location: erc8042:erc1155 + + +{`struct ERC1155Storage { + mapping(uint256 id => mapping(address account => uint256 balance)) balanceOf; + mapping(address account => mapping(address operator => bool)) isApprovedForAll; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the ERC-1155 storage struct from the predefined diamond storage slot. Uses inline assembly to set the storage slot reference. + + +{`function getStorage() pure returns (ERC1155Storage storage s);`} + + +**Returns:** + + + +--- +### safeBatchTransferFrom + +Safely transfers multiple token types from one address to another in a single transaction. Validates ownership, approval, and receiver address before updating balances for each token type. Performs ERC1155Receiver validation if recipient is a contract (safe transfer). Complies with EIP-1155 safe transfer requirements. + + +{`function safeBatchTransferFrom( +address _from, +address _to, +uint256[] memory _ids, +uint256[] memory _values, +address _operator +) ;`} + + +**Parameters:** + + + +--- +### safeTransferFrom + +Safely transfers a single token type from one address to another. Validates ownership, approval, and receiver address before updating balances. Performs ERC1155Receiver validation if recipient is a contract (safe transfer). Complies with EIP-1155 safe transfer requirements. + + +{`function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, address _operator) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all transfers. +
+ +
+ Signature: + +{`event TransferBatch( +address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values +);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`. +
+ +
+ Signature: + +{`event TransferSingle( +address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value +);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ **Title:** ERC-1155 Transfer Module Provides internal transfer functionality for ERC-1155 tokens. Error indicating insufficient balance for a transfer. +
+ +
+ Signature: + +error ERC1155InsufficientBalance(address _sender, uint256 _balance, uint256 _needed, uint256 _tokenId); + +
+
+ +
+ Error indicating array length mismatch in batch operations. +
+ +
+ Signature: + +error ERC1155InvalidArrayLength(uint256 _idsLength, uint256 _valuesLength); + +
+
+ +
+ Error indicating the receiver address is invalid. +
+ +
+ Signature: + +error ERC1155InvalidReceiver(address _receiver); + +
+
+ +
+ Error indicating the sender address is invalid. +
+ +
+ Signature: + +error ERC1155InvalidSender(address _sender); + +
+
+ +
+ Error indicating missing approval for an operator. +
+ +
+ Signature: + +error ERC1155MissingApprovalForAll(address _operator, address _owner); + +
+
+
+ + + + +## Best Practices + + +- Ensure necessary approvals are granted using `ERC1155ApproveMod` before calling transfer functions. +- Verify that the `_to` address is a valid receiver, especially if it's a contract, to prevent unexpected behavior. +- Handle the `ERC1155InsufficientBalance`, `ERC1155InvalidArrayLength`, `ERC1155InvalidReceiver`, `ERC1155InvalidSender`, and `ERC1155MissingApprovalForAll` errors appropriately. + + +## Integration Notes + + +This module interacts with diamond storage at the slot identified by `keccak256(\"erc1155\")`. The `getStorage()` function provides direct access to the `ERC1155Storage` struct, which holds all token balance information. Any modifications to balances made by `safeTransferFrom` or `safeBatchTransferFrom` are immediately reflected in this shared storage, visible to all facets accessing the same storage slot. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC1155/Transfer/_category_.json b/website/docs/library/token/ERC1155/Transfer/_category_.json new file mode 100644 index 00000000..1907f53c --- /dev/null +++ b/website/docs/library/token/ERC1155/Transfer/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Transfer", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC1155/Transfer/index" + } +} diff --git a/website/docs/library/token/ERC1155/Transfer/index.mdx b/website/docs/library/token/ERC1155/Transfer/index.mdx new file mode 100644 index 00000000..b6efd1c1 --- /dev/null +++ b/website/docs/library/token/ERC1155/Transfer/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Transfer" +description: "Transfer components for Compose 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'; + + + Transfer components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC1155/_category_.json b/website/docs/library/token/ERC1155/_category_.json new file mode 100644 index 00000000..cdb57d9a --- /dev/null +++ b/website/docs/library/token/ERC1155/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "ERC-1155", + "position": 3, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC1155/index" + } +} diff --git a/website/docs/library/token/ERC1155/index.mdx b/website/docs/library/token/ERC1155/index.mdx new file mode 100644 index 00000000..58448c4d --- /dev/null +++ b/website/docs/library/token/ERC1155/index.mdx @@ -0,0 +1,57 @@ +--- +title: "ERC-1155" +description: "ERC-1155 multi-token implementations." +--- + +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'; + + + ERC-1155 multi-token implementations. + + + + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC20/Approve/ERC20ApproveFacet.mdx b/website/docs/library/token/ERC20/Approve/ERC20ApproveFacet.mdx new file mode 100644 index 00000000..358c1447 --- /dev/null +++ b/website/docs/library/token/ERC20/Approve/ERC20ApproveFacet.mdx @@ -0,0 +1,233 @@ +--- +sidebar_position: 200 +title: "ERC-20 Approve Facet" +description: "Approves token spending on behalf of an owner" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Approve/ERC20ApproveFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Approves token spending on behalf of an owner + + + +- Exposes `approve` function for managing token allowances. +- Emits `Approval` event for off-chain tracking. +- Interacts with diamond's shared storage for approval state. +- Functions are callable through the diamond proxy. + + +## Overview + +This facet exposes external functions for approving token spending within a diamond. It interacts with shared diamond storage to manage approvals and emits events for off-chain consumption. Developers add this facet to enable token allowance functionality in a composable manner. + +## Storage + +### ERC20Storage + + +{`struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; + mapping(address owner => mapping(address spender => uint256 allowance)) allowance; +}`} + + +### State Variables + + + +## Functions + +### approve + +Approves a spender to transfer up to a certain amount of tokens on behalf of the caller. Emits an Approval event. + + +{`function approve(address _spender, uint256 _value) external returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC20ApproveFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when an approval is made for a spender by an owner. +
+ +
+ Signature: + +{`event Approval(address indexed _owner, address indexed _spender, uint256 _value);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the spender address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidSpender(address _spender); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `ERC20ApproveFacet` is properly initialized within the diamond. +- Verify that the caller has the necessary permissions to approve token spending. +- Listen for `Approval` events to track token allowances off-chain. + + +## Security Considerations + + +The `approve` function is protected by access control, ensuring only the token owner can set allowances. Input validation for the `_spender` address is performed, reverting with `ERC20InvalidSpender` if it is the zero address. The function follows the checks-effects-interactions pattern to mitigate reentrancy risks. Developers must ensure the diamond's access control mechanisms are correctly configured for this facet. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Approve/ERC20ApproveMod.mdx b/website/docs/library/token/ERC20/Approve/ERC20ApproveMod.mdx new file mode 100644 index 00000000..7b586350 --- /dev/null +++ b/website/docs/library/token/ERC20/Approve/ERC20ApproveMod.mdx @@ -0,0 +1,252 @@ +--- +sidebar_position: 210 +title: "ERC-20 Approve Module" +description: "Manages ERC-20 token allowances for spenders" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Approve/ERC20ApproveMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages ERC-20 token allowances for spenders + + + +- Exposes `internal` functions for use within custom facets. +- Manages ERC-20 allowances using the diamond storage pattern (EIP-8042). +- Emits an `Approval` event upon successful allowance updates. +- Includes a custom error `ERC20InvalidSpender` for invalid spender addresses. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions to manage ERC-20 token allowances. Facets can import this module to approve spenders on behalf of token owners using shared diamond storage. Changes to allowances are immediately visible to all facets interacting with the same storage slot. + +## Storage + +### ERC20Storage + +ERC-8042 compliant storage struct for ERC20 token data. storage-location: erc8042:erc20 + + +{`struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; + mapping(address owner => mapping(address spender => uint256 allowance)) allowance; +}`} + + +### State Variables + + + +## Functions + +### approve + +Approves a spender to transfer tokens on behalf of the caller. Sets the allowance for the spender. + + +{`function approve(address _spender, uint256 _value) returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### getStorage + +Returns the ERC20 storage struct from the predefined diamond storage slot. Uses inline assembly to set the storage slot reference. + + +{`function getStorage() pure returns (ERC20Storage storage s);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when an approval is made for a spender by an owner. +
+ +
+ Signature: + +{`event Approval(address indexed _owner, address indexed _spender, uint256 _value);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the spender address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidSpender(address _spender); + +
+
+
+ + + + +## Best Practices + + +- Ensure the caller has sufficient tokens before calling `approve` if simulating a full ERC-20 behavior. +- Handle the `ERC20InvalidSpender` error if the spender address is zero. +- Verify access control is properly managed by the calling facet before invoking `approve`. + + +## Integration Notes + + +This module utilizes the diamond storage pattern at `STORAGE_POSITION`, keyed by `keccak256("erc20")`. The `approve` function modifies the allowance mapping within this shared storage. The `getStorage` function provides direct access to the `ERC20Storage` struct, allowing other facets to inspect the allowance state. All modifications are immediately visible to any facet that reads from the same storage slot. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Approve/_category_.json b/website/docs/library/token/ERC20/Approve/_category_.json new file mode 100644 index 00000000..1a88ee38 --- /dev/null +++ b/website/docs/library/token/ERC20/Approve/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Approve", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC20/Approve/index" + } +} diff --git a/website/docs/library/token/ERC20/Approve/index.mdx b/website/docs/library/token/ERC20/Approve/index.mdx new file mode 100644 index 00000000..f6d9db07 --- /dev/null +++ b/website/docs/library/token/ERC20/Approve/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Approve" +description: "Approve extension for ERC-20 tokens." +--- + +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'; + + + Approve extension for ERC-20 tokens. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC20/Bridgeable/ERC20BridgeableFacet.mdx b/website/docs/library/token/ERC20/Bridgeable/ERC20BridgeableFacet.mdx new file mode 100644 index 00000000..d4d59a61 --- /dev/null +++ b/website/docs/library/token/ERC20/Bridgeable/ERC20BridgeableFacet.mdx @@ -0,0 +1,416 @@ +--- +sidebar_position: 1 +title: "ERC-20 Bridgeable Facet" +description: "Cross-chain ERC-20 token minting and burning" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Bridgeable/ERC20BridgeableFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Cross-chain ERC-20 token minting and burning + + + +- Enables cross-chain minting and burning of ERC-20 tokens. +- Enforces `trusted-bridge` role for authorized operations via access control. +- Functions are callable via the diamond proxy, adhering to ERC-2535. +- Exports its own selectors for discovery by the diamond. + + +## Overview + +This facet exposes cross-chain mint and burn functionalities for ERC-20 tokens within a diamond. It enforces access control via the `trusted-bridge` role, ensuring only authorized entities can perform these operations. Calls are routed through the diamond proxy, leveraging shared diamond storage for state management. + +## Storage + +### ERC20Storage + + +{`struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; +}`} + + +--- +### AccessControlStorage + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; +}`} + + +### State Variables + + + +## Functions + +### crosschainMint + +Cross-chain mint — callable only by an address having the `trusted-bridge` role. + + +{`function crosschainMint(address _account, uint256 _value) external;`} + + +**Parameters:** + + + +--- +### crosschainBurn + +Cross-chain burn — callable only by an address having the `trusted-bridge` role. + + +{`function crosschainBurn(address _from, uint256 _value) external;`} + + +**Parameters:** + + + +--- +### checkTokenBridge + +Internal check to check if the bridge (caller) is trusted. Reverts if caller is zero or not in the AccessControl `trusted-bridge` role. + + +{`function checkTokenBridge(address _caller) external view;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC20BridgeableFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when tokens are minted via a cross-chain bridge. +
+ +
+ Signature: + +{`event CrosschainMint(address indexed _to, uint256 _amount, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when a crosschain transfer burns tokens. +
+ +
+ Signature: + +{`event CrosschainBurn(address indexed _from, uint256 _amount, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when tokens are transferred between two addresses. +
+ +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 _value);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Revert when a provided receiver is invalid(e.g,zero address) . +
+ +
+ Signature: + +error ERC20InvalidReceiver(address _receiver); + +
+
+ +
+ Thrown when the sender address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidSender(address _sender); + +
+
+ +
+ Revert when caller is not a trusted bridge. +
+ +
+ Signature: + +error ERC20InvalidBridgeAccount(address _caller); + +
+
+ +
+ Revert when caller address is invalid. +
+ +
+ Signature: + +error ERC20InvalidCallerAddress(address _caller); + +
+
+ +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+ + +
+ Signature: + +error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `trusted-bridge` role is correctly assigned before calling `crosschainMint` or `crosschainBurn`. +- Verify caller identity and permissions through the diamond proxy before executing cross-chain operations. +- Use `checkTokenBridge` to confirm trusted bridge addresses when necessary. + + +## Security Considerations + + +State-changing functions `crosschainMint` and `crosschainBurn` are protected by the `trusted-bridge` role, enforced by internal checks. The `checkTokenBridge` function reverts if the caller is not a trusted bridge address or is the zero address. Input validation for `_account`, `_from`, and `_value` is crucial and should be handled by the caller or prior facets. Follow standard Solidity security practices for external calls and state updates. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Bridgeable/ERC20BridgeableMod.mdx b/website/docs/library/token/ERC20/Bridgeable/ERC20BridgeableMod.mdx new file mode 100644 index 00000000..b4b46791 --- /dev/null +++ b/website/docs/library/token/ERC20/Bridgeable/ERC20BridgeableMod.mdx @@ -0,0 +1,439 @@ +--- +sidebar_position: 2 +title: "ERC-20 Bridgeable Module" +description: "Internal functions for cross-chain ERC20 token operations" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Bridgeable/ERC20BridgeableMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Internal functions for cross-chain ERC20 token operations + + + +- Internal functions for cross-chain token operations. +- Leverages diamond storage for access control roles (`trusted-bridge`). +- Emits `CrosschainBurn` and `CrosschainMint` events for off-chain monitoring. +- Does not use `using` directives or external dependencies. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for cross-chain ERC20 token transfers, specifically for burning and minting tokens across different chains. It relies on diamond storage for managing access control roles, ensuring that only trusted bridges can execute these sensitive operations. Changes to token balances are immediately visible to all facets interacting with the same diamond storage. + +## Storage + +### AccessControlStorage + +storage struct for the AccessControl. storage-location: erc8042:compose.accesscontrol + + +{`struct AccessControlStorage { + mapping(address account => mapping(bytes32 role => bool hasRole)) hasRole; +}`} + + +--- +### ERC20Storage + +ERC-8042 compliant storage struct for ERC20 token data. storage-location: erc8042:erc20 + + +{`struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; +}`} + + +### State Variables + + + +## Functions + +### checkTokenBridge + +Internal check to check if the bridge (caller) is trusted. Reverts if caller is zero or not in the AccessControl `trusted-bridge` role. + + +{`function checkTokenBridge(address _caller) view;`} + + +**Parameters:** + + + +--- +### crosschainBurn + +Cross-chain burn — callable only by an address having the `trusted-bridge` role. + + +{`function crosschainBurn(address _from, uint256 _value) ;`} + + +**Parameters:** + + + +--- +### crosschainMint + +Cross-chain mint — callable only by an address having the `trusted-bridge` role. + + +{`function crosschainMint(address _account, uint256 _value) ;`} + + +**Parameters:** + + + +--- +### getAccessControlStorage + +helper to return AccessControlStorage at its diamond slot + + +{`function getAccessControlStorage() pure returns (AccessControlStorage storage s);`} + + +--- +### getERC20Storage + +Returns the ERC20 storage struct from the predefined diamond storage slot. Uses inline assembly to set the storage slot reference. + + +{`function getERC20Storage() pure returns (ERC20Storage storage s);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when a crosschain transfer burns tokens. +
+ +
+ Signature: + +{`event CrosschainBurn(address indexed _from, uint256 _amount, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when tokens are minted via a cross-chain bridge. +
+ +
+ Signature: + +{`event CrosschainMint(address indexed _to, uint256 _amount, address indexed _sender);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when tokens are transferred between two addresses. +
+ +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 _value);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the account does not have a specific role. +
+ +
+ Signature: + +error AccessControlUnauthorizedAccount(address _account, bytes32 _role); + +
+
+ + +
+ Signature: + +error ERC20InsufficientBalance(address _from, uint256 _accountBalance, uint256 _value); + +
+
+ +
+ Revert when caller is not a trusted bridge. +
+ +
+ Signature: + +error ERC20InvalidBridgeAccount(address _caller); + +
+
+ +
+ Revert when caller address is invalid. +
+ +
+ Signature: + +error ERC20InvalidCallerAddress(address _caller); + +
+
+ +
+ Revert when a provided receiver is invalid(e.g,zero address) . +
+ +
+ Signature: + +error ERC20InvalidReceiver(address _receiver); + +
+
+ +
+ Thrown when the sender address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidSender(address _sender); + +
+
+
+ + + + +## Best Practices + + +- Ensure the caller has the `trusted-bridge` role before invoking `crosschainBurn` or `crosschainMint`. +- Verify that the diamond storage layout remains compatible when upgrading facets to prevent storage collisions. +- Handle potential reverts from `ERC20InsufficientBalance`, `ERC20InvalidBridgeAccount`, `ERC20InvalidCallerAddress`, `ERC20InvalidReceiver`, or `ERC20InvalidSender`. + + +## Integration Notes + + +This module utilizes the diamond storage pattern, with its state stored at the `ERC20_STORAGE_POSITION` slot, identified by `keccak256("erc20")`. Functions like `crosschainBurn` and `crosschainMint` directly read from and write to the shared diamond storage. The `AccessControlStorage` struct, though empty in the provided definition, is conceptually used for role management. Any changes made to token balances via this module are immediately reflected for all other facets accessing the same storage. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Bridgeable/_category_.json b/website/docs/library/token/ERC20/Bridgeable/_category_.json new file mode 100644 index 00000000..f1ce70cd --- /dev/null +++ b/website/docs/library/token/ERC20/Bridgeable/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Bridgeable", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC20/Bridgeable/index" + } +} diff --git a/website/docs/library/token/ERC20/Bridgeable/index.mdx b/website/docs/library/token/ERC20/Bridgeable/index.mdx new file mode 100644 index 00000000..6eab8218 --- /dev/null +++ b/website/docs/library/token/ERC20/Bridgeable/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Bridgeable" +description: "Bridgeable extension for ERC-20 tokens." +--- + +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'; + + + Bridgeable extension for ERC-20 tokens. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC20/Burn/ERC20BurnFacet.mdx b/website/docs/library/token/ERC20/Burn/ERC20BurnFacet.mdx new file mode 100644 index 00000000..71d5986f --- /dev/null +++ b/website/docs/library/token/ERC20/Burn/ERC20BurnFacet.mdx @@ -0,0 +1,264 @@ +--- +sidebar_position: 3 +title: "ERC-20 Burn Facet" +description: "Burns ERC-20 tokens from caller or allowance" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Burn/ERC20BurnFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Burns ERC-20 tokens from caller or allowance + + + +- Enables burning of tokens from caller's balance via `burn`. +- Supports burning tokens from another account using allowance via `burnFrom`. +- Emits `Transfer` events to the zero address upon successful burns. +- Exports its selectors via `exportSelectors` for diamond discovery. + + +## Overview + +This facet implements ERC-20 token burning functionality within a diamond. It exposes external functions for callers to burn their own tokens or burn tokens from another account using their allowance. Calls are routed through the diamond proxy, and storage is accessed via the diamond's shared storage pattern. + +## Storage + +### ERC20Storage + + +{`struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; + mapping(address owner => mapping(address spender => uint256 allowance)) allowance; +}`} + + +### State Variables + + + +## Functions + +### burn + +Burns (destroys) a specific amount of tokens from the caller's balance. Emits a Transfer event to the zero address. + + +{`function burn(uint256 _value) external;`} + + +**Parameters:** + + + +--- +### burnFrom + +Burns tokens from another account, deducting from the caller's allowance. Emits a Transfer event to the zero address. + + +{`function burnFrom(address _account, uint256 _value) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC20BurnFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when tokens are transferred between two addresses. +
+ +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 _value);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when an account has insufficient balance for a transfer or burn. +
+ +
+ Signature: + +error ERC20InsufficientBalance(address _sender, uint256 _balance, uint256 _needed); + +
+
+ +
+ Thrown when a spender tries to use more than the approved allowance. +
+ +
+ Signature: + +error ERC20InsufficientAllowance(address _spender, uint256 _allowance, uint256 _needed); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `ERC20BurnFacet` is correctly added to the diamond with its selectors. +- Verify sufficient token balances and allowances before calling `burn` or `burnFrom` respectively. +- Implement proper access control mechanisms in the diamond to restrict who can call `burnFrom` if necessary. + + +## Security Considerations + + +The `burn` function requires the caller to have sufficient token balance. The `burnFrom` function requires the caller to have sufficient allowance for the specified account. Both functions emit `Transfer` events to the zero address, signaling token destruction. Developers should ensure that the underlying ERC-20 token logic correctly handles balance and allowance checks. Reentrancy is not a direct concern as these functions do not perform external calls that could re-enter the facet, but standard checks-effects-interactions patterns should be followed by the diamond's storage access. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Burn/ERC20BurnMod.mdx b/website/docs/library/token/ERC20/Burn/ERC20BurnMod.mdx new file mode 100644 index 00000000..d04a9885 --- /dev/null +++ b/website/docs/library/token/ERC20/Burn/ERC20BurnMod.mdx @@ -0,0 +1,248 @@ +--- +sidebar_position: 210 +title: "ERC-20 Burn Module" +description: "Internal ERC-20 token burning functionality" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Burn/ERC20BurnMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Internal ERC-20 token burning functionality + + + +- Exposes only `internal` functions, intended for use within custom facets. +- Operates on shared diamond storage using EIP-8042 semantics. +- Emits `Transfer` events upon successful burning, signaling changes in token ownership. +- Reverts with custom errors `ERC20InsufficientBalance` and `ERC20InvalidSender` for explicit error handling. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for burning ERC-20 tokens, reducing the total supply and an account's balance. Facets can import this module to implement burning logic, operating on shared diamond storage. Changes to token supply and balances are immediately visible to all facets interacting with the same storage. + +## Storage + +### ERC20Storage + +ERC-20 storage layout using the ERC-8042 standard. storage-location: erc8042:erc20 + + +{`struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; +}`} + + +### State Variables + + + +## Functions + +### burn + +Burns tokens from a specified address. Decreases both total supply and the sender's balance. This module does not perform allowance checks. Ensure proper allowance or authorization validation before calling this function. + + +{`function burn(address _account, uint256 _value) ;`} + + +**Parameters:** + + + +--- +### getStorage + +Returns a pointer to the ERC-20 storage struct. Uses inline assembly to bind the storage struct to the fixed storage position. + + +{`function getStorage() pure returns (ERC20Storage storage s);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when tokens are transferred between addresses. +
+ +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 _value);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when a sender attempts to transfer or burn more tokens than their balance. +
+ +
+ Signature: + +error ERC20InsufficientBalance(address _sender, uint256 _balance, uint256 _needed); + +
+
+ +
+ Thrown when the sender address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidSender(address _sender); + +
+
+
+ + + + +## Best Practices + + +- Ensure `_account` authorization and allowance checks are performed before calling `burn` functions to prevent unauthorized burning. +- Handle `ERC20InsufficientBalance` and `ERC20InvalidSender` errors to gracefully manage invalid operations. +- Verify that the storage pointer is correctly initialized and points to the shared diamond storage slot designated for ERC-20 data. + + +## Integration Notes + + +This module interacts with diamond storage at the `STORAGE_POSITION` slot, identified by `keccak256("erc20")`. The `getStorage` function returns a pointer to the `ERC20Storage` struct, which contains the `totalSupply` variable. The `burn` function directly modifies the `totalSupply` and the balance of the specified `_account` within this shared storage. Any facet that reads from or writes to this same storage slot will immediately observe these changes. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Burn/_category_.json b/website/docs/library/token/ERC20/Burn/_category_.json new file mode 100644 index 00000000..70a934b8 --- /dev/null +++ b/website/docs/library/token/ERC20/Burn/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Burn", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC20/Burn/index" + } +} diff --git a/website/docs/library/token/ERC20/Burn/index.mdx b/website/docs/library/token/ERC20/Burn/index.mdx new file mode 100644 index 00000000..e3c20dc6 --- /dev/null +++ b/website/docs/library/token/ERC20/Burn/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Burn" +description: "Burn extension for ERC-20 tokens." +--- + +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'; + + + Burn extension for ERC-20 tokens. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC20/Data/ERC20DataFacet.mdx b/website/docs/library/token/ERC20/Data/ERC20DataFacet.mdx new file mode 100644 index 00000000..2b1a637d --- /dev/null +++ b/website/docs/library/token/ERC20/Data/ERC20DataFacet.mdx @@ -0,0 +1,256 @@ +--- +sidebar_position: 200 +title: "ERC-20 Data Facet" +description: "ERC-20 token data retrieval within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Data/ERC20DataFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +ERC-20 token data retrieval within a diamond + + + +- Exposes external view functions for ERC-20 data retrieval. +- Reads data directly from shared diamond storage. +- No state-changing functions, ensuring read-only operations. +- Compatible with ERC-2535 diamond standard for seamless integration. + + +## Overview + +This facet provides external view functions for retrieving ERC-20 token data, such as total supply, account balances, and allowances. It interacts with shared diamond storage to access this information, ensuring consistency across all facets. Developers integrate this facet to expose token metrics externally while maintaining the diamond's upgradeability. + +## Storage + +### ERC20Storage + + +{`struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; + mapping(address owner => mapping(address spender => uint256 allowance)) allowance; +}`} + + +### State Variables + + + +## Functions + +### totalSupply + +Returns the total supply of tokens. + + +{`function totalSupply() external view returns (uint256);`} + + +**Returns:** + + + +--- +### balanceOf + +Returns the balance of a specific account. + + +{`function balanceOf(address _account) external view returns (uint256);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### allowance + +Returns the remaining number of tokens that a spender is allowed to spend on behalf of an owner. + + +{`function allowance(address _owner, address _spender) external view returns (uint256);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC20Data facet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + + + + +## Best Practices + + +- Ensure the `ERC20DataFacet` is added to the diamond and its selectors are registered. +- Access token data by calling functions through the diamond proxy address. +- Use the `exportSelectors` function during diamond deployment or upgrade to discover facet selectors. + + +## Security Considerations + + +This facet contains only view functions and does not modify state. Standard Solidity security practices should be followed when interacting with the diamond proxy. Input validation is handled by the underlying diamond proxy mechanism for external calls. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Data/_category_.json b/website/docs/library/token/ERC20/Data/_category_.json new file mode 100644 index 00000000..8f90b4b2 --- /dev/null +++ b/website/docs/library/token/ERC20/Data/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Data", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC20/Data/index" + } +} diff --git a/website/docs/library/token/ERC20/Data/index.mdx b/website/docs/library/token/ERC20/Data/index.mdx new file mode 100644 index 00000000..4cde8de5 --- /dev/null +++ b/website/docs/library/token/ERC20/Data/index.mdx @@ -0,0 +1,22 @@ +--- +title: "Data" +description: "Data extension for ERC-20 tokens." +--- + +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'; + + + Data extension for ERC-20 tokens. + + + + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC20/Metadata/ERC20MetadataFacet.mdx b/website/docs/library/token/ERC20/Metadata/ERC20MetadataFacet.mdx new file mode 100644 index 00000000..6a13d5d2 --- /dev/null +++ b/website/docs/library/token/ERC20/Metadata/ERC20MetadataFacet.mdx @@ -0,0 +1,206 @@ +--- +sidebar_position: 200 +title: "ERC-20 Metadata Facet" +description: "ERC-20 token metadata within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Metadata/ERC20MetadataFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +ERC-20 token metadata within a diamond + + + +- Exposes external view functions for token name, symbol, and decimals. +- Retrieves metadata from shared diamond storage. +- Compatible with ERC-2535 diamond standard for routing. +- Provides immutable selectors for discovery via `exportSelectors`. + + +## Overview + +This facet exposes ERC-20 token metadata functions, such as name, symbol, and decimals, through the diamond proxy. It accesses shared storage to retrieve these values, providing a consistent interface for token information within a Compose diamond. Developers add this facet to make token metadata accessible while maintaining diamond's upgradeability. + +## Storage + +### ERC20MetadataStorage + + +{`struct ERC20MetadataStorage { + string name; + string symbol; + uint8 decimals; +}`} + + +### State Variables + + + +## Functions + +### name + +Returns the name of the token. + + +{`function name() external view returns (string memory);`} + + +**Returns:** + + + +--- +### symbol + +Returns the symbol of the token. + + +{`function symbol() external view returns (string memory);`} + + +**Returns:** + + + +--- +### decimals + +Returns the number of decimals used for token precision. + + +{`function decimals() external view returns (uint8);`} + + +**Returns:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC20Metadata facet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + + + + +## Best Practices + + +- Ensure the `ERC20MetadataFacet` is correctly registered with the diamond's facet registry. +- Access token metadata via the diamond proxy address to leverage routing and upgradeability. +- Do not directly call internal functions of this facet; use its external ABI exposed through the diamond. + + +## Security Considerations + + +This facet contains only view functions and does not modify state. Input validation is not applicable. Follow standard Solidity security practices. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Metadata/ERC20MetadataMod.mdx b/website/docs/library/token/ERC20/Metadata/ERC20MetadataMod.mdx new file mode 100644 index 00000000..ea5b7c25 --- /dev/null +++ b/website/docs/library/token/ERC20/Metadata/ERC20MetadataMod.mdx @@ -0,0 +1,184 @@ +--- +sidebar_position: 210 +title: "ERC-20 Metadata Module" +description: "Manage ERC-20 token metadata within a diamond" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Metadata/ERC20MetadataMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manage ERC-20 token metadata within a diamond + + + +- Internal functions for setting and retrieving metadata. +- Utilizes the diamond storage pattern (EIP-8042) for shared state. +- No external dependencies, promoting composability. +- Directly manipulates storage slots for metadata. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions to set and retrieve ERC-20 token metadata, such as name, symbol, and decimals. Facets can import this module to manage token metadata using shared diamond storage, ensuring consistency across all facets interacting with the token. + +## Storage + +### ERC20MetadataStorage + +ERC-8042 compliant storage struct for ERC20 token data. storage-location: erc8042:erc20.metadata + + +{`struct ERC20MetadataStorage { + string name; + string symbol; + uint8 decimals; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the ERC20 storage struct from the predefined diamond storage slot. Uses inline assembly to set the storage slot reference. + + +{`function getStorage() pure returns (ERC20MetadataStorage storage s);`} + + +**Returns:** + + + +--- +### setMetadata + +Sets the metadata for the ERC20 token. + + +{`function setMetadata(string memory _name, string memory _symbol, uint8 _decimals) ;`} + + +**Parameters:** + + + + + + +## Best Practices + + +- Ensure the `ERC20MetadataMod` is correctly initialized with the diamond's storage pointer before calling `setMetadata`. +- Call `setMetadata` only once during the diamond's initialization phase to establish token identity. +- Verify that the `STORAGE_POSITION` and `keccak256("erc20.metadata")` key are correctly configured for the diamond's storage mapping. + + +## Integration Notes + + +This module interacts with diamond storage at a specific `STORAGE_POSITION` identified by `keccak256("erc20.metadata")`. The `ERC20MetadataStorage` struct, containing `name`, `symbol`, and `decimals`, is stored at this location. All facets that import and use this module will access and modify the same shared storage, ensuring metadata consistency across the diamond. Changes made by `setMetadata` are immediately visible to any facet calling `getStorage`. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Metadata/_category_.json b/website/docs/library/token/ERC20/Metadata/_category_.json new file mode 100644 index 00000000..d45bbd60 --- /dev/null +++ b/website/docs/library/token/ERC20/Metadata/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Metadata", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC20/Metadata/index" + } +} diff --git a/website/docs/library/token/ERC20/Metadata/index.mdx b/website/docs/library/token/ERC20/Metadata/index.mdx new file mode 100644 index 00000000..1cf0eb31 --- /dev/null +++ b/website/docs/library/token/ERC20/Metadata/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Metadata" +description: "Metadata extension for ERC-20 tokens." +--- + +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'; + + + Metadata extension for ERC-20 tokens. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC20/Mint/ERC20MintMod.mdx b/website/docs/library/token/ERC20/Mint/ERC20MintMod.mdx new file mode 100644 index 00000000..f08d88f6 --- /dev/null +++ b/website/docs/library/token/ERC20/Mint/ERC20MintMod.mdx @@ -0,0 +1,239 @@ +--- +sidebar_position: 210 +title: "ERC-20 Mint Module" +description: "Internal functions for minting ERC-20 tokens" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Mint/ERC20MintMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Internal functions for minting ERC-20 tokens + + + +- Provides `internal` functions for facet composition. +- Utilizes the diamond storage pattern (EIP-8042) for shared state. +- No external dependencies or `using` directives. +- Minimal gas overhead due to direct storage manipulation. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for minting ERC-20 tokens. Facets can import this module to manage token creation using shared diamond storage. Changes to total supply and recipient balances are immediately reflected across all facets accessing the same storage. + +## Storage + +### ERC20Storage + + +{`struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns a pointer to the ERC-20 storage struct. Uses inline assembly to bind the storage struct to the fixed storage position. + + +{`function getStorage() pure returns (ERC20Storage storage s);`} + + +**Returns:** + + + +--- +### mint + +Mints new tokens to a specified address. Increases both total supply and the recipient's balance. + + +{`function mint(address _account, uint256 _value) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when tokens are transferred between addresses. +
+ +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 _value);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the receiver address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidReceiver(address _receiver); + +
+
+
+ + + + +## Best Practices + + +- Ensure that access control for minting is enforced by the calling facet. +- Verify storage layout compatibility when upgrading or adding facets to avoid storage collisions. +- Handle the `ERC20InvalidReceiver` error if the minting logic were to incorporate checks for valid recipients. + + +## Integration Notes + + +This module interacts with diamond storage at a fixed position identified by `keccak256(\"erc20\")`. It reads and writes to the `ERC20Storage` struct, specifically modifying `totalSupply`. All state changes made by `mint` are immediately visible to any other facet that reads from the same storage position within the diamond. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Mint/_category_.json b/website/docs/library/token/ERC20/Mint/_category_.json new file mode 100644 index 00000000..c285c6f0 --- /dev/null +++ b/website/docs/library/token/ERC20/Mint/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Mint", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC20/Mint/index" + } +} diff --git a/website/docs/library/token/ERC20/Mint/index.mdx b/website/docs/library/token/ERC20/Mint/index.mdx new file mode 100644 index 00000000..d136abbd --- /dev/null +++ b/website/docs/library/token/ERC20/Mint/index.mdx @@ -0,0 +1,22 @@ +--- +title: "Mint" +description: "Mint extension for ERC-20 tokens." +--- + +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'; + + + Mint extension for ERC-20 tokens. + + + + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC20/Permit/ERC20PermitFacet.mdx b/website/docs/library/token/ERC20/Permit/ERC20PermitFacet.mdx new file mode 100644 index 00000000..4665d773 --- /dev/null +++ b/website/docs/library/token/ERC20/Permit/ERC20PermitFacet.mdx @@ -0,0 +1,356 @@ +--- +sidebar_position: 1 +title: "ERC-20 Permit Facet" +description: "Implements EIP-2612 permit for ERC-20 within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Permit/ERC20PermitFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Implements EIP-2612 permit for ERC-20 within a diamond + + + +- Implements EIP-2612 permit for off-chain approvals. +- Manages nonces within diamond storage for replay protection. +- Exposes `DOMAIN_SEPARATOR` for signature encoding. +- Includes `exportSelectors` for diamond facet discovery. + + +## Overview + +This facet implements EIP-2612 permit functionality, allowing users to grant allowances via signed messages. It integrates with diamond storage for nonce management and provides the necessary domain separator for signature validation. Developers add this facet to enable off-chain approval for token transfers. + +## Storage + +### ERC20MetadataStorage + + +{`struct ERC20MetadataStorage { + string name; +}`} + + +--- +### ERC20Storage + + +{`struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; + mapping(address owner => mapping(address spender => uint256 allowance)) allowance; +}`} + + +--- +### NoncesStorage + + +{`struct NoncesStorage { + mapping(address owner => uint256) nonces; +}`} + + +### State Variables + + + +## Functions + +### nonces + +Returns the current nonce for an owner. This value changes each time a permit is used. + + +{`function nonces(address _owner) external view returns (uint256);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### DOMAIN_SEPARATOR + +Returns the domain separator used in the encoding of the signature for permit. This value is unique to a contract and chain ID combination to prevent replay attacks. + + +{`function DOMAIN_SEPARATOR() external view returns (bytes32);`} + + +**Returns:** + + + +--- +### permit + +Sets the allowance for a spender via a signature. This function implements EIP-2612 permit functionality. + + +{`function permit( + address _owner, + address _spender, + uint256 _value, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s +) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC20PermitFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when an approval is made for a spender by an owner. +
+ +
+ Signature: + +{`event Approval(address indexed _owner, address indexed _spender, uint256 _value);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when a permit signature is invalid or expired. +
+ +
+ Signature: + +error ERC2612InvalidSignature( + address _owner, address _spender, uint256 _value, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s +); + +
+
+ +
+ Thrown when the spender address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidSpender(address _spender); + +
+
+
+ + + + +## Best Practices + + +- Initialize the ERC20PermitMod module before calling functions from this facet. +- Ensure the correct domain separator is used when generating signatures off-chain. +- Verify that the nonce for the owner has been incremented after a successful permit operation. + + +## Security Considerations + + +The `permit` function validates signatures using owner nonces and the domain separator to prevent replay attacks. Ensure off-chain signature generation uses the correct parameters. The `nonces` function increments the nonce after a successful `permit` call, which is critical for subsequent permit operations. Follow standard Solidity security practices for input validation and access control. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Permit/ERC20PermitMod.mdx b/website/docs/library/token/ERC20/Permit/ERC20PermitMod.mdx new file mode 100644 index 00000000..03a26bfc --- /dev/null +++ b/website/docs/library/token/ERC20/Permit/ERC20PermitMod.mdx @@ -0,0 +1,304 @@ +--- +sidebar_position: 2 +title: "ERC-20 Permit Module" +description: "Manage ERC-2612 permit signatures and domain separators" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Permit/ERC20PermitMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manage ERC-2612 permit signatures and domain separators + + + +- Provides internal functions for ERC-2612 `permit` functionality. +- Manages unique domain separators to prevent replay attacks. +- Interacts with diamond storage for allowance updates. +- No external dependencies, promoting composability. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module manages ERC-2612 permit logic, including domain separators and signature validation for allowance approvals. Facets can use this module to implement the permit functionality, ensuring unique domain separators per contract and chain ID to prevent replay attacks. Changes to allowances made via permit are recorded in diamond storage and are visible to all facets. + +## Storage + +### ERC20MetadataStorage + +storage-location: erc8042:erc20.metadata + + +{`struct ERC20MetadataStorage { + string name; +}`} + + +--- +### ERC20Storage + +storage-location: erc8042:erc20 + + +{`struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; + mapping(address owner => mapping(address spender => uint256 allowance)) allowance; +}`} + + +--- +### NoncesStorage + +storage-location: erc8042:nonces + + +{`struct NoncesStorage { + mapping(address owner => uint256) nonces; +}`} + + +### State Variables + + + +## Functions + +### DOMAIN_SEPARATOR + +Returns the domain separator used in the encoding of the signature for {permit}. This value is unique to a contract and chain ID combination to prevent replay attacks. + + +{`function DOMAIN_SEPARATOR() view returns (bytes32);`} + + +**Returns:** + + + +--- +### getERC20MetadataStorage + + +{`function getERC20MetadataStorage() pure returns (ERC20MetadataStorage storage s);`} + + +--- +### getERC20Storage + + +{`function getERC20Storage() pure returns (ERC20Storage storage s);`} + + +--- +### getPermitStorage + + +{`function getPermitStorage() pure returns (NoncesStorage storage s);`} + + +--- +### permit + +Validates a permit signature and sets allowance. Emits Approval event; must be emitted by the calling facet/contract. + + +{`function permit(address _owner, address _spender, uint256 _value, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when an approval is made for a spender by an owner. +
+ +
+ Signature: + +{`event Approval(address indexed _owner, address indexed _spender, uint256 _value);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the spender address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidSpender(address _spender); + +
+
+ +
+ Thrown when a permit signature is invalid or expired. +
+ +
+ Signature: + +error ERC2612InvalidSignature( +address _owner, address _spender, uint256 _value, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s +); + +
+
+
+ + + + +## Best Practices + + +- Ensure the calling facet correctly emits the `Approval` event after a successful `permit` call. +- Verify that the `DOMAIN_SEPARATOR` used by the module is correctly initialized and unique to the contract and chain ID. +- Handle the `ERC2612InvalidSignature` and `ERC20InvalidSpender` errors to provide appropriate feedback to users. + + +## Integration Notes + + +This module interacts with diamond storage to manage permit-related state, specifically related to allowances and nonces. The `permit` function updates allowance values directly in the shared diamond storage. The `DOMAIN_SEPARATOR` function is crucial for signature verification and ensures that permits are specific to the contract instance and chain. Changes made via the `permit` function are immediately visible to any other facet that reads the allowance state from the diamond storage. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Permit/_category_.json b/website/docs/library/token/ERC20/Permit/_category_.json new file mode 100644 index 00000000..50cce4c9 --- /dev/null +++ b/website/docs/library/token/ERC20/Permit/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Permit", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC20/Permit/index" + } +} diff --git a/website/docs/library/token/ERC20/Permit/index.mdx b/website/docs/library/token/ERC20/Permit/index.mdx new file mode 100644 index 00000000..6fc82240 --- /dev/null +++ b/website/docs/library/token/ERC20/Permit/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Permit" +description: "Permit extension for ERC-20 tokens." +--- + +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'; + + + Permit extension for ERC-20 tokens. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC20/Transfer/ERC20TransferFacet.mdx b/website/docs/library/token/ERC20/Transfer/ERC20TransferFacet.mdx new file mode 100644 index 00000000..8ff68644 --- /dev/null +++ b/website/docs/library/token/ERC20/Transfer/ERC20TransferFacet.mdx @@ -0,0 +1,331 @@ +--- +sidebar_position: 200 +title: "ERC-20 Transfer Facet" +description: "ERC-20 token transfers within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Transfer/ERC20TransferFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +ERC-20 token transfers within a diamond + + + +- Exposes external `transfer` and `transferFrom` functions for diamond interaction. +- Manages token balances and allowances using shared diamond storage. +- Emits standard `Transfer` events for off-chain monitoring. +- Utilizes custom errors for clear revert reasons. + + +## Overview + +This facet implements ERC-20 token transfer functionality, exposing external functions for token movement within a diamond. It accesses shared diamond storage to manage balances and allowances, adhering to the ERC-2535 standard for upgradeability. Developers integrate this facet to enable token operations via the diamond proxy. + +## Storage + +### ERC20Storage + + +{`struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; + mapping(address owner => mapping(address spender => uint256 allowance)) allowance; +}`} + + +### State Variables + + + +## Functions + +### transfer + +Transfers tokens to another address. Emits a Transfer event. + + +{`function transfer(address _to, uint256 _value) external returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### transferFrom + +Transfers tokens on behalf of another account, provided sufficient allowance exists. Emits a Transfer event and decreases the spender's allowance. + + +{`function transferFrom(address _from, address _to, uint256 _value) external returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC20TransferFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when tokens are transferred between two addresses. +
+ +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 _value);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when an account has insufficient balance for a transfer or burn. +
+ +
+ Signature: + +error ERC20InsufficientBalance(address _sender, uint256 _balance, uint256 _needed); + +
+
+ +
+ Thrown when the sender address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidSender(address _sender); + +
+
+ +
+ Thrown when the receiver address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidReceiver(address _receiver); + +
+
+ +
+ Thrown when a spender tries to use more than the approved allowance. +
+ +
+ Signature: + +error ERC20InsufficientAllowance(address _spender, uint256 _allowance, uint256 _needed); + +
+
+ +
+ Thrown when the spender address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidSpender(address _spender); + +
+
+
+ + + + +## Best Practices + + +- Ensure the ERC20Storage struct is correctly initialized in diamond storage. +- Verify that allowances are managed correctly before calling `transferFrom`. +- Use the `exportSelectors` function during diamond upgrades to discover facet selectors. + + +## Security Considerations + + +Functions are protected by Compose's access control mechanisms. Input validation is performed for recipient addresses and transfer values. The `transferFrom` function requires sufficient allowance to be set by the token owner. Reentrancy is mitigated by adhering to the checks-effects-interactions pattern. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Transfer/ERC20TransferMod.mdx b/website/docs/library/token/ERC20/Transfer/ERC20TransferMod.mdx new file mode 100644 index 00000000..7810170f --- /dev/null +++ b/website/docs/library/token/ERC20/Transfer/ERC20TransferMod.mdx @@ -0,0 +1,380 @@ +--- +sidebar_position: 210 +title: "ERC-20 Transfer Module" +description: "Internal ERC-20 token transfer functions" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC20/Transfer/ERC20TransferMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Internal ERC-20 token transfer functions + + + +- Internal functions for token transfers, designed for use within custom facets. +- Utilizes diamond storage (EIP-8042) for state management. +- No external dependencies, promoting composability. +- Emits `Transfer` events upon successful transfers. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for executing ERC-20 token transfers and transfers from an allowance. Facets can import and utilize these functions to manage token balances and operations within the diamond storage pattern. Changes to balances are immediately reflected across all facets accessing the same storage. + +## Storage + +### ERC20Storage + + +{`struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; + mapping(address owner => mapping(address spender => uint256 allowance)) allowance; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns a pointer to the ERC-20 storage struct. Uses inline assembly to bind the storage struct to the fixed storage position. + + +{`function getStorage() pure returns (ERC20Storage storage s);`} + + +**Returns:** + + + +--- +### transfer + +Transfers tokens from the caller to another address. Updates balances directly without allowance mechanism. + + +{`function transfer(address _to, uint256 _value) returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### transferFrom + +Transfers tokens from one address to another using an allowance. Deducts the spender's allowance and updates balances. + + +{`function transferFrom(address _from, address _to, uint256 _value) returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +## Events + + + +
+ Emitted when an approval is made for a spender by an owner. +
+ +
+ Signature: + +{`event Approval(address indexed _owner, address indexed _spender, uint256 _value);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when tokens are transferred between addresses. +
+ +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 _value);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when a spender tries to spend more than their allowance. +
+ +
+ Signature: + +error ERC20InsufficientAllowance(address _spender, uint256 _allowance, uint256 _needed); + +
+
+ +
+ Thrown when a sender attempts to transfer or burn more tokens than their balance. +
+ +
+ Signature: + +error ERC20InsufficientBalance(address _sender, uint256 _balance, uint256 _needed); + +
+
+ +
+ Thrown when the receiver address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidReceiver(address _receiver); + +
+
+ +
+ Thrown when the sender address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidSender(address _sender); + +
+
+ +
+ Thrown when the spender address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC20InvalidSpender(address _spender); + +
+
+
+ + + + +## Best Practices + + +- Ensure the caller has sufficient balance before calling `transfer`. +- Verify the spender has sufficient allowance before calling `transferFrom`. +- Handle potential errors such as `ERC20InvalidReceiver` or `ERC20InsufficientBalance`. +- Use the `getStorage()` function to access the storage pointer for direct inspection if necessary. + + +## Integration Notes + + +This module reads and writes to diamond storage at the `STORAGE_POSITION`, identified by `keccak256("erc20")`. The `ERC20Storage` struct, containing fields like `totalSupply`, is managed here. All functions operate directly on this shared storage, ensuring that balance updates are immediately visible to any facet that accesses the same storage slot. The `getStorage()` function provides a direct pointer to this struct via inline assembly. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC20/Transfer/_category_.json b/website/docs/library/token/ERC20/Transfer/_category_.json new file mode 100644 index 00000000..b668c1db --- /dev/null +++ b/website/docs/library/token/ERC20/Transfer/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Transfer", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC20/Transfer/index" + } +} diff --git a/website/docs/library/token/ERC20/Transfer/index.mdx b/website/docs/library/token/ERC20/Transfer/index.mdx new file mode 100644 index 00000000..8aae7138 --- /dev/null +++ b/website/docs/library/token/ERC20/Transfer/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Transfer" +description: "Transfer extension for ERC-20 tokens." +--- + +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'; + + + Transfer extension for ERC-20 tokens. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC20/_category_.json b/website/docs/library/token/ERC20/_category_.json new file mode 100644 index 00000000..0e078cb1 --- /dev/null +++ b/website/docs/library/token/ERC20/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "ERC-20", + "position": 1, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC20/index" + } +} diff --git a/website/docs/library/token/ERC20/index.mdx b/website/docs/library/token/ERC20/index.mdx new file mode 100644 index 00000000..88687227 --- /dev/null +++ b/website/docs/library/token/ERC20/index.mdx @@ -0,0 +1,71 @@ +--- +title: "ERC-20" +description: "ERC-20 fungible token implementations." +--- + +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'; + + + ERC-20 fungible token implementations. + + + + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC6909/ERC6909/ERC6909Facet.mdx b/website/docs/library/token/ERC6909/ERC6909/ERC6909Facet.mdx new file mode 100644 index 00000000..db5afd8e --- /dev/null +++ b/website/docs/library/token/ERC6909/ERC6909/ERC6909Facet.mdx @@ -0,0 +1,511 @@ +--- +sidebar_position: 1 +title: "ERC-6909 Facet" +description: "ERC-6909 token functionality within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC6909/ERC6909/ERC6909Facet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +ERC-6909 token functionality within a diamond + + + +- Implements ERC-6909 token transfer and approval functions. +- Utilizes diamond storage for shared state management. +- Exposes external functions for direct diamond interaction. +- No external dependencies beyond diamond proxy and storage. + + +## Overview + +This facet implements ERC-6909 token functionality as external functions callable through a diamond proxy. It accesses shared diamond storage to manage token balances, allowances, and operator approvals. Developers integrate this facet to expose token operations while leveraging the diamond's upgradeability and composability. + +## Storage + +### ERC6909Storage + + +{`struct ERC6909Storage { + mapping(address owner => mapping(uint256 id => uint256 amount)) balanceOf; + mapping(address owner => mapping(address spender => mapping(uint256 id => uint256 amount))) allowance; + mapping(address owner => mapping(address spender => bool)) isOperator; +}`} + + +### State Variables + + + +## Functions + +### balanceOf + +Owner balance of an id. + + +{`function balanceOf(address _owner, uint256 _id) external view returns (uint256);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### allowance + +Spender allowance of an id. + + +{`function allowance(address _owner, address _spender, uint256 _id) external view returns (uint256);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### isOperator + +Checks if a spender is approved by an owner as an operator. + + +{`function isOperator(address _owner, address _spender) external view returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### transfer + +Transfers an amount of an id from the caller to a receiver. + + +{`function transfer(address _receiver, uint256 _id, uint256 _amount) external returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### transferFrom + +Transfers an amount of an id from a sender to a receiver. + + +{`function transferFrom(address _sender, address _receiver, uint256 _id, uint256 _amount) external returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### approve + +Approves an amount of an id to a spender. + + +{`function approve(address _spender, uint256 _id, uint256 _amount) external returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### setOperator + +Sets or removes a spender as an operator for the caller. + + +{`function setOperator(address _spender, bool _approved) external returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +## Events + + + + +
+ Signature: + +{`event Transfer( + address _caller, address indexed _sender, address indexed _receiver, uint256 indexed _id, uint256 _amount +);`} + +
+ +
+ + +
+ Signature: + +{`event OperatorSet(address indexed _owner, address indexed _spender, bool _approved);`} + +
+ +
+ + +
+ Signature: + +{`event Approval(address indexed _owner, address indexed _spender, uint256 indexed _id, uint256 _amount);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error ERC6909InsufficientBalance(address _sender, uint256 _balance, uint256 _needed, uint256 _id); + +
+
+ + +
+ Signature: + +error ERC6909InsufficientAllowance(address _spender, uint256 _allowance, uint256 _needed, uint256 _id); + +
+
+ + +
+ Signature: + +error ERC6909InvalidReceiver(address _receiver); + +
+
+ + +
+ Signature: + +error ERC6909InvalidSender(address _sender); + +
+
+ + +
+ Signature: + +error ERC6909InvalidSpender(address _spender); + +
+
+
+ + + + +## Best Practices + + +- Initialize ERC6909 storage correctly before calling any state-changing functions. +- Ensure the caller has sufficient balance or allowance before attempting transfers. +- Verify that operator approvals are managed securely and revoked when necessary. + + +## Security Considerations + + +The `transfer` and `transferFrom` functions check for sufficient balance and allowance before executing state changes, adhering to a checks-effects-interactions pattern. Input validation is performed to prevent invalid receivers or senders. Reentrancy is mitigated by the checks-effects-interactions pattern. Operator approvals are managed via `setOperator` and checked in `transferFrom`. All state-changing functions are intended to be called through a diamond proxy, inheriting its access control mechanisms. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC6909/ERC6909/ERC6909Mod.mdx b/website/docs/library/token/ERC6909/ERC6909/ERC6909Mod.mdx new file mode 100644 index 00000000..126fcade --- /dev/null +++ b/website/docs/library/token/ERC6909/ERC6909/ERC6909Mod.mdx @@ -0,0 +1,564 @@ +--- +sidebar_position: 2 +title: "ERC-6909 Module" +description: "Internal functions for ERC-6909 multi-token logic" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC6909/ERC6909/ERC6909Mod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Internal functions for ERC-6909 multi-token logic + + + +- Provides `internal` functions for core ERC-6909 token operations. +- Leverages the diamond storage pattern (EIP-8042) for shared state management. +- No external dependencies or `using` directives, promoting composability. +- Designed for integration within ERC-2535 compliant diamonds. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for managing ERC-6909 compliant multi-tokens within a diamond. Facets can import and utilize these functions to perform essential operations like minting, burning, transferring, and managing approvals using shared diamond storage. Changes made through these internal functions are immediately visible across all facets interacting with the same storage. + +## Storage + +### ERC6909Storage + +storage-location: erc8042:erc6909 + + +{`struct ERC6909Storage { + mapping(address owner => mapping(uint256 id => uint256 amount)) balanceOf; + mapping(address owner => mapping(address spender => mapping(uint256 id => uint256 amount))) allowance; + mapping(address owner => mapping(address spender => bool)) isOperator; +}`} + + +### State Variables + + + +## Functions + +### approve + +Approves an amount of an id to a spender. + + +{`function approve(address _owner, address _spender, uint256 _id, uint256 _amount) ;`} + + +**Parameters:** + + + +--- +### burn + +Burns `_amount` of token id `_id` from `_from`. + + +{`function burn(address _from, uint256 _id, uint256 _amount) ;`} + + +**Parameters:** + + + +--- +### getStorage + +Returns a pointer to the ERC-6909 storage struct. Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + + +{`function getStorage() pure returns (ERC6909Storage storage s);`} + + +**Returns:** + + + +--- +### mint + +Mints `_amount` of token id `_id` to `_to`. + + +{`function mint(address _to, uint256 _id, uint256 _amount) ;`} + + +**Parameters:** + + + +--- +### setOperator + +Sets or removes a spender as an operator for the caller. + + +{`function setOperator(address _owner, address _spender, bool _approved) ;`} + + +**Parameters:** + + + +--- +### transfer + +Transfers `_amount` of token id `_id` from `_from` to `_to`. Allowance is not deducted if it is `type(uint256).max` Allowance is not deducted if `_by` is an operator for `_from`. + + +{`function transfer(address _by, address _from, address _to, uint256 _id, uint256 _amount) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when an approval occurs. +
+ +
+ Signature: + +{`event Approval(address indexed _owner, address indexed _spender, uint256 indexed _id, uint256 _amount);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when an operator is set. +
+ +
+ Signature: + +{`event OperatorSet(address indexed _owner, address indexed _spender, bool _approved);`} + +
+ +
+ Parameters: + +
+
+ +
+ Emitted when a transfer occurs. +
+ +
+ Signature: + +{`event Transfer( +address _caller, address indexed _sender, address indexed _receiver, uint256 indexed _id, uint256 _amount +);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the spender has insufficient allowance. +
+ +
+ Signature: + +error ERC6909InsufficientAllowance(address _spender, uint256 _allowance, uint256 _needed, uint256 _id); + +
+
+ +
+ Thrown when the sender has insufficient balance. +
+ +
+ Signature: + +error ERC6909InsufficientBalance(address _sender, uint256 _balance, uint256 _needed, uint256 _id); + +
+
+ +
+ Thrown when the approver address is invalid. +
+ +
+ Signature: + +error ERC6909InvalidApprover(address _approver); + +
+
+ +
+ Thrown when the receiver address is invalid. +
+ +
+ Signature: + +error ERC6909InvalidReceiver(address _receiver); + +
+
+ +
+ Thrown when the sender address is invalid. +
+ +
+ Signature: + +error ERC6909InvalidSender(address _sender); + +
+
+ +
+ Thrown when the spender address is invalid. +
+ +
+ Signature: + +error ERC6909InvalidSpender(address _spender); + +
+
+
+ + + + +## Best Practices + + +- Ensure that any facet calling `mint`, `burn`, `transfer`, `approve`, or `setOperator` has the necessary access control checks implemented, as these functions are internal. +- Verify the storage layout compatibility when upgrading facets to prevent unexpected behavior or data corruption, especially regarding the `ERC6909Storage` struct. +- Handle all custom errors (`ERC6909InsufficientAllowance`, `ERC6909InsufficientBalance`, etc.) returned by the module's functions to ensure robust error management. + + +## Integration Notes + + +This module utilizes the diamond storage pattern, storing its state at a specific `STORAGE_POSITION` identified by `keccak2535("erc6909")`. The `ERC6909Storage` struct, though empty in this definition, serves as the container for token-specific data. All functions operate internally and directly interact with this shared diamond storage. Any modifications made to the token state via functions like `mint`, `burn`, or `transfer` are immediately reflected and accessible by any other facet that reads from the same `STORAGE_POSITION`. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC6909/ERC6909/_category_.json b/website/docs/library/token/ERC6909/ERC6909/_category_.json new file mode 100644 index 00000000..d4d084dc --- /dev/null +++ b/website/docs/library/token/ERC6909/ERC6909/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "ERC-6909", + "position": 4, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC6909/ERC6909/index" + } +} diff --git a/website/docs/library/token/ERC6909/ERC6909/index.mdx b/website/docs/library/token/ERC6909/ERC6909/index.mdx new file mode 100644 index 00000000..4b8dfa0c --- /dev/null +++ b/website/docs/library/token/ERC6909/ERC6909/index.mdx @@ -0,0 +1,29 @@ +--- +title: "ERC-6909" +description: "ERC-6909 minimal multi-token implementations." +--- + +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'; + + + ERC-6909 minimal multi-token implementations. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC6909/_category_.json b/website/docs/library/token/ERC6909/_category_.json new file mode 100644 index 00000000..42f1101f --- /dev/null +++ b/website/docs/library/token/ERC6909/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "ERC-6909", + "position": 4, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC6909/index" + } +} diff --git a/website/docs/library/token/ERC6909/index.mdx b/website/docs/library/token/ERC6909/index.mdx new file mode 100644 index 00000000..b91f1e51 --- /dev/null +++ b/website/docs/library/token/ERC6909/index.mdx @@ -0,0 +1,22 @@ +--- +title: "ERC-6909" +description: "ERC-6909 minimal multi-token implementations." +--- + +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'; + + + ERC-6909 minimal multi-token implementations. + + + + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC721/Approve/ERC721ApproveFacet.mdx b/website/docs/library/token/ERC721/Approve/ERC721ApproveFacet.mdx new file mode 100644 index 00000000..aebfa896 --- /dev/null +++ b/website/docs/library/token/ERC721/Approve/ERC721ApproveFacet.mdx @@ -0,0 +1,248 @@ +--- +sidebar_position: 200 +title: "ERC-721 Approve Facet" +description: "Manage ERC-721 approvals within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Approve/ERC721ApproveFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manage ERC-721 approvals within a diamond + + + +- Exposes external functions for ERC-721 approval management. +- Interacts with shared diamond storage via `ERC721Storage`. +- Supports `approve` and `setApprovalForAll` operations. +- Provides `exportSelectors` for discovery within a diamond. + + +## Overview + +This facet provides external functions for managing ERC-721 token approvals within a diamond. It interacts with shared diamond storage to track token ownership and approvals. Developers integrate this facet to enable external calls for approving token transfers and operator permissions, ensuring upgradeability and composability. + +## Storage + +### ERC721Storage + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### approve + +Approves another address to transfer the given token ID. + + +{`function approve(address _to, uint256 _tokenId) external;`} + + +**Parameters:** + + + +--- +### setApprovalForAll + +Approves or revokes permission for an operator to manage all caller's assets. + + +{`function setApprovalForAll(address _operator, bool _approved) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC721ApproveFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + + +
+ Signature: + +{`event Approval(address indexed _owner, address indexed _to, uint256 indexed _tokenId);`} + +
+ +
+ + +
+ Signature: + +{`event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error ERC721NonexistentToken(uint256 _tokenId); + +
+
+ + +
+ Signature: + +error ERC721InvalidApprover(address _approver); + +
+
+ + +
+ Signature: + +error ERC721InvalidOperator(address _operator); + +
+
+
+ + + + +## Best Practices + + +- Initialize the facet and its associated storage correctly during diamond deployment. +- Ensure that access control for state-changing functions aligns with your diamond's security model. +- When upgrading, verify that the storage slot `STORAGE_POSITION` is compatible with previous versions to prevent data loss. + + +## Security Considerations + + +The `approve` function reverts with `ERC721NonexistentToken` if the token ID does not exist, and `ERC721InvalidApprover` if the caller is not the owner or approved delegate. The `setApprovalForAll` function reverts with `ERC721InvalidOperator` if the operator is the caller. Follow standard Solidity security practices for input validation and access control. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Approve/ERC721ApproveMod.mdx b/website/docs/library/token/ERC721/Approve/ERC721ApproveMod.mdx new file mode 100644 index 00000000..d3d4a76c --- /dev/null +++ b/website/docs/library/token/ERC721/Approve/ERC721ApproveMod.mdx @@ -0,0 +1,260 @@ +--- +sidebar_position: 210 +title: "ERC-721 Approve Module" +description: "Manages ERC-721 token approvals and operator status" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Approve/ERC721ApproveMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages ERC-721 token approvals and operator status + + + +- Provides internal functions for ERC-721 token approvals and operator management. +- Utilizes the diamond storage pattern (EIP-8042) for shared state. +- No external dependencies, promoting composability within a diamond. +- Emits `Approval` and `ApprovalForAll` events upon successful state changes. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for managing ERC-721 token approvals and operator status within a diamond. Facets can import this module to interact with shared ERC-721 storage, ensuring consistent state management across the diamond. Changes to approvals and operator statuses are immediately visible to all facets accessing the same diamond storage. + +## Storage + +### ERC721Storage + +storage-location: erc8042:erc721 + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### approve + +Approves another address to transfer the given token ID. + + +{`function approve(address _to, uint256 _tokenId) ;`} + + +**Parameters:** + + + +--- +### getStorage + +Returns a pointer to the ERC-721 storage struct. Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + + +{`function getStorage() pure returns (ERC721Storage storage s);`} + + +**Returns:** + + + +--- +### setApprovalForAll + +Approves or revokes permission for an operator to manage all users's assets. + + +{`function setApprovalForAll(address user, address _operator, bool _approved) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when the approved address for an NFT is changed or reaffirmed. +
+ +
+ Signature: + +{`event Approval(address indexed _owner, address indexed _to, uint256 indexed _tokenId);`} + +
+ +
+ +
+ Emitted when an operator is enabled or disabled for an owner. +
+ +
+ Signature: + +{`event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);`} + +
+ +
+
+ +## Errors + + + +
+ Error indicating the operator address is invalid. +
+ +
+ Signature: + +error ERC721InvalidOperator(address _operator); + +
+
+ +
+ **Title:** ERC-721 Approve Module Error indicating that the queried token does not exist. +
+ +
+ Signature: + +error ERC721NonexistentToken(uint256 _tokenId); + +
+
+
+ + + + +## Best Practices + + +- Call `approve` or `setApprovalForAll` only after verifying caller permissions if required by your facet's logic. +- Ensure the `ERC721Storage` struct definition in your facet matches the one defined in this module to prevent storage collisions. +- Handle potential errors like `ERC721NonexistentToken` or `ERC721InvalidOperator` when calling the module's functions. + + +## Integration Notes + + +This module interacts with diamond storage at the slot identified by `STORAGE_POSITION`, which is defined as `keccak256(\"erc721\")`. It uses the `ERC721Storage` struct to manage approval data. All state modifications made through this module are written directly to this shared storage slot, making them immediately visible to any other facet within the same diamond that reads from or writes to this storage position. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Approve/_category_.json b/website/docs/library/token/ERC721/Approve/_category_.json new file mode 100644 index 00000000..b402cfd1 --- /dev/null +++ b/website/docs/library/token/ERC721/Approve/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Approve", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC721/Approve/index" + } +} diff --git a/website/docs/library/token/ERC721/Approve/index.mdx b/website/docs/library/token/ERC721/Approve/index.mdx new file mode 100644 index 00000000..6c0dc936 --- /dev/null +++ b/website/docs/library/token/ERC721/Approve/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Approve" +description: "Approve extension for ERC-721 tokens." +--- + +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'; + + + Approve extension for ERC-721 tokens. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC721/Burn/ERC721BurnFacet.mdx b/website/docs/library/token/ERC721/Burn/ERC721BurnFacet.mdx new file mode 100644 index 00000000..c2da7bfc --- /dev/null +++ b/website/docs/library/token/ERC721/Burn/ERC721BurnFacet.mdx @@ -0,0 +1,225 @@ +--- +sidebar_position: 3 +title: "ERC-721 Burn Facet" +description: "Burns ERC-721 tokens within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Burn/ERC721BurnFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Burns ERC-721 tokens within a diamond + + + +- Exposes external functions for burning ERC-721 tokens through a diamond proxy. +- Interacts with shared diamond storage for token management. +- Supports burning single tokens and batches of tokens. +- Includes `exportSelectors` for diamond selector discovery. + + +## Overview + +This facet provides functionality to burn (destroy) ERC-721 tokens. It interacts with diamond storage to manage token destruction. Developers integrate this facet to enable token holders to permanently remove their tokens from circulation via the diamond proxy. + +## Storage + +### ERC721Storage + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### burn + +Burns (destroys) a token, removing it from enumeration tracking. + + +{`function burn(uint256 _tokenId) external;`} + + +**Parameters:** + + + +--- +### burnBatch + +Burns (destroys) a token, removing it from enumeration tracking. + + +{`function burnBatch(uint256[] memory _tokenIds) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC721BurnFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + + +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error ERC721NonexistentToken(uint256 _tokenId); + +
+
+ + +
+ Signature: + +error ERC721InsufficientApproval(address _operator, uint256 _tokenId); + +
+
+
+ + + + +## Best Practices + + +- Ensure the ERC721BurnFacet is properly initialized with access control if required by your diamond architecture. +- Validate that the caller has the necessary permissions to burn tokens before calling the `burn` or `burnBatch` functions. +- Verify storage slot compatibility with other facets before upgrading or adding this facet to a diamond. + + +## Security Considerations + + +The `burn` and `burnBatch` functions modify state by destroying tokens. Ensure that appropriate access control mechanisms are in place within the diamond to restrict who can call these functions. The facet does not explicitly implement reentrancy guards; follow standard Solidity security practices for external calls if any are introduced by related facets. Input validation for `_tokenIds` is crucial to prevent unexpected behavior or denial of service. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Burn/ERC721BurnMod.mdx b/website/docs/library/token/ERC721/Burn/ERC721BurnMod.mdx new file mode 100644 index 00000000..00f01a5c --- /dev/null +++ b/website/docs/library/token/ERC721/Burn/ERC721BurnMod.mdx @@ -0,0 +1,237 @@ +--- +sidebar_position: 210 +title: "ERC-721 Burn Module" +description: "Burns ERC-721 tokens using diamond storage" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Burn/ERC721BurnMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Burns ERC-721 tokens using diamond storage + + + +- Provides `internal` functions for token burning. +- Utilizes the diamond storage pattern for shared state. +- No external dependencies, ensuring minimal on-chain footprint. +- Emits `Transfer` event upon successful token burning. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for burning ERC-721 tokens. Facets can import and use this module to remove tokens from circulation via shared diamond storage. Token state changes made through this module are immediately visible to all facets accessing the same storage. + +## Storage + +### ERC721Storage + +Storage layout for ERC-721 token management. Defines ownership, balances, approvals, and operator mappings per ERC-721 standard. storage-location: erc8042:erc721 + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### burn + +Burns (destroys) a specific ERC-721 token. Reverts if the token does not exist. Clears ownership and approval. This module does not perform approval checks. Ensure proper ownership or approval validation before calling this function. + + +{`function burn(uint256 _tokenId) ;`} + + +**Parameters:** + + + +--- +### getStorage + +Returns the ERC-721 storage struct from its predefined slot. Uses inline assembly to access diamond storage location. + + +{`function getStorage() pure returns (ERC721Storage storage s);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when ownership of a token changes, including minting and burning. +
+ +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when attempting to interact with a non-existent token. +
+ +
+ Signature: + +error ERC721NonexistentToken(uint256 _tokenId); + +
+
+
+ + + + +## Best Practices + + +- Ensure appropriate ownership or approval checks are performed by the calling facet before invoking `burn`. +- Verify that the `ERC721Storage` struct definition used by this module is compatible with your diamond's storage layout. +- Handle the `ERC721NonexistentToken` error if the token ID provided to `burn` does not exist. + + +## Integration Notes + + +This module interacts with diamond storage at the `STORAGE_POSITION` defined by `keccak256(\"erc721\")`. The `burn` function modifies internal state within the `ERC721Storage` struct, including clearing ownership and approvals for the specified token ID. The `getStorage` function provides direct access to this shared storage struct via inline assembly. Changes to this storage are immediately visible to all facets within the same diamond. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Burn/_category_.json b/website/docs/library/token/ERC721/Burn/_category_.json new file mode 100644 index 00000000..8cb67023 --- /dev/null +++ b/website/docs/library/token/ERC721/Burn/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Burn", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC721/Burn/index" + } +} diff --git a/website/docs/library/token/ERC721/Burn/index.mdx b/website/docs/library/token/ERC721/Burn/index.mdx new file mode 100644 index 00000000..7c5efb36 --- /dev/null +++ b/website/docs/library/token/ERC721/Burn/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Burn" +description: "Burn extension for ERC-721 tokens." +--- + +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'; + + + Burn extension for ERC-721 tokens. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC721/Data/ERC721DataFacet.mdx b/website/docs/library/token/ERC721/Data/ERC721DataFacet.mdx new file mode 100644 index 00000000..51425adc --- /dev/null +++ b/website/docs/library/token/ERC721/Data/ERC721DataFacet.mdx @@ -0,0 +1,314 @@ +--- +sidebar_position: 200 +title: "ERC-721 Data Facet" +description: "ERC-721 token data retrieval functions for diamonds" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Data/ERC721DataFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +ERC-721 token data retrieval functions for diamonds + + + +- Exposes external view functions for ERC-721 token data. +- Accesses shared diamond storage via `getStorage` internal function. +- Supports ERC-721 data querying patterns like `ownerOf`, `balanceOf`, `getApproved`, and `isApprovedForAll`. +- Provides `exportSelectors` for diamond facet discovery. + + +## Overview + +This facet provides external view functions for retrieving ERC-721 token ownership and approval data within a diamond. It accesses shared diamond storage to provide these details, enabling other facets or external contracts to query token information without direct storage access. Developers integrate this facet to expose standard ERC-721 data queries through the diamond proxy interface. + +## Storage + +### ERC721Storage + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### balanceOf + +Returns the number of tokens owned by a given address. + + +{`function balanceOf(address _owner) external view returns (uint256);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### ownerOf + +Returns the owner of a given token ID. + + +{`function ownerOf(uint256 _tokenId) public view returns (address);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### getApproved + +Returns the approved address for a given token ID. + + +{`function getApproved(uint256 _tokenId) external view returns (address);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### isApprovedForAll + +Returns true if an operator is approved to manage all of an owner's assets. + + +{`function isApprovedForAll(address _owner, address _operator) external view returns (bool);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC721DataFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Errors + + + + +
+ Signature: + +error ERC721InvalidOwner(address _owner); + +
+
+ + +
+ Signature: + +error ERC721NonexistentToken(uint256 _tokenId); + +
+
+
+ + + + +## Best Practices + + +- Ensure the ERC721DataFacet is correctly registered with the diamond's facet registry. +- Always call functions like `ownerOf` and `balanceOf` through the diamond proxy address. +- Verify that the `ERC721Storage` struct is correctly initialized and populated by other facets before querying. + + +## Security Considerations + + +This facet primarily exposes view functions and does not perform state modifications. Ensure that the underlying ERC721Storage is managed securely by other facets. The `ownerOf` and `balanceOf` functions revert with `ERC721NonexistentToken` if the token ID does not exist, and `ownerOf` reverts with `ERC721InvalidOwner` if the owner address is zero. Input validation for token IDs and addresses is handled by the facet. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Data/_category_.json b/website/docs/library/token/ERC721/Data/_category_.json new file mode 100644 index 00000000..4d838623 --- /dev/null +++ b/website/docs/library/token/ERC721/Data/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Data", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC721/Data/index" + } +} diff --git a/website/docs/library/token/ERC721/Data/index.mdx b/website/docs/library/token/ERC721/Data/index.mdx new file mode 100644 index 00000000..4bddc55f --- /dev/null +++ b/website/docs/library/token/ERC721/Data/index.mdx @@ -0,0 +1,22 @@ +--- +title: "Data" +description: "Data extension for ERC-721 tokens." +--- + +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'; + + + Data extension for ERC-721 tokens. + + + + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnFacet.mdx b/website/docs/library/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnFacet.mdx new file mode 100644 index 00000000..b2ef53d6 --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnFacet.mdx @@ -0,0 +1,227 @@ +--- +sidebar_position: 3 +title: "ERC-721 Enumerable Burn Facet" +description: "ERC-721 token burning and enumeration tracking" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +ERC-721 token burning and enumeration tracking + + + +- Implements ERC-721 token burning. +- Integrates with diamond storage for enumeration tracking. +- Exposes `burn` function externally via the diamond proxy. +- Provides internal functions (`getStorage`, `getERC721Storage`) for accessing facet-specific and shared storage. + + +## Overview + +This facet implements ERC-721 token burning functionality within a Compose diamond. It exposes external functions for destroying tokens and integrates with diamond storage for enumeration tracking. Developers add this facet to manage token lifecycle and maintain correct token counts. + +## Storage + +### ERC721EnumerableStorage + + +{`struct ERC721EnumerableStorage { + mapping(address owner => mapping(uint256 index => uint256 tokenId)) ownerTokens; + mapping(uint256 tokenId => uint256 ownerTokensIndex) ownerTokensIndex; + uint256[] allTokens; + mapping(uint256 tokenId => uint256 allTokensIndex) allTokensIndex; +}`} + + +--- +### ERC721Storage + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### burn + +Burns (destroys) a token, removing it from enumeration tracking. + + +{`function burn(uint256 _tokenId) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC721EnumerableBurnFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + + +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error ERC721NonexistentToken(uint256 _tokenId); + +
+
+ + +
+ Signature: + +error ERC721InsufficientApproval(address _operator, uint256 _tokenId); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `ERC721EnumerableBurnFacet` is properly registered with the diamond proxy. +- Initialize the diamond's storage for ERC-721 before adding this facet. +- Verify that the `burn` function's access control is configured appropriately for your diamond's security model. + + +## Security Considerations + + +The `burn` function is an external function and should have appropriate access controls implemented at the diamond level to prevent unauthorized token destruction. The function removes a token from enumeration tracking, which could affect other facets relying on token order or count. Errors `ERC721NonexistentToken` and `ERC721InsufficientApproval` are emitted if the token does not exist or if approval conditions are not met, respectively. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnMod.mdx b/website/docs/library/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnMod.mdx new file mode 100644 index 00000000..5475f313 --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnMod.mdx @@ -0,0 +1,239 @@ +--- +sidebar_position: 210 +title: "ERC-721 Enumerable Burn Module" +description: "Destroys ERC-721 tokens and removes them from enumeration" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Destroys ERC-721 tokens and removes them from enumeration + + + +- Exposes an `internal` function for burning tokens. +- Integrates with diamond storage to remove tokens from enumeration. +- Emits a `Transfer` event upon successful burning. +- Does not perform approval checks; relies on the calling facet for this. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides an internal function to burn ERC-721 tokens, removing them from enumeration tracking. Facets can integrate this module to handle token destruction directly within the diamond's shared storage. This ensures that token removal is consistent across all facets interacting with the ERC-721 state. + +## Storage + +### ERC721EnumerableStorage + +storage-location: erc8042:erc721.enumerable + + +{`struct ERC721EnumerableStorage { + mapping(address owner => mapping(uint256 index => uint256 tokenId)) ownerTokens; + mapping(uint256 tokenId => uint256 ownerTokensIndex) ownerTokensIndex; + uint256[] allTokens; + mapping(uint256 tokenId => uint256 allTokensIndex) allTokensIndex; +}`} + + +--- +### ERC721Storage + +storage-location: erc8042:erc721 + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### burn + +Burns (destroys) a token, removing it from enumeration tracking. This module does not check for approval. Use the facet for approval-checked burns. + + +{`function burn(uint256 _tokenId) ;`} + + +**Parameters:** + + + +--- +### getERC721Storage + +Returns a pointer to the ERC-721 storage struct. Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + + +{`function getERC721Storage() pure returns (ERC721Storage storage s);`} + + +**Returns:** + + + +--- +### getStorage + +Returns the storage struct used by this facet. + + +{`function getStorage() pure returns (ERC721EnumerableStorage storage s);`} + + +**Returns:** + + + +## Events + + + +
+ Emitted when a token is transferred between addresses. +
+ +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);`} + +
+ +
+
+ +## Errors + + + +
+ Thrown when operating on a non-existent token. +
+ +
+ Signature: + +error ERC721NonexistentToken(uint256 _tokenId); + +
+
+
+ + + + +## Best Practices + + +- Call `burn` only after verifying token ownership and necessary approvals within the calling facet. +- Ensure the calling facet correctly handles the `Transfer` event emitted by this module. +- Verify that the diamond's storage layout is compatible when upgrading facets that interact with this module. + + +## Integration Notes + + +This module interacts with diamond storage at the `STORAGE_POSITION` defined by `keccak256("erc721.enumerable")`. The `burn` function modifies the `ERC721EnumerableStorage` struct, ensuring that burned tokens are no longer tracked in enumeration. Changes to this shared storage are immediately visible to all facets operating within the same diamond. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Enumerable/Burn/_category_.json b/website/docs/library/token/ERC721/Enumerable/Burn/_category_.json new file mode 100644 index 00000000..5a50cfc0 --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Burn/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Burn", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC721/Enumerable/Burn/index" + } +} diff --git a/website/docs/library/token/ERC721/Enumerable/Burn/index.mdx b/website/docs/library/token/ERC721/Enumerable/Burn/index.mdx new file mode 100644 index 00000000..eee0444b --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Burn/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Burn" +description: "Burn components for Compose 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'; + + + Burn components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC721/Enumerable/Data/ERC721EnumerableDataFacet.mdx b/website/docs/library/token/ERC721/Enumerable/Data/ERC721EnumerableDataFacet.mdx new file mode 100644 index 00000000..be717dc3 --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Data/ERC721EnumerableDataFacet.mdx @@ -0,0 +1,267 @@ +--- +sidebar_position: 200 +title: "ERC-721 Enumerable Data Facet" +description: "Enumerable ERC-721 token data retrieval" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Enumerable/Data/ERC721EnumerableDataFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Enumerable ERC-721 token data retrieval + + + +- Exposes external functions for enumerable ERC-721 data retrieval via diamond proxy. +- Reads from shared diamond storage without modifying state. +- Supports querying total supply and specific tokens by index or owner. +- Utilizes inline assembly for efficient storage access. + + +## Overview + +This facet provides external functions for retrieving enumerable ERC-721 token data from a diamond. It accesses shared storage to return total token supply and specific token IDs by index or owner. Developers add this facet to enable querying of token ownership and enumeration within a diamond. + +## Storage + +### ERC721EnumerableStorage + + +{`struct ERC721EnumerableStorage { + mapping(address owner => mapping(uint256 index => uint256 tokenId)) ownerTokens; + mapping(uint256 tokenId => uint256 ownerTokensIndex) ownerTokensIndex; + uint256[] allTokens; +}`} + + +--- +### ERC721Storage + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; +}`} + + +### State Variables + + + +## Functions + +### totalSupply + +Returns the total number of tokens in existence. + + +{`function totalSupply() external view returns (uint256);`} + + +**Returns:** + + + +--- +### tokenOfOwnerByIndex + +Returns a token ID owned by a given address at a specific index. + + +{`function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### tokenByIndex + +Enumerate valid NFTs Throws if `_index` >= `totalSupply()`. + + +{`function tokenByIndex(uint256 _index) external view returns (uint256);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC721EnumerableDataFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Errors + + + + +
+ Signature: + +error ERC721OutOfBoundsIndex(address _owner, uint256 _index); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `ERC721EnumerableDataFacet` is added to the diamond and its selectors are correctly registered. +- When calling `tokenOfOwnerByIndex` or `tokenByIndex`, validate the provided index against `totalSupply()` to prevent `ERC721OutOfBoundsIndex` errors. +- This facet only reads from shared storage; ensure other facets managing token state maintain storage invariants. + + +## Security Considerations + + +The `tokenByIndex` function reverts if `_index` is out of bounds, indicated by the `ERC721OutOfBoundsIndex` error. All functions are `view` and do not pose reentrancy risks. Access control is implicitly handled by the diamond proxy routing calls to this facet. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Enumerable/Data/_category_.json b/website/docs/library/token/ERC721/Enumerable/Data/_category_.json new file mode 100644 index 00000000..46b7be20 --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Data/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Data", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC721/Enumerable/Data/index" + } +} diff --git a/website/docs/library/token/ERC721/Enumerable/Data/index.mdx b/website/docs/library/token/ERC721/Enumerable/Data/index.mdx new file mode 100644 index 00000000..701d0786 --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Data/index.mdx @@ -0,0 +1,22 @@ +--- +title: "Data" +description: "Data components for Compose 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'; + + + Data components for Compose diamonds. + + + + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC721/Enumerable/Mint/ERC721EnumerableMintMod.mdx b/website/docs/library/token/ERC721/Enumerable/Mint/ERC721EnumerableMintMod.mdx new file mode 100644 index 00000000..705b8f84 --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Mint/ERC721EnumerableMintMod.mdx @@ -0,0 +1,248 @@ +--- +sidebar_position: 210 +title: "ERC-721 Enumerable Mint Module" +description: "Mint ERC-721 tokens and manage enumeration lists" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Enumerable/Mint/ERC721EnumerableMintMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Mint ERC-721 tokens and manage enumeration lists + + + +- Internal functions for minting and enumeration management. +- Uses diamond storage pattern (EIP-8042) for shared state. +- Enforces ERC-721 token existence checks. +- Emits `Transfer` event upon successful minting. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions to mint ERC-721 tokens and maintain enumeration lists within a diamond. Facets can import this module to handle token creation and ensure that all minted tokens are tracked, making them accessible to other facets via shared diamond storage. This facilitates consistent state management across the diamond. + +## Storage + +### ERC721EnumerableStorage + +storage-location: erc8042:erc721.enumerable + + +{`struct ERC721EnumerableStorage { + mapping(address owner => mapping(uint256 index => uint256 tokenId)) ownerTokens; + mapping(uint256 tokenId => uint256 ownerTokensIndex) ownerTokensIndex; + uint256[] allTokens; + mapping(uint256 tokenId => uint256 allTokensIndex) allTokensIndex; +}`} + + +--- +### ERC721Storage + +storage-location: erc8042:erc721 + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### getERC721Storage + +Returns a pointer to the ERC-721 storage struct. Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + + +{`function getERC721Storage() pure returns (ERC721Storage storage s);`} + + +**Returns:** + + + +--- +### getStorage + +Returns the storage struct used by this facet. + + +{`function getStorage() pure returns (ERC721EnumerableStorage storage s);`} + + +**Returns:** + + + +--- +### mint + +Mints a new ERC-721 token to the specified address, adding it to enumeration lists. Reverts if the receiver address is zero or if the token already exists. + + +{`function mint(address _to, uint256 _tokenId) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when a token is transferred between addresses. +
+ +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);`} + +
+ +
+
+ +## Errors + + + +
+ Thrown when the receiver address is invalid. +
+ +
+ Signature: + +error ERC721InvalidReceiver(address _receiver); + +
+
+ +
+ Thrown when the sender address is invalid. +
+ +
+ Signature: + +error ERC721InvalidSender(address _sender); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `to` address is not the zero address before calling `mint`. +- Verify that the `tokenId` does not already exist in storage to prevent re-minting. +- Call `mint` only after confirming necessary access controls are met by the calling facet. + + +## Integration Notes + + +This module interacts with diamond storage at the `STORAGE_POSITION` defined by `keccak256(\"erc721.enumerable\")`. The `mint` function reads from and writes to the `ERC721EnumerableStorage` struct, ensuring that newly minted tokens are added to internal enumeration lists. Changes to this storage are immediately visible to all facets operating on the same diamond storage layout. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Enumerable/Mint/_category_.json b/website/docs/library/token/ERC721/Enumerable/Mint/_category_.json new file mode 100644 index 00000000..cc5eab2e --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Mint/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Mint", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC721/Enumerable/Mint/index" + } +} diff --git a/website/docs/library/token/ERC721/Enumerable/Mint/index.mdx b/website/docs/library/token/ERC721/Enumerable/Mint/index.mdx new file mode 100644 index 00000000..ae9dbf04 --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Mint/index.mdx @@ -0,0 +1,22 @@ +--- +title: "Mint" +description: "Mint components for Compose 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'; + + + Mint components for Compose diamonds. + + + + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferFacet.mdx b/website/docs/library/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferFacet.mdx new file mode 100644 index 00000000..20f53861 --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferFacet.mdx @@ -0,0 +1,313 @@ +--- +sidebar_position: 200 +title: "ERC-721 Enumerable Transfer Facet" +description: "ERC-721 token transfers within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +ERC-721 token transfers within a diamond + + + +- Exposes external functions for diamond routing of ERC-721 transfers. +- Accesses shared ERC-721 state via diamond storage. +- Supports standard ERC-721 transfer and safe transfer operations. +- Includes a mechanism (`exportSelectors`) for diamond selector discovery. + + +## Overview + +This facet implements ERC-721 token transfers as external functions in a diamond. It routes calls through the diamond proxy and accesses shared storage via `ERC721Storage`. Developers add this facet to expose token transfer functionality while maintaining upgradeability. + +## Storage + +### ERC721EnumerableStorage + + +{`struct ERC721EnumerableStorage { + mapping(address owner => mapping(uint256 index => uint256 tokenId)) ownerTokens; + mapping(uint256 tokenId => uint256 ownerTokensIndex) ownerTokensIndex; + uint256[] allTokens; + mapping(uint256 tokenId => uint256 allTokensIndex) allTokensIndex; +}`} + + +--- +### ERC721Storage + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### transferFrom + +Transfers a token from one address to another. + + +{`function transferFrom(address _from, address _to, uint256 _tokenId) external;`} + + +**Parameters:** + + + +--- +### safeTransferFrom + +Safely transfers a token, checking for receiver contract compatibility. + + +{`function safeTransferFrom(address _from, address _to, uint256 _tokenId) external;`} + + +**Parameters:** + + + +--- +### safeTransferFrom + +Safely transfers a token with additional data. + + +{`function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata _data) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC721EnumerableTransferFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + + +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error ERC721NonexistentToken(uint256 _tokenId); + +
+
+ + +
+ Signature: + +error ERC721IncorrectOwner(address _sender, uint256 _tokenId, address _owner); + +
+
+ + +
+ Signature: + +error ERC721InvalidReceiver(address _receiver); + +
+
+ + +
+ Signature: + +error ERC721InsufficientApproval(address _operator, uint256 _tokenId); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `ERC721Storage` struct is properly initialized and accessible by this facet. +- Verify that other facets do not interfere with the token ownership state managed by this facet. +- Call `transferFrom`, `safeTransferFrom` functions through the diamond proxy address. + + +## Security Considerations + + +The `transferFrom` and `safeTransferFrom` functions handle token ownership transfers. Reentrancy is mitigated by the checks-effects-interactions pattern. Input validation is crucial; ensure token IDs are valid and ownership rules are respected. Errors `ERC721NonexistentToken`, `ERC721IncorrectOwner`, `ERC721InvalidReceiver`, and `ERC721InsufficientApproval` are used to revert on invalid operations. The `safeTransferFrom` variations include checks for receiver contract compatibility. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferMod.mdx b/website/docs/library/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferMod.mdx new file mode 100644 index 00000000..32b66e8a --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferMod.mdx @@ -0,0 +1,309 @@ +--- +sidebar_position: 210 +title: "ERC-721 Enumerable Transfer Module" +description: "Internal ERC721 token transfer logic" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Internal ERC721 token transfer logic + + + +- Internal functions for token transfers, designed for facet composition. +- Utilizes diamond storage for token ownership tracking. +- Includes `safeTransferFrom` for compatibility checks with ERC721Receiver interfaces. +- No external dependencies, ensuring composability within a diamond. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for managing ERC721 token transfers within a diamond. It leverages diamond storage to track token ownership and ensures safe transfers by checking receiver compatibility. Changes made through this module are immediately visible to all facets accessing the shared ERC721 storage. + +## Storage + +### ERC721EnumerableStorage + +storage-location: erc8042:erc721.enumerable + + +{`struct ERC721EnumerableStorage { + mapping(address owner => mapping(uint256 index => uint256 tokenId)) ownerTokens; + mapping(uint256 tokenId => uint256 ownerTokensIndex) ownerTokensIndex; + uint256[] allTokens; + mapping(uint256 tokenId => uint256 allTokensIndex) allTokensIndex; +}`} + + +--- +### ERC721Storage + +storage-location: erc8042:erc721 + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### getERC721Storage + +Returns a pointer to the ERC-721 storage struct. Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + + +{`function getERC721Storage() pure returns (ERC721Storage storage s);`} + + +**Returns:** + + + +--- +### getStorage + +Returns the storage struct used by this facet. + + +{`function getStorage() pure returns (ERC721EnumerableStorage storage s);`} + + +**Returns:** + + + +--- +### function safeTransferFrom + +Safely transfers a token, checking for receiver contract compatibility. Safely transfers a token with additional data. + + +{`function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory _data) ;`} + + +**Parameters:** + + + +--- +### transferFrom + +Internal function to transfer ownership of a token ID. + + +{`function transferFrom(address _from, address _to, uint256 _tokenId) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when a token is transferred between addresses. +
+ +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);`} + +
+ +
+
+ +## Errors + + + +
+ Thrown when the provided owner does not match the actual owner of the token. +
+ +
+ Signature: + +error ERC721IncorrectOwner(address _sender, uint256 _tokenId, address _owner); + +
+
+ +
+ Thrown when the receiver address is invalid. +
+ +
+ Signature: + +error ERC721InvalidReceiver(address _receiver); + +
+
+ +
+ Thrown when operating on a non-existent token. +
+ +
+ Signature: + +error ERC721NonexistentToken(uint256 _tokenId); + +
+
+
+ + + + +## Best Practices + + +- Ensure caller owns the token and the token exists before calling `transferFrom` or `safeTransferFrom`. +- Verify receiver contract compatibility before calling `safeTransferFrom` to prevent unexpected reverts. +- Handle custom errors like `ERC721IncorrectOwner`, `ERC721InvalidReceiver`, and `ERC721NonexistentToken` in calling facets. + + +## Integration Notes + + +This module interacts with diamond storage via the `STORAGE_POSITION` slot, identified by `keccak256(\"erc721.enumerable\")`. It reads and writes to the `ERC721EnumerableStorage` struct. All state changes made through `transferFrom` and `safeTransferFrom` are immediately reflected in this shared storage, making them visible to any other facet that accesses the same storage slot. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Enumerable/Transfer/_category_.json b/website/docs/library/token/ERC721/Enumerable/Transfer/_category_.json new file mode 100644 index 00000000..c867aef4 --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Transfer/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Transfer", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC721/Enumerable/Transfer/index" + } +} diff --git a/website/docs/library/token/ERC721/Enumerable/Transfer/index.mdx b/website/docs/library/token/ERC721/Enumerable/Transfer/index.mdx new file mode 100644 index 00000000..47ccb624 --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/Transfer/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Transfer" +description: "Transfer components for Compose 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'; + + + Transfer components for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC721/Enumerable/_category_.json b/website/docs/library/token/ERC721/Enumerable/_category_.json new file mode 100644 index 00000000..e7d5143b --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Enumerable", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC721/Enumerable/index" + } +} diff --git a/website/docs/library/token/ERC721/Enumerable/index.mdx b/website/docs/library/token/ERC721/Enumerable/index.mdx new file mode 100644 index 00000000..9ed0ef29 --- /dev/null +++ b/website/docs/library/token/ERC721/Enumerable/index.mdx @@ -0,0 +1,43 @@ +--- +title: "Enumerable" +description: "Enumerable extension for ERC-721 tokens." +--- + +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'; + + + Enumerable extension for ERC-721 tokens. + + + + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC721/Metadata/ERC721MetadataFacet.mdx b/website/docs/library/token/ERC721/Metadata/ERC721MetadataFacet.mdx new file mode 100644 index 00000000..70fa8783 --- /dev/null +++ b/website/docs/library/token/ERC721/Metadata/ERC721MetadataFacet.mdx @@ -0,0 +1,264 @@ +--- +sidebar_position: 200 +title: "ERC-721 Metadata Facet" +description: "ERC-721 token metadata retrieval for diamonds" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Metadata/ERC721MetadataFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +ERC-721 token metadata retrieval for diamonds + + + +- Exposes external view functions for metadata retrieval. +- Accesses shared diamond storage for name, symbol, and base URI. +- Functions are routed through the diamond proxy. +- Compatible with ERC-2535 diamond standard. + + +## Overview + +This facet provides external view functions for retrieving ERC-721 token metadata, such as name, symbol, and token URIs, within a Compose diamond. It accesses shared diamond storage to ensure consistency across facets. Developers integrate this facet to expose token metadata directly through the diamond proxy. + +## Storage + +### ERC721MetadataStorage + + +{`struct ERC721MetadataStorage { + string name; + string symbol; + string baseURI; +}`} + + +--- +### ERC721Storage + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### name + +Returns the token collection name. + + +{`function name() external view returns (string memory);`} + + +**Returns:** + + + +--- +### symbol + +Returns the token collection symbol. + + +{`function symbol() external view returns (string memory);`} + + +**Returns:** + + + +--- +### tokenURI + +Provide the metadata URI for a given token ID. + + +{`function tokenURI(uint256 _tokenId) external view returns (string memory);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC721MetadataFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Errors + + + + +
+ Signature: + +error ERC721NonexistentToken(uint256 _tokenId); + +
+
+ + +
+ Signature: + +error ERC721InvalidOwner(address _owner); + +
+
+
+ + + + +## Best Practices + + +- Ensure the `ERC721MetadataFacet` is added to the diamond with the correct selectors. +- Call `name()`, `symbol()`, and `tokenURI()` through the diamond proxy address. +- Verify storage slot compatibility if upgrading or adding facets that interact with ERC-721 storage. + + +## Security Considerations + + +The `tokenURI` function reverts with `ERC721NonexistentToken` if the provided `_tokenId` does not exist within the diamond's state. Input validation for `_tokenId` is crucial. Access to metadata is read-only and does not pose reentrancy risks. Follow standard Solidity security practices for diamond interactions. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Metadata/ERC721MetadataMod.mdx b/website/docs/library/token/ERC721/Metadata/ERC721MetadataMod.mdx new file mode 100644 index 00000000..db654cc2 --- /dev/null +++ b/website/docs/library/token/ERC721/Metadata/ERC721MetadataMod.mdx @@ -0,0 +1,224 @@ +--- +sidebar_position: 210 +title: "ERC-721 Metadata Module" +description: "Manages ERC721 metadata within a diamond" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Metadata/ERC721MetadataMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages ERC721 metadata within a diamond + + + +- Provides internal functions for ERC721 metadata management. +- Utilizes diamond storage (EIP-8042) for shared state. +- No external dependencies, promoting composability. +- Functions are designed for internal use within custom facets. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for managing ERC721 metadata. Facets can import this module to access and modify the name, symbol, and base URI using shared diamond storage. Changes made through this module are immediately visible to all facets interacting with the same storage slot. + +## Storage + +### ERC721MetadataStorage + +storage-location: erc8042:erc721.metadata + + +{`struct ERC721MetadataStorage { + string name; + string symbol; + string baseURI; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns a pointer to the ERC-721 storage struct. Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + + +{`function getStorage() pure returns (ERC721MetadataStorage storage s);`} + + +**Returns:** + + + +--- +### setMetadata + + +{`function setMetadata(string memory _name, string memory _symbol, string memory _baseURI) ;`} + + +**Parameters:** + + + +## Errors + + + +
+ Error indicating the queried owner address is invalid (zero address). +
+ +
+ Signature: + +error ERC721InvalidOwner(address _owner); + +
+
+ +
+ Error indicating that the queried token does not exist. +
+ +
+ Signature: + +error ERC721NonexistentToken(uint256 _tokenId); + +
+
+
+ + + + +## Best Practices + + +- Ensure the ERC721MetadataMod is correctly initialized and accessible via diamond storage. +- Call `getStorage()` to read the current metadata values before attempting to set new ones. +- Be aware that changes to metadata are immediately visible to all facets using the same storage slot. + + +## Integration Notes + + +This module interacts with diamond storage at the slot identified by `STORAGE_POSITION`, which is defined as `keccak256(\"erc721.metadata\")`. The `getStorage()` function uses inline assembly to access this specific storage slot, returning a pointer to the `ERC721MetadataStorage` struct. This struct contains fields for `name`, `symbol`, and `baseURI`. Any modifications made to this storage slot by any facet through this module are immediately reflected and accessible to all other facets interacting with the same diamond storage pattern. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Metadata/_category_.json b/website/docs/library/token/ERC721/Metadata/_category_.json new file mode 100644 index 00000000..264ed3f1 --- /dev/null +++ b/website/docs/library/token/ERC721/Metadata/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Metadata", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC721/Metadata/index" + } +} diff --git a/website/docs/library/token/ERC721/Metadata/index.mdx b/website/docs/library/token/ERC721/Metadata/index.mdx new file mode 100644 index 00000000..847efa78 --- /dev/null +++ b/website/docs/library/token/ERC721/Metadata/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Metadata" +description: "Metadata extension for ERC-721 tokens." +--- + +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'; + + + Metadata extension for ERC-721 tokens. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC721/Mint/ERC721MintMod.mdx b/website/docs/library/token/ERC721/Mint/ERC721MintMod.mdx new file mode 100644 index 00000000..45519934 --- /dev/null +++ b/website/docs/library/token/ERC721/Mint/ERC721MintMod.mdx @@ -0,0 +1,230 @@ +--- +sidebar_position: 210 +title: "ERC-721 Mint Module" +description: "Mint ERC-721 tokens using diamond storage" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Mint/ERC721MintMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Mint ERC-721 tokens using diamond storage + + + +- Exposes `internal` `mint` function for facet integration. +- Utilizes diamond storage pattern (EIP-8042) for state management. +- Emits `Transfer` event upon successful minting. +- No external dependencies, ensuring minimal on-chain footprint. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions to mint new ERC-721 tokens. Facets can import this module to manage token creation, leveraging shared diamond storage for consistency. Changes made via this module are immediately visible to all facets sharing the same storage. + +## Storage + +### ERC721Storage + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the ERC-721 storage struct from its predefined slot. Uses inline assembly to access diamond storage location. + + +{`function getStorage() pure returns (ERC721Storage storage s);`} + + +**Returns:** + + + +--- +### mint + +Mints a new ERC-721 token to the specified address. Reverts if the receiver address is zero or if the token already exists. + + +{`function mint(address _to, uint256 _tokenId) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when ownership of a token changes, including minting and burning. +
+ +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the receiver address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC721InvalidReceiver(address _receiver); + +
+
+ +
+ Thrown when the sender address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC721InvalidSender(address _sender); + +
+
+
+ + + + +## Best Practices + + +- Ensure the calling facet enforces access control before invoking `mint`. +- Verify the `to` address is not the zero address and the `tokenId` does not already exist before calling `mint`. +- Handle `ERC721InvalidReceiver` and other potential errors gracefully. + + +## Integration Notes + + +This module accesses shared diamond storage at the position identified by `keccak256(\"erc721\")` to manage ERC-721 token state. The `mint` function reads and writes to this shared storage. Any facet that also accesses this storage slot will observe changes made by this module immediately. The `ERC721Storage` struct, though empty in this definition, reserves the storage slot for future ERC-721 specific state. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Mint/_category_.json b/website/docs/library/token/ERC721/Mint/_category_.json new file mode 100644 index 00000000..a9dd634c --- /dev/null +++ b/website/docs/library/token/ERC721/Mint/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Mint", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC721/Mint/index" + } +} diff --git a/website/docs/library/token/ERC721/Mint/index.mdx b/website/docs/library/token/ERC721/Mint/index.mdx new file mode 100644 index 00000000..ba73f8f4 --- /dev/null +++ b/website/docs/library/token/ERC721/Mint/index.mdx @@ -0,0 +1,22 @@ +--- +title: "Mint" +description: "Mint extension for ERC-721 tokens." +--- + +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'; + + + Mint extension for ERC-721 tokens. + + + + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC721/Transfer/ERC721TransferFacet.mdx b/website/docs/library/token/ERC721/Transfer/ERC721TransferFacet.mdx new file mode 100644 index 00000000..c79974b1 --- /dev/null +++ b/website/docs/library/token/ERC721/Transfer/ERC721TransferFacet.mdx @@ -0,0 +1,307 @@ +--- +sidebar_position: 200 +title: "ERC-721 Transfer Facet" +description: "ERC-721 token transfers within a diamond" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Transfer/ERC721TransferFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +ERC-721 token transfers within a diamond + + + +- Exposes external ERC-721 transfer functions for diamond routing. +- Internally uses `internalTransferFrom` for core transfer logic and validation. +- Accesses shared diamond storage for ERC-721 state. +- Supports `safeTransferFrom` with and without additional data. + + +## Overview + +This facet provides external functions for ERC-721 token transfers, routed through the diamond proxy. It interacts with shared diamond storage via the `getStorage` internal function. Developers integrate this facet to enable token transfer functionality while preserving diamond's upgradeability. + +## Storage + +### ERC721Storage + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### transferFrom + +Transfers a token from one address to another. + + +{`function transferFrom(address _from, address _to, uint256 _tokenId) external;`} + + +**Parameters:** + + + +--- +### safeTransferFrom + +Safely transfers a token, checking if the receiver can handle ERC-721 tokens. + + +{`function safeTransferFrom(address _from, address _to, uint256 _tokenId) external;`} + + +**Parameters:** + + + +--- +### safeTransferFrom + +Safely transfers a token with additional data. + + +{`function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata _data) external;`} + + +**Parameters:** + + + +--- +### exportSelectors + +Exports the function selectors of the ERC721TransferFacet This function is use as a selector discovery mechanism for diamonds + + +{`function exportSelectors() external pure returns (bytes memory);`} + + +**Returns:** + + + +## Events + + + + +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);`} + +
+ +
+
+ +## Errors + + + + +
+ Signature: + +error ERC721NonexistentToken(uint256 _tokenId); + +
+
+ + +
+ Signature: + +error ERC721IncorrectOwner(address _sender, uint256 _tokenId, address _owner); + +
+
+ + +
+ Signature: + +error ERC721InvalidReceiver(address _receiver); + +
+
+ + +
+ Signature: + +error ERC721InsufficientApproval(address _operator, uint256 _tokenId); + +
+
+
+ + + + +## Best Practices + + +- Initialize ERC721 storage correctly before adding this facet. +- Ensure the caller has the necessary ownership or approval before invoking transfer functions. +- Verify that receiver contracts implement the ERC721TokenReceiver interface when using `safeTransferFrom`. + + +## Security Considerations + + +All state-changing functions (`transferFrom`, `safeTransferFrom`) perform checks before state modifications, adhering to the checks-effects-interactions pattern. Reentrancy is mitigated by the diamond's proxy nature and the internal checks. Input validation is performed to prevent transfers of non-existent tokens, incorrect ownership, or to invalid receivers. Errors `ERC721NonexistentToken`, `ERC721IncorrectOwner`, `ERC721InvalidReceiver`, and `ERC721InsufficientApproval` are used to signal failures. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Transfer/ERC721TransferMod.mdx b/website/docs/library/token/ERC721/Transfer/ERC721TransferMod.mdx new file mode 100644 index 00000000..d6b712b2 --- /dev/null +++ b/website/docs/library/token/ERC721/Transfer/ERC721TransferMod.mdx @@ -0,0 +1,266 @@ +--- +sidebar_position: 210 +title: "ERC-721 Transfer Module" +description: "Manage ERC-721 token transfers within a diamond" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/ERC721/Transfer/ERC721TransferMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manage ERC-721 token transfers within a diamond + + + +- All functions are `internal` for use within facets. +- Uses the diamond storage pattern (EIP-8042) for state management. +- Emits a `Transfer` event upon successful token transfer. +- Includes specific errors for common ERC-721 transfer failures. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions to manage ERC-721 token transfers using diamond storage. Facets can import this module to perform token ownership changes, ensuring consistency across the diamond. State modifications are immediately visible to all facets accessing the shared ERC-721 storage. + +## Storage + +### ERC721Storage + +Storage layout for ERC-721 token management. Defines ownership, balances, approvals, and operator mappings per ERC-721 standard. storage-location: erc8042:erc721 + + +{`struct ERC721Storage { + mapping(uint256 tokenId => address owner) ownerOf; + mapping(address owner => uint256 balance) balanceOf; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(uint256 tokenId => address approved) approved; +}`} + + +### State Variables + + + +## Functions + +### getStorage + +Returns the ERC-721 storage struct from its predefined slot. Uses inline assembly to access diamond storage location. + + +{`function getStorage() pure returns (ERC721Storage storage s);`} + + +**Returns:** + + + +--- +### transferFrom + +Transfers ownership of a token ID from one address to another. Validates ownership, approval, and receiver address before updating state. + + +{`function transferFrom(address _from, address _to, uint256 _tokenId) ;`} + + +**Parameters:** + + + +## Events + + + +
+ Emitted when ownership of a token changes, including minting and burning. +
+ +
+ Signature: + +{`event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);`} + +
+ +
+ Parameters: + +
+
+
+ +## Errors + + + +
+ Thrown when the sender is not the owner of the token. +
+ +
+ Signature: + +error ERC721IncorrectOwner(address _sender, uint256 _tokenId, address _owner); + +
+
+ +
+ Thrown when the receiver address is invalid (e.g., zero address). +
+ +
+ Signature: + +error ERC721InvalidReceiver(address _receiver); + +
+
+ +
+ Thrown when attempting to interact with a non-existent token. +
+ +
+ Signature: + +error ERC721NonexistentToken(uint256 _tokenId); + +
+
+
+ + + + +## Best Practices + + +- Ensure caller is the owner or has been approved for the token before calling `transferFrom`. +- Handle `ERC721IncorrectOwner`, `ERC721InvalidReceiver`, and `ERC721NonexistentToken` errors when interacting with `transferFrom`. +- Verify that the `ERC721Storage` struct definition remains compatible with the diamond's storage layout upon upgrades. + + +## Integration Notes + + +This module reads and writes to diamond storage at the slot designated by `keccak256(\"erc721\")`. The `ERC721Storage` struct defines the layout for token ownership and related metadata. Changes made via the `transferFrom` function are immediately reflected in this shared storage, making them visible to all facets that access the same storage position. The `getStorage` function provides a read-only view of this state using inline assembly. + + +
+ +
+ + diff --git a/website/docs/library/token/ERC721/Transfer/_category_.json b/website/docs/library/token/ERC721/Transfer/_category_.json new file mode 100644 index 00000000..6f21bff3 --- /dev/null +++ b/website/docs/library/token/ERC721/Transfer/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Transfer", + "position": 99, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC721/Transfer/index" + } +} diff --git a/website/docs/library/token/ERC721/Transfer/index.mdx b/website/docs/library/token/ERC721/Transfer/index.mdx new file mode 100644 index 00000000..e4b0779c --- /dev/null +++ b/website/docs/library/token/ERC721/Transfer/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Transfer" +description: "Transfer extension for ERC-721 tokens." +--- + +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'; + + + Transfer extension for ERC-721 tokens. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/ERC721/_category_.json b/website/docs/library/token/ERC721/_category_.json new file mode 100644 index 00000000..8ee4f288 --- /dev/null +++ b/website/docs/library/token/ERC721/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "ERC-721", + "position": 2, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/ERC721/index" + } +} diff --git a/website/docs/library/token/ERC721/index.mdx b/website/docs/library/token/ERC721/index.mdx new file mode 100644 index 00000000..75d432af --- /dev/null +++ b/website/docs/library/token/ERC721/index.mdx @@ -0,0 +1,64 @@ +--- +title: "ERC-721" +description: "ERC-721 non-fungible token implementations." +--- + +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'; + + + ERC-721 non-fungible token implementations. + + + + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/Royalty/RoyaltyFacet.mdx b/website/docs/library/token/Royalty/RoyaltyFacet.mdx new file mode 100644 index 00000000..f2982326 --- /dev/null +++ b/website/docs/library/token/Royalty/RoyaltyFacet.mdx @@ -0,0 +1,179 @@ +--- +sidebar_position: 1 +title: "Royalty Facet" +description: "Provides ERC-2981 royalty information for tokens" +sidebar_label: "Facet" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/Royalty/RoyaltyFacet.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Provides ERC-2981 royalty information for tokens + + + +- Implements ERC-2981 `royaltyInfo` function for on-chain royalty queries. +- Reads from shared diamond storage for default and token-specific royalty data. +- Operates as an internal facet, accessed via the diamond proxy. +- Self-contained with no external dependencies other than diamond storage access. + + +## Overview + +This facet implements the ERC-2981 royaltyInfo function, enabling a diamond to provide royalty information for tokens. It accesses shared diamond storage to retrieve default and token-specific royalty settings. Developers add this facet to integrate royalty payment logic into their NFT marketplaces or token contracts within a diamond. + +## Storage + +### RoyaltyInfo + + +{`struct RoyaltyInfo { + address receiver; + uint96 royaltyFraction; +}`} + + +--- +### RoyaltyStorage + + +{`struct RoyaltyStorage { + RoyaltyInfo defaultRoyaltyInfo; + mapping(uint256 tokenId => RoyaltyInfo) tokenRoyaltyInfo; +}`} + + +### State Variables + + + +## Functions + +### royaltyInfo + +Returns royalty information for a given token and sale price. Returns token-specific royalty if set, otherwise falls back to default royalty. Royalty amount is calculated as a percentage of the sale price using basis points. Implements the ERC-2981 royaltyInfo function. + + +{`function royaltyInfo(uint256 _tokenId, uint256 _salePrice) + external + view + returns (address receiver, uint256 royaltyAmount);`} + + +**Parameters:** + + + +**Returns:** + + + + + + +## Best Practices + + +- Integrate this facet into your diamond to enable ERC-2981 compliant royalty queries. +- Ensure the `RoyaltyStorage` struct is correctly initialized with default royalty information during diamond deployment. +- Use the `royaltyInfo` function through the diamond proxy to retrieve royalty details for token sales. + + +## Security Considerations + + +The `royaltyInfo` function is a `view` function and does not pose reentrancy risks. Input validation for `_tokenId` and `_salePrice` is handled by the function logic to calculate royalty amounts. Access control is implicit as the function is external and called through the diamond proxy, which routes calls to the appropriate facet. Follow standard Solidity security practices for handling token IDs and sale prices. + + +
+ +
+ + diff --git a/website/docs/library/token/Royalty/RoyaltyMod.mdx b/website/docs/library/token/Royalty/RoyaltyMod.mdx new file mode 100644 index 00000000..24165c48 --- /dev/null +++ b/website/docs/library/token/Royalty/RoyaltyMod.mdx @@ -0,0 +1,346 @@ +--- +sidebar_position: 2 +title: "Royalty Module" +description: "Manages ERC-2981 royalty information for tokens" +sidebar_label: "Module" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/token/Royalty/RoyaltyMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Manages ERC-2981 royalty information for tokens + + + +- Implements logic for ERC-2981 `royaltyInfo` function. +- Uses diamond storage pattern for shared royalty data. +- Provides internal functions for setting, resetting, and deleting royalties. +- Handles both token-specific and default royalty configurations. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions for managing ERC-2981 royalty information within a diamond. It allows facets to set, retrieve, and reset both default and token-specific royalties using shared diamond storage. This ensures consistent royalty handling across all facets interacting with the same token data. + +## Storage + +### RoyaltyInfo + +Structure containing royalty information. **Properties** + + +{`struct RoyaltyInfo { + address receiver; + uint96 royaltyFraction; +}`} + + +--- +### RoyaltyStorage + +storage-location: erc8042:erc2981 + + +{`struct RoyaltyStorage { + RoyaltyInfo defaultRoyaltyInfo; + mapping(uint256 tokenId => RoyaltyInfo) tokenRoyaltyInfo; +}`} + + +### State Variables + + + +## Functions + +### deleteDefaultRoyalty + +Removes default royalty information. After calling this function, royaltyInfo will return (address(0), 0) for tokens without specific royalty. + + +{`function deleteDefaultRoyalty() ;`} + + +--- +### getStorage + +Returns the royalty storage struct from its predefined slot. Uses inline assembly to access diamond storage location. + + +{`function getStorage() pure returns (RoyaltyStorage storage s);`} + + +**Returns:** + + + +--- +### resetTokenRoyalty + +Resets royalty information for a specific token to use the default setting. Clears token-specific royalty storage, causing fallback to default royalty. + + +{`function resetTokenRoyalty(uint256 _tokenId) ;`} + + +**Parameters:** + + + +--- +### royaltyInfo + +Queries royalty information for a given token and sale price. Returns token-specific royalty or falls back to default royalty. Royalty amount is calculated as a percentage of the sale price using basis points. Implements the ERC-2981 royaltyInfo function logic. + + +{`function royaltyInfo(uint256 _tokenId, uint256 _salePrice) view returns (address receiver, uint256 royaltyAmount);`} + + +**Parameters:** + + + +**Returns:** + + + +--- +### setDefaultRoyalty + +Sets the default royalty information that applies to all tokens. Validates receiver and fee, then updates default royalty storage. + + +{`function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) ;`} + + +**Parameters:** + + + +--- +### setTokenRoyalty + +Sets royalty information for a specific token, overriding the default. Validates receiver and fee, then updates token-specific royalty storage. + + +{`function setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) ;`} + + +**Parameters:** + + + +## Errors + + + +
+ Thrown when default royalty fee exceeds 100% (10000 basis points). +
+ +
+ Signature: + +error ERC2981InvalidDefaultRoyalty(uint256 _numerator, uint256 _denominator); + +
+
+ +
+ Thrown when default royalty receiver is the zero address. +
+ +
+ Signature: + +error ERC2981InvalidDefaultRoyaltyReceiver(address _receiver); + +
+
+ +
+ Thrown when token-specific royalty fee exceeds 100% (10000 basis points). +
+ +
+ Signature: + +error ERC2981InvalidTokenRoyalty(uint256 _tokenId, uint256 _numerator, uint256 _denominator); + +
+
+ +
+ Thrown when token-specific royalty receiver is the zero address. +
+ +
+ Signature: + +error ERC2981InvalidTokenRoyaltyReceiver(uint256 _tokenId, address _receiver); + +
+
+
+ + + + +## Best Practices + + +- Ensure receiver addresses are valid and fee numerators are within acceptable bounds before calling `setDefaultRoyalty` or `setTokenRoyalty`. +- Use `royaltyInfo` to retrieve royalty details for a token, falling back to default if token-specific royalties are not set. +- Call `resetTokenRoyalty` to revert a token's royalty settings to the default, ensuring predictable behavior after changes. + + +## Integration Notes + + +This module interacts with diamond storage at the `STORAGE_POSITION` defined by `keccak256("erc2981")`. The `RoyaltyStorage` struct, containing `defaultRoyaltyInfo`, is accessed via inline assembly. Functions like `setDefaultRoyalty` and `setTokenRoyalty` modify this shared storage. The `royaltyInfo` function reads from this storage to determine applicable royalties, first checking for token-specific settings and then falling back to default settings. Changes made by this module are immediately visible to any facet that reads from the same storage position. + + +
+ +
+ + diff --git a/website/docs/library/token/Royalty/_category_.json b/website/docs/library/token/Royalty/_category_.json new file mode 100644 index 00000000..cb6b460f --- /dev/null +++ b/website/docs/library/token/Royalty/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Royalty", + "position": 5, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/Royalty/index" + } +} diff --git a/website/docs/library/token/Royalty/index.mdx b/website/docs/library/token/Royalty/index.mdx new file mode 100644 index 00000000..92b4ff63 --- /dev/null +++ b/website/docs/library/token/Royalty/index.mdx @@ -0,0 +1,29 @@ +--- +title: "Royalty" +description: "ERC-2981 royalty standard implementations." +--- + +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'; + + + ERC-2981 royalty standard implementations. + + + + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/token/_category_.json b/website/docs/library/token/_category_.json new file mode 100644 index 00000000..3f26c2ce --- /dev/null +++ b/website/docs/library/token/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Token Standards", + "position": 3, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/token/index" + } +} diff --git a/website/docs/library/token/index.mdx b/website/docs/library/token/index.mdx new file mode 100644 index 00000000..e18f1fe8 --- /dev/null +++ b/website/docs/library/token/index.mdx @@ -0,0 +1,50 @@ +--- +title: "Token Standards" +description: "Token standard implementations for Compose 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'; + + + Token standard implementations for Compose diamonds. + + + + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + } + size="medium" + /> + diff --git a/website/docs/library/utils/NonReentrancyMod.mdx b/website/docs/library/utils/NonReentrancyMod.mdx new file mode 100644 index 00000000..b48f1714 --- /dev/null +++ b/website/docs/library/utils/NonReentrancyMod.mdx @@ -0,0 +1,145 @@ +--- +sidebar_position: 1 +title: "Non Reentrancy Module" +description: "Prevent reentrancy in smart contract functions" +gitSource: "https://github.com/Perfect-Abstractions/Compose/tree/main/src/libraries/NonReentrancyMod.sol" +--- + +import DocSubtitle from '@site/src/components/docs/DocSubtitle'; +import Badge from '@site/src/components/ui/Badge'; +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'; +import GradientText from '@site/src/components/ui/GradientText'; +import GradientButton from '@site/src/components/ui/GradientButton'; + + +Prevent reentrancy in smart contract functions + + + +- Internal functions `enter` and `exit` for reentrancy protection. +- Emits a `Reentrancy` error if reentrancy is detected. +- No external dependencies, designed for direct integration into facets. +- Compatible with ERC-2535 diamonds by managing the guard variable within diamond storage. + + + +This module provides internal functions for use in your custom facets. Import it to access shared logic and storage. + + +## Overview + +This module provides internal functions to prevent reentrancy attacks by managing entry and exit points. Facets can import and use these functions to ensure critical operations are not called recursively within the same execution context. This enhances the security and reliability of your diamond. + +## Storage + +### State Variables + + + +## Functions + +### enter + +How to use as a library in user facets How to use as a modifier in user facets This unlocks the entry into a function + + +{`function enter() ;`} + + +--- +### exit + +This locks the entry into a function + + +{`function exit() ;`} + + +## Errors + + + +
+ Function selector - 0x43a0d067 +
+ +
+ Signature: + +error Reentrancy(); + +
+
+
+ + + + +## Best Practices + + +- Call `NonReentrancyMod.enter()` at the beginning of state-changing functions to prevent reentrancy. +- Call `NonReentrancyMod.exit()` at the end of state-changing functions to unlock reentrancy. +- Ensure the reentrancy guard variable is properly initialized and managed across facet upgrades. + + +## Integration Notes + + +This module does not directly interact with diamond storage. It relies on a simple storage variable (e.g., `uint256`) within the calling facet to track the reentrancy state. The `enter` function checks if the guard is already set, reverting with `Reentrancy` if it is. Upon successful execution, `enter` sets the guard, and `exit` unsets it. This pattern ensures that state changes are atomic and not subject to recursive calls within the same transaction. + + +
+ +
+ + diff --git a/website/docs/library/utils/_category_.json b/website/docs/library/utils/_category_.json new file mode 100644 index 00000000..d9c087be --- /dev/null +++ b/website/docs/library/utils/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Utilities", + "position": 4, + "collapsible": true, + "collapsed": true, + "link": { + "type": "doc", + "id": "library/utils/index" + } +} diff --git a/website/docs/library/utils/index.mdx b/website/docs/library/utils/index.mdx new file mode 100644 index 00000000..4f43f6a0 --- /dev/null +++ b/website/docs/library/utils/index.mdx @@ -0,0 +1,22 @@ +--- +title: "Utilities" +description: "Utility libraries and helpers for diamond development." +--- + +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'; + + + Utility libraries and helpers for diamond development. + + + + } + size="medium" + /> + diff --git a/website/package.json b/website/package.json index 7c24befb..489b28c5 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 d684cc43..fb17ed80 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -19,34 +19,34 @@ const sidebars = { 'intro', { type: 'category', - label: 'Foundations', - collapsed: false, - link: { - type: 'doc', - id: 'foundations/index', - }, + label: 'Getting Started', + collapsed: true, items: [ { type: 'autogenerated', - dirName: 'foundations', + dirName: 'getting-started', }, ], }, { type: 'category', - label: 'Getting Started', - collapsed: true, + label: 'Foundations', + collapsed: false, + link: { + type: 'doc', + id: 'foundations/index', + }, items: [ { type: 'autogenerated', - dirName: 'getting-started', + dirName: 'foundations', }, ], }, { type: 'category', label: 'Design', - collapsed: false, + collapsed: true, link: { type: 'doc', id: 'design/index', @@ -58,19 +58,21 @@ const sidebars = { }, ], }, - /* { type: 'category', - label: 'Facets', + label: 'Library', collapsed: true, + link: { + type: 'doc', + id: 'library/index', + }, items: [ { type: 'autogenerated', - dirName: 'facets', + 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 a8072213..99c083da 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 Component - MoneyKit-inspired documentation card * @@ -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 9b245175..701ca039 100644 --- a/website/src/components/docs/DocCard/styles.module.css +++ b/website/src/components/docs/DocCard/styles.module.css @@ -110,6 +110,10 @@ transition: all 0.3s ease; } +[data-theme='dark'] .docCardArrow { + color: #ffffff; +} + .docCard:hover .docCardArrow { color: var(--ifm-color-primary); transform: translateX(4px); diff --git a/website/src/components/docs/WasThisHelpful/index.js b/website/src/components/docs/WasThisHelpful/index.js index 35f8454b..b01030fa 100644 --- a/website/src/components/docs/WasThisHelpful/index.js +++ b/website/src/components/docs/WasThisHelpful/index.js @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import clsx from 'clsx'; import Icon from '../../ui/Icon'; import styles from './styles.module.css'; +import { useDocumentationFeedback } from '../../../hooks/useDocumentationFeedback'; /** * WasThisHelpful Component - Feedback widget for documentation pages @@ -13,6 +14,7 @@ export default function WasThisHelpful({ pageId, onSubmit }) { + const { submitFeedback } = useDocumentationFeedback(); const [feedback, setFeedback] = useState(null); const [comment, setComment] = useState(''); const [submitted, setSubmitted] = useState(false); @@ -22,12 +24,11 @@ export default function WasThisHelpful({ }; const handleSubmit = () => { + submitFeedback(pageId, feedback, comment.trim() || null); if (onSubmit) { onSubmit({ pageId, feedback, comment }); - } else { - // Default behavior - could log to analytics - console.log('Feedback submitted:', { pageId, feedback, comment }); } + setSubmitted(true); }; @@ -53,7 +54,14 @@ export default function WasThisHelpful({ onClick={() => handleFeedback('yes')} aria-label="Yes, this was helpful" > - + Yes
diff --git a/website/src/components/docs/WasThisHelpful/styles.module.css b/website/src/components/docs/WasThisHelpful/styles.module.css index 30f78901..caaae8b5 100644 --- a/website/src/components/docs/WasThisHelpful/styles.module.css +++ b/website/src/components/docs/WasThisHelpful/styles.module.css @@ -75,6 +75,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/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 7c542c4f..2633c039 100644 --- a/website/static/icons/thumbs-down.svg +++ b/website/static/icons/thumbs-down.svg @@ -1,11 +1,9 @@ - + - - - - + + \ No newline at end of file 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 @@ + + + + diff --git a/website/static/icons/thumbs-up.svg b/website/static/icons/thumbs-up.svg index 86b9a56b..e3a3ac9d 100644 --- a/website/static/icons/thumbs-up.svg +++ b/website/static/icons/thumbs-up.svg @@ -1,11 +1,9 @@ - + - - - - + + \ No newline at end of file