Skip to content

Latest commit

 

History

History
199 lines (169 loc) · 12.5 KB

File metadata and controls

199 lines (169 loc) · 12.5 KB
  • Project/Directory
    • Interfaces
      1. Interfaces should be named with the prefix I and the name should be written in PascalCasing i.e. IAccount .
      2. Interfaces should be defined in an interfaces/ subfolder within the module directory (e.g., src/branch/interfaces/IActivePool.cairo for the ActivePool contract in src/branch/).
      3. The generic state parameter should be named TState.
      4. Functions in interfaces should be grouped by category: write functions first, then view functions.
      5. Within each category (write/view), functions should be ordered alphabetically.
      6. Public structs that appear in interface signatures should be defined in the interface file, not in the contract file.
    • Contracts
      • Declaration Order and Section Organization
        1. Each major section in a contract should be visually separated using comment blocks with forward slashes (e.g., ////////////////////////////////////////////////////////////////)
        2. Section headers should be centered within the delimiter blocks with consistent spacing
        3. The order of sections and their standard names:
          • Imports (no delimiter needed at top of file)
          • COMPONENT DECLARATIONS - For component! macro declarations
          • COMPONENT IMPORTS - For component implementation imports and exposures
          • CONSTANTS - For constant declarations
          • STORAGE - For storage struct definition
          • STRUCTS - For custom struct definitions
          • ENUMS - For custom enum definitions (excluding the main Event enum)
          • EVENTS - For event enum and event struct definitions
          • CONSTRUCTOR - For the contract constructor (if present)
          • ADDITIONAL IMPLEMENTATIONS - For supplementary trait implementations (e.g., SNIP12Metadata, access control traits, etc.)
          • PUBLIC FUNCTIONS or INTERFACE FUNCTIONS - For the main interface implementation (write functions first, then view functions, in the same order as declared in the interface file)
          • INTERNAL FUNCTIONS - For internal implementation block with #[generate_trait] (sorted by logical order, then alphabetical order)
          • UTILITY FUNCTIONS - For standalone functions that don't depend on contract state (placed at bottom, outside any impl block)
        4. Within function implementation blocks (both interface and internal), use sub-sections:
          • WRITE FUNCTIONS - For functions that modify contract state
          • READ FUNCTIONS - For read-only functions that don't modify state (view/getter functions)
        5. No internal function should be left free; they should all be in an InternalImpl block.
        6. Example structure:
          ////////////////////////////////////////////////////////////////
          //                 COMPONENT DECLARATIONS                     //
          ////////////////////////////////////////////////////////////////
          
          component!(...)
          
          ////////////////////////////////////////////////////////////////
          //                    COMPONENT IMPORTS                       //
          ////////////////////////////////////////////////////////////////
          
          #[abi(embed_v0)]
          impl ComponentImpl = ...
          
          ////////////////////////////////////////////////////////////////
          //                       CONSTANTS                            //
          ////////////////////////////////////////////////////////////////
          
          const MY_CONSTANT: u256 = 100;
          
          ////////////////////////////////////////////////////////////////
          //                        STORAGE                             //
          ////////////////////////////////////////////////////////////////
          
          #[storage]
          struct Storage { ... }
          
          ////////////////////////////////////////////////////////////////
          //                        STRUCTS                             //
          ////////////////////////////////////////////////////////////////
          
          #[derive(Drop, Serde)]
          struct MyCustomStruct { ... }
          
          ////////////////////////////////////////////////////////////////
          //                         ENUMS                              //
          ////////////////////////////////////////////////////////////////
          
          #[derive(Drop, Serde)]
          enum MyCustomEnum { ... }
          
          ////////////////////////////////////////////////////////////////
          //                        EVENTS                              //
          ////////////////////////////////////////////////////////////////
          
          #[event]
          #[derive(Drop, starknet::Event)]
          enum Event {
              MyEvent: MyEvent,
          }
          
          #[derive(Drop, starknet::Event)]
          struct MyEvent { ... }
          
          ////////////////////////////////////////////////////////////////
          //                      CONSTRUCTOR                           //
          ////////////////////////////////////////////////////////////////
          
          #[constructor]
          fn constructor(ref self: ContractState, owner: ContractAddress) {
              self.owner.write(owner);
          }
          
          ////////////////////////////////////////////////////////////////
          //                ADDITIONAL IMPLEMENTATIONS                  //
          ////////////////////////////////////////////////////////////////
          
          #[abi(embed_v0)]
          impl SNIP12MetadataImpl of ISNIP12Metadata<ContractState> {
              fn name(self: @ContractState) -> felt252 { ... }
              fn version(self: @ContractState) -> felt252 { ... }
          }
          
          ////////////////////////////////////////////////////////////////
          //                   PUBLIC FUNCTIONS                         //
          ////////////////////////////////////////////////////////////////
          
          #[abi(embed_v0)]
          impl IMyContractImpl of IMyContract<ContractState> {
              ////////////////////////////////////////////////////////////////
              //                     WRITE FUNCTIONS                        //
              ////////////////////////////////////////////////////////////////
              
              fn set_value(ref self: ContractState, value: u256) { ... }
              
              ////////////////////////////////////////////////////////////////
              //                      READ FUNCTIONS                        //
              ////////////////////////////////////////////////////////////////
              
              fn get_value(self: @ContractState) -> u256 { ... }
          }
          
          ////////////////////////////////////////////////////////////////
          //                   INTERNAL FUNCTIONS                       //
          ////////////////////////////////////////////////////////////////
          
          #[generate_trait]
          impl InternalImpl of InternalTrait {
              ////////////////////////////////////////////////////////////////
              //                     WRITE FUNCTIONS                        //
              ////////////////////////////////////////////////////////////////
              
              fn update_internal_state(ref self: ContractState) { ... }
              
              ////////////////////////////////////////////////////////////////
              //                      READ FUNCTIONS                        //
              ////////////////////////////////////////////////////////////////
              
              fn calculate_internal_value(self: @ContractState) -> u256 { ... }
          }
          
          ////////////////////////////////////////////////////////////////
          //                   UTILITY FUNCTIONS                        //
          ////////////////////////////////////////////////////////////////
          
          fn calculate_hash(value: u256) -> felt252 { ... }
      • Imports
        1. Imports should be listed alphabetically (scarb fmt automatically does this).
        2. When bringing multiple elements into scope from one source, favor limiting each use clause to 3 lines total.
        3. Group imported elements in a logical way e.g. don't import a component, event, and function in the same use statement.
      • Constants
        1. Constant variables should be written in SCREAMING_SNAKE_CASE.
      • Storage
        1. Storage variables should be prefixed with the component name.
      • Events
        1. An Events enum should list all component events.
        2. Each component event should be defined as a struct.
        3. Event names should be written in PascalCasing.
        4. Event parameters should be written in snake_case.
        5. The #[key] annotation should be added above the event parameter if that parameter should be indexed.
      • Impl
        1. Implementations should follow the naming pattern of:
          • Remove the I prefix for the impl name i.e. IERC20 ⇒ ERC20.
          • Add the Impl suffix as the embeddable impl name i.e. IERC20 ⇒ ERC20Impl.
        2. If the name of the Trait finishes with Trait, then the impl doesn’t have to end with Impl
        3. Internal implementations should be named as InternalImpl.
      • Functions/Casings
        1. Functions should be written in snake_case.
        2. The only exception to this is when defining dual case dispatchers.
        3. Underscore prefix (_) usage:
          • By default, all functions should be written without underscore prefix
          • Use _ prefix ONLY to disambiguate when there's a naming conflict between:
            • An embeddable trait function and an internal implementation function
            • Example: transfer() in embeddable trait calls _transfer() in InternalImpl
          • The underscore has no visibility meaning - it's purely for disambiguation
        4. Standalone functions that don't depend on contract state:
          • If used only within the current contract: place outside impl blocks as regular functions
          • If used across multiple contracts: place in common file (e.g. utils.cairo)
      • Error Messages
        1. Error messages should always start with a module prefix (e.g., ActivePool errors should start with 'AP: ')
        2. Use consistent short prefixes for each module to stay within the 31 character limit
      • Documentation
        1. Developer-related documentation should appear in the code implementation
        2. General user-facing documentation should appear in the interface definitions
    • Testing
      1. Test functions should follow the naming pattern test_PREFIX_* where PREFIX is a shorthand for the module being tested
        • Example: ActivePool tests should be named test_ap_*
        • This enables running module-specific tests with scarb test test_PREFIX
      2. Use consistent prefixes across all tests for the same module
      3. Organize tests logically: success cases, failure cases, edge cases
    • File Naming
      1. Contract files should be named in PascalCase (e.g., ActivePool.cairo)
      2. All other files should be named in snake_case (e.g., test_helpers.cairo)
    • Code Optimization
      1. Prioritize code readability over gas optimization
      2. Only apply gas optimizations when they provide significant improvements
      3. Document any non-obvious optimizations with comments explaining the trade-off