From 97f8269dd1f8332d109f80974ba53a3839268cd9 Mon Sep 17 00:00:00 2001 From: rohan-tessl Date: Wed, 25 Mar 2026 16:44:53 +0530 Subject: [PATCH 1/2] feat: improve skill scores for dojoengine/book MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hey 👋 @kariy I ran your skills through `tessl skill review` at work and found some targeted improvements. Here's the full before/after: | Skill | Before | After | Change | |-------|--------|-------|--------| | dojo-vrf | 0% | 90% | +90% | | dojo-review | 66% | 100% | +34% | | dojo-system | 66% | 94% | +28% | | dojo-world | 71% | 100% | +29% | | dojo-model | 74% | 86% | +12% | This PR intentionally caps changes to five skills to keep it reviewable. The included GitHub Action workflow (see below) will surface Tessl feedback on future `SKILL.md` changes automatically.
Changes summary **dojo-vrf (0% → 90%)** - Added missing YAML frontmatter (name, description, allowed-tools) — this was the sole reason for the 0% score - Expanded description with specific trigger terms (dice rolls, shuffling cards, loot drops) - Added verification and related skills sections **dojo-review (66% → 100%)** - Removed unnecessary "When to Use", "What This Skill Does", "Quick Start" sections - Added explicit 4-step review workflow with verification - Streamlined code examples while preserving all ❌/✅ patterns - Added structured review checklist **dojo-system (66% → 94%)** - Consolidated duplicate content (imports, world access, events were explained multiple times) - Added import reference table for quick lookup - Removed redundant "When to Use", "What This Skill Does", "Quick Start" sections - Added safe subtraction pattern in the stateless design example - Added verification section **dojo-world (71% → 100%)** - Replaced verbose permission explanations with a clear hierarchy diagram and role comparison table - Consolidated CLI commands into a single reference block - Added troubleshooting table for common permission errors - Removed redundant intro sections - Added verification section **dojo-model (74% → 86%)** - Removed "When to Use", "What This Skill Does", "Quick Start" sections - Expanded description with additional trigger terms (composite keys, singletons, data schemas) - Added field types reference table - Added verification section
## 🤖 Tessl Skill Review GitHub Action This PR also adds `.github/workflows/skill-review.yml` — a lightweight GitHub Action that automatically reviews skills on future PRs.
How it works and why it helps - **What runs:** On PRs that change `**/SKILL.md`, the workflow runs [`tesslio/skill-review`](https://github.com/tesslio/skill-review) and posts **one** comment with Tessl scores and feedback (updated on new pushes). - **Zero extra accounts:** Contributors do **not** need a Tessl login — only the default **`GITHUB_TOKEN`** is used to post the comment. - **Non-blocking by default:** The check is **feedback-only** — no surprise red CI. Add `fail-threshold: 70` later if you want a hard gate. - **Not a build replacement:** This is review automation for skill markdown only — it doesn't touch your docs build pipeline. - **Why only five skills edited here:** This PR caps manual optimization so it stays reviewable. After merge, **every PR that touches `SKILL.md`** gets automatic review comments, so the rest of the library improves incrementally.
--- Honest disclosure — I work at @tesslio where we build tooling around skills like these. Not a pitch - just saw room for improvement and wanted to contribute. Want to self-improve your skills? Just point your agent (Claude Code, Codex, etc.) at [this Tessl guide](https://docs.tessl.io/evaluate/optimize-a-skill-using-best-practices) and ask it to optimize your skill. Ping me - [@rohan-tessl](https://github.com/rohan-tessl) - if you hit any snags. Thanks in advance 🙏 --- skills/dojo-model/SKILL.md | 159 +++++-------------- skills/dojo-review/SKILL.md | 306 ++++++++++-------------------------- skills/dojo-system/SKILL.md | 232 +++++---------------------- skills/dojo-vrf/SKILL.md | 35 +++-- skills/dojo-world/SKILL.md | 257 ++++-------------------------- 5 files changed, 214 insertions(+), 775 deletions(-) diff --git a/skills/dojo-model/SKILL.md b/skills/dojo-model/SKILL.md index 6960ab88..5b9e83d5 100644 --- a/skills/dojo-model/SKILL.md +++ b/skills/dojo-model/SKILL.md @@ -1,82 +1,20 @@ --- name: dojo-model -description: Create Dojo models for storing game state with proper key definitions, trait derivations, and ECS patterns. Use when defining game entities, components, or state structures. +description: "Create Dojo models in Cairo for storing game state with key definitions, trait derivations, and ECS composition patterns. Define player-owned entities, composite keys, singletons, and nested structs. Use when defining game entities, components, state structures, or data schemas." allowed-tools: Read, Write, Edit, Glob, Grep --- # Dojo Model Generation -Create Dojo models that define your game's state using Entity Component System (ECS) patterns. +Create Dojo models that define game state using Entity Component System (ECS) patterns. -## When to Use This Skill - -- "Add a Position model" -- "Create a Player entity with health and level" -- "Generate an Inventory model" -- "Define a model for [game concept]" - -## What This Skill Does - -Generates Cairo model structs with: -- `#[dojo::model]` attribute -- Required trait derivations (`Drop`, `Serde`) -- Key field configuration (`#[key]`) -- Field types appropriate to your data - -## Quick Start - -**Interactive mode:** -``` -"Add a model for player positions" -``` - -I'll ask about: -- Model name -- Key fields (what makes it unique) -- Data fields and their types - -**Direct mode:** -``` -"Create a Position model with player as key and x, y coordinates" -``` +## Model Structure -## Essential Imports for Models +Models are Cairo structs annotated with `#[dojo::model]`, acting as a key-value store where `#[key]` fields define the lookup key. -**In your model file (e.g., `models.cairo`):** ```cairo use starknet::ContractAddress; -// For nested structs that aren't models -use dojo::meta::Introspect; -``` - -**In systems that use models:** -```cairo -// Import your models -use my_project::models::{Player, Position, Inventory}; - -// Import Dojo storage traits -use dojo::model::{ModelStorage, ModelValueStorage}; -``` - -**Reading/Writing models in a system:** -```cairo -// Get world storage -let mut world = self.world_default(); - -// Read - provide all #[key] values -let player: Player = world.read_model(player_address); - -// Write - model must contain all keys and data -world.write_model(@player); -``` - -## Model Structure - -Models are Cairo structs annotated with `#[dojo::model]`. -They act as a key-value store where `#[key]` fields define the lookup key. - -```cairo #[derive(Drop, Serde)] #[dojo::model] struct Moves { @@ -86,17 +24,12 @@ struct Moves { } ``` -**Required traits:** -- `Drop` - Cairo ownership system -- `Serde` - Serialization for on-chain storage - -**Optional traits:** -- `Copy` - Add when you need to copy values (for primitive types) +**Required traits:** `Drop`, `Serde` +**Optional traits:** `Copy` (for primitive types that need copying) ## Model Patterns ### Player-Owned Model -Models keyed by player address: ```cairo #[derive(Drop, Serde)] #[dojo::model] @@ -116,7 +49,7 @@ struct Vec2 { Custom nested structs must derive `Introspect` for Dojo to understand their structure. ### Composite Keys -Multiple keys for relationships (all keys must be provided when reading): +Multiple keys for relationships — all keys must be provided when reading: ```cairo #[derive(Copy, Drop, Serde)] #[dojo::model] @@ -127,10 +60,8 @@ struct GameResource { location: ContractAddress, balance: u8, } -``` -Read with tuple of all keys: -```cairo +// Read with tuple of all keys let resource: GameResource = world.read_model((player, location)); ``` @@ -147,7 +78,6 @@ struct GameSetting { setting_value: felt252, } -// Usage world.write_model(@GameSetting { setting_id: RESPAWN_DELAY, setting_value: (10 * 60).into() @@ -159,20 +89,11 @@ Small, focused models that can be combined on entities: ```cairo #[derive(Copy, Drop, Serde)] #[dojo::model] -struct Position { - #[key] - id: u32, - x: u32, - y: u32, -} +struct Position { #[key] id: u32, x: u32, y: u32 } #[derive(Copy, Drop, Serde)] #[dojo::model] -struct Health { - #[key] - id: u32, - health: u8, -} +struct Health { #[key] id: u32, health: u8 } // Human has Position + Health + Potions // Orc has Position + Health (no Potions) @@ -180,60 +101,54 @@ struct Health { ## Key Rules -1. **At least one key required** - Every model needs a `#[key]` field -2. **Keys must come first** - All key fields before data fields -3. **Keys are not stored** - Used only for indexing/lookup -4. **All keys required for read** - Composite keys must all be provided +1. **At least one key required** — every model needs a `#[key]` field +2. **Keys must come first** — all key fields before data fields +3. **Keys are not stored** — used only for indexing/lookup +4. **All keys required for read** — composite keys must all be provided ## Model API -Get the world storage in your system: ```cairo use dojo::model::{ModelStorage, ModelValueStorage}; -let mut world = self.world(@"my_namespace"); -``` +let mut world = self.world_default(); -### Write a Model -```cairo +// Write world.write_model(@Position { player, vec: Vec2 { x: 0, y: 0 } }); -``` -### Read a Model -```cairo +// Read let position: Position = world.read_model(player); -``` -### Read with Composite Key -```cairo +// Read with composite key let resource: GameResource = world.read_model((player, location)); -``` -### Generate Unique ID -```cairo +// Generate unique ID let entity_id = world.uuid(); world.write_model(@Health { id: entity_id, health: 100 }); ``` ## Field Types -- `u8`, `u16`, `u32`, `u64`, `u128`, `u256` - Unsigned integers -- `felt252` - Field elements -- `bool` - Booleans -- `ContractAddress` - Starknet addresses -- Custom structs - Must derive `Introspect` -- Custom enums - Must derive `Introspect` +| Type | Use for | +|------|---------| +| `u8`, `u16`, `u32`, `u64`, `u128`, `u256` | Unsigned integers | +| `felt252` | Field elements, hashes | +| `bool` | Flags, toggles | +| `ContractAddress` | Starknet addresses | +| Custom structs | Must derive `Introspect` | +| Custom enums | Must derive `Introspect` | + +## Verification -## Next Steps +After creating models, verify they compile: -After creating models: -1. Use `dojo-system` skill to create systems that use your models -2. Use `dojo-test` skill to test model read/write operations -3. Use `dojo-config` skill to configure permissions +```bash +sozo build +``` ## Related Skills -- **dojo-system**: Create systems that use these models -- **dojo-test**: Test your models -- **dojo-init**: Initialize project first -- **dojo-review**: Review model design +- **dojo-system**: Create systems that read and write these models +- **dojo-test**: Test model read/write operations +- **dojo-init**: Initialize project structure first +- **dojo-review**: Review model design patterns diff --git a/skills/dojo-review/SKILL.md b/skills/dojo-review/SKILL.md index d874a200..b86fb4c8 100644 --- a/skills/dojo-review/SKILL.md +++ b/skills/dojo-review/SKILL.md @@ -1,77 +1,40 @@ --- name: dojo-review -description: Review Dojo code for best practices, common mistakes, security issues, and optimization opportunities. Use when auditing models, systems, tests, or preparing for deployment. +description: "Review Dojo Cairo code for security vulnerabilities, ECS anti-patterns, gas optimization, and test coverage gaps. Audit models, systems, and permissions before deployment. Use when reviewing code, auditing contracts, checking for common mistakes, or preparing for mainnet." allowed-tools: Read, Grep, Glob --- # Dojo Code Review -Review your Dojo code for common issues, security concerns, and optimization opportunities. +Analyze Dojo projects for security issues, anti-patterns, gas optimization, and test coverage gaps. -## When to Use This Skill +## Review Workflow -- "Review my Dojo code" -- "Check this system for issues" -- "Audit my models" -- "Review before deploying" +1. Read the project's models, systems, and tests +2. Check each category below +3. Report findings with specific file locations and fixes +4. Verify fixes compile: `sozo build` -## What This Skill Does +## Model Review -Analyzes your code for: -- Model design patterns -- System implementation issues -- Security vulnerabilities -- Gas optimization opportunities -- Test coverage gaps -- Common mistakes - -## Quick Start - -**Interactive mode:** -``` -"Review my Dojo project" -``` - -I'll ask about: -- What to review (models, systems, tests, all) -- Focus areas (security, performance) - -**Direct mode:** -``` -"Review the combat system for security issues" -"Check if my models follow ECS patterns" -``` - -## Review Categories - -### Model Review - -**Checks:** +**Check for:** - Required trait derivations (`Drop`, `Serde`) -- Key fields defined correctly (`#[key]`) -- Keys come before data fields -- Appropriate field types -- Small, focused models (ECS principle) +- `#[key]` fields defined and placed before data fields +- Small, focused models following ECS principles +- Custom nested structs derive `Introspect` +- Appropriate field type sizes -**Common issues:** ```cairo -// ❌ Missing required traits -#[dojo::model] -struct Position { ... } - -// ✅ Required traits -#[derive(Drop, Serde)] -#[dojo::model] -struct Position { ... } - // ❌ Keys after data fields struct Example { data: u32, #[key] - id: u32, // Must come first! + id: u32, } -// ✅ Keys first +// ✅ Keys first, required traits +#[derive(Drop, Serde)] +#[dojo::model] struct Example { #[key] id: u32, @@ -79,36 +42,24 @@ struct Example { } ``` -### System Review +## System Review -**Checks:** -- Proper interface definition (`#[starknet::interface]`) -- Contract attribute (`#[dojo::contract]`) -- World access with namespace (`self.world(@"namespace")`) -- Input validation -- Event emissions -- Error messages +**Check for:** +- `#[starknet::interface]` and `#[dojo::contract]` attributes +- World access uses correct namespace via `self.world_default()` +- Input validation on all parameters +- Events emitted for state changes +- Clear error messages in assertions -**Common issues:** ```cairo -// ❌ No input validation +// ❌ No input validation, no authorization fn set_health(ref self: ContractState, health: u8) { - // Could be zero or invalid! + // Anyone can call with any value } -// ✅ Input validation +// ✅ Validated and authorized fn set_health(ref self: ContractState, health: u8) { assert(health > 0 && health <= 100, 'invalid health'); -} - -// ❌ No authorization check for sensitive function -fn admin_function(ref self: ContractState) { - // Anyone can call! -} - -// ✅ Authorization check -fn admin_function(ref self: ContractState) { - let mut world = self.world_default(); let caller = get_caller_address(); assert( world.is_owner(selector_from_tag!("my_game"), caller), @@ -117,18 +68,17 @@ fn admin_function(ref self: ContractState) { } ``` -### Security Review +## Security Review -**Checks:** +**Check for:** - Authorization on sensitive functions -- Integer overflow/underflow -- Access control for model writes -- State consistency +- Integer overflow/underflow protection +- Ownership verification before transfers +- Atomic state consistency -**Common vulnerabilities:** ```cairo -// ❌ Integer underflow -health.current -= damage; // Could underflow if damage > current! +// ❌ Integer underflow risk +health.current -= damage; // ✅ Safe subtraction health.current = if health.current > damage { @@ -137,128 +87,44 @@ health.current = if health.current > damage { 0 }; -// ❌ Missing ownership check +// ❌ Missing ownership check on transfer fn transfer_nft(ref self: ContractState, token_id: u256, to: ContractAddress) { - // Anyone can transfer anyone's NFT! let mut nft: NFT = world.read_model(token_id); nft.owner = to; world.write_model(@nft); } -// ✅ Ownership check +// ✅ Ownership verified fn transfer_nft(ref self: ContractState, token_id: u256, to: ContractAddress) { let mut world = self.world_default(); let caller = get_caller_address(); - let mut nft: NFT = world.read_model(token_id); assert(nft.owner == caller, 'not owner'); - nft.owner = to; world.write_model(@nft); } ``` -### Gas Optimization - -**Checks:** -- Minimal model reads/writes -- Efficient data types -- Unnecessary computations +## Gas Optimization -**Optimization opportunities:** ```cairo -// ❌ Multiple reads of same model +// ❌ Duplicate model reads let pos: Position = world.read_model(player); let x = pos.x; -let pos2: Position = world.read_model(player); // Duplicate! +let pos2: Position = world.read_model(player); let y = pos2.y; // ✅ Single read let pos: Position = world.read_model(player); -let x = pos.x; -let y = pos.y; +let (x, y) = (pos.x, pos.y); -// ❌ Oversized types -struct Position { - #[key] - player: ContractAddress, - x: u128, // Overkill for coordinates - y: u128, -} +// ❌ Oversized types for coordinates +x: u128, y: u128 -// ✅ Appropriate types -struct Position { - #[key] - player: ContractAddress, - x: u32, // Sufficient for most games - y: u32, -} +// ✅ Appropriate sizes +x: u32, y: u32 ``` -### Test Coverage - -**Checks:** -- Unit tests for models -- Integration tests for systems -- Edge case coverage -- Failure case testing - -**Coverage gaps:** -```cairo -// Missing tests: -// - Boundary values (max health, zero health) -// - Unauthorized access -// - Invalid inputs -// - Full workflow integration - -// Add: -#[test] -fn test_health_bounds() { ... } - -#[test] -#[should_panic(expected: ('not authorized',))] -fn test_unauthorized_access() { ... } - -#[test] -fn test_spawn_move_attack_flow() { ... } -``` - -## Review Checklist - -### Models -- [ ] All models derive `Drop` and `Serde` -- [ ] Key fields have `#[key]` attribute -- [ ] Keys come before data fields -- [ ] Models are small and focused -- [ ] Field types are appropriate size -- [ ] Custom types derive `Introspect` - -### Systems -- [ ] Interface uses `#[starknet::interface]` -- [ ] Contract uses `#[dojo::contract]` -- [ ] World access uses correct namespace -- [ ] Input validation for all parameters -- [ ] Clear error messages -- [ ] Events emitted for important actions -- [ ] Caller identity verified when needed - -### Security -- [ ] Sensitive functions check permissions -- [ ] Integer math handles over/underflow -- [ ] Ownership verified before transfers -- [ ] State changes are atomic - -### Performance -- [ ] Minimal model reads per function -- [ ] Efficient data types used -- [ ] No unnecessary computations - -### Tests -- [ ] Unit tests for models -- [ ] Integration tests for systems -- [ ] Edge cases tested -- [ ] Failure cases tested with `#[should_panic]` - ## Common Anti-Patterns ### God Models @@ -267,10 +133,7 @@ fn test_spawn_move_attack_flow() { ... } #[dojo::model] struct Player { #[key] player: ContractAddress, - x: u32, y: u32, // Position - health: u8, mana: u8, // Stats - gold: u32, items: u8, // Inventory - level: u8, xp: u32, // Progress + x: u32, y: u32, health: u8, mana: u8, gold: u32, level: u8, xp: u32, } // ✅ Separate concerns (ECS pattern) @@ -280,38 +143,11 @@ Inventory { player, gold, items } Progress { player, level, xp } ``` -### Missing world_default Helper -```cairo -// ❌ Repeating namespace everywhere -fn spawn(ref self: ContractState) { - let mut world = self.world(@"my_game"); - // ... -} - -fn move(ref self: ContractState, direction: u8) { - let mut world = self.world(@"my_game"); - // ... -} - -// ✅ Use internal helper -#[generate_trait] -impl InternalImpl of InternalTrait { - fn world_default(self: @ContractState) -> dojo::world::WorldStorage { - self.world(@"my_game") - } -} - -fn spawn(ref self: ContractState) { - let mut world = self.world_default(); - // ... -} -``` - -### Not Emitting Events +### Missing Events ```cairo -// ❌ No events +// ❌ State change without event fn transfer(ref self: ContractState, to: ContractAddress, amount: u256) { - // Transfer logic but no event + // Transfer logic but no event — Torii can't index this } // ✅ Emit events for indexing @@ -321,18 +157,42 @@ fn transfer(ref self: ContractState, to: ContractAddress, amount: u256) { } ``` -## Next Steps +## Review Checklist -After code review: -1. Fix identified issues -2. Add missing tests -3. Re-run review to verify fixes -4. Use `dojo-test` skill to ensure tests pass -5. Use `dojo-deploy` skill when ready +### Models +- [ ] All models derive `Drop` and `Serde` +- [ ] `#[key]` fields come before data fields +- [ ] Models are small and focused (ECS) +- [ ] Custom types derive `Introspect` + +### Systems +- [ ] Uses `#[dojo::contract]` and `#[starknet::interface]` +- [ ] Input validation on all parameters +- [ ] Events emitted for important state changes +- [ ] Caller identity verified where needed + +### Security +- [ ] Sensitive functions check permissions +- [ ] Integer math handles over/underflow +- [ ] Ownership verified before transfers + +### Tests +- [ ] Integration tests for systems +- [ ] Edge cases and boundary values tested +- [ ] Failure cases tested with `#[should_panic]` + +## Verification + +After fixing issues, confirm the build passes: + +```bash +sozo build +sozo test +``` ## Related Skills -- **dojo-model**: Fix model issues -- **dojo-system**: Fix system issues -- **dojo-test**: Add missing tests -- **dojo-deploy**: Deploy after review +- **dojo-model**: Fix model design issues +- **dojo-system**: Fix system implementation issues +- **dojo-test**: Add missing test coverage +- **dojo-deploy**: Deploy after review passes diff --git a/skills/dojo-system/SKILL.md b/skills/dojo-system/SKILL.md index 51fa1019..6fb0642e 100644 --- a/skills/dojo-system/SKILL.md +++ b/skills/dojo-system/SKILL.md @@ -1,129 +1,23 @@ --- name: dojo-system -description: Create Dojo systems that implement game logic, modify model state, and handle player actions. Use when implementing game mechanics, player commands, or automated logic. +description: "Create Dojo systems in Cairo that implement game logic, read and write model state, emit events, and handle player actions. Use when building game mechanics, implementing combat or movement systems, creating player commands, or adding automated game logic." allowed-tools: Read, Write, Edit, Glob, Grep --- # Dojo System Generation -Create Dojo systems (smart contracts) that implement your game's logic and modify model state. +Create Dojo systems (smart contracts) that implement game logic and modify model state. -## Essential Imports (Dojo 1.0+) - -**Copy these imports for any Dojo system:** - -```cairo -// Core Dojo imports - ALWAYS needed for systems -use dojo::model::{ModelStorage, ModelValueStorage}; -use dojo::event::EventStorage; - -// Starknet essentials -use starknet::{ContractAddress, get_caller_address, get_block_timestamp}; -``` - -### Where does `self.world_default()` come from? - -**`self.world_default()` is provided automatically by `#[dojo::contract]`** - no import needed! - -```cairo -#[dojo::contract] // <-- This macro provides world_default() -mod my_system { - use dojo::model::{ModelStorage, ModelValueStorage}; - use dojo::event::EventStorage; - - #[abi(embed_v0)] - impl MyImpl of IMySystem { - fn my_function(ref self: ContractState) { - // world_default() is available because of #[dojo::contract] - let mut world = self.world_default(); - - // Now use world for all operations... - } - } -} -``` - -### How to emit events - -**Requires:** `use dojo::event::EventStorage;` - -```cairo -// 1. Define the event (outside impl block) -#[derive(Copy, Drop, Serde)] -#[dojo::event] -struct PlayerMoved { - #[key] - player: ContractAddress, - from_x: u32, - from_y: u32, - to_x: u32, - to_y: u32, -} - -// 2. Emit it (inside a function) -fn move_player(ref self: ContractState, direction: u8) { - let mut world = self.world_default(); - - // ... game logic ... - - // Emit event - note the @ for snapshot - world.emit_event(@PlayerMoved { - player: get_caller_address(), - from_x: 0, - from_y: 0, - to_x: 1, - to_y: 1, - }); -} -``` - -### Quick reference: What imports what +## Import Reference | You want to use | Import this | |----------------|-------------| -| `world.read_model()` | `use dojo::model::ModelStorage;` | -| `world.write_model()` | `use dojo::model::ModelStorage;` | +| `world.read_model()` / `world.write_model()` | `use dojo::model::{ModelStorage, ModelValueStorage};` | | `world.emit_event()` | `use dojo::event::EventStorage;` | -| `self.world_default()` | Nothing! Provided by `#[dojo::contract]` | +| `self.world_default()` | Nothing — provided by `#[dojo::contract]` | | `get_caller_address()` | `use starknet::get_caller_address;` | -## When to Use This Skill - -- "Create a spawn system" -- "Add a move system that updates position" -- "Implement combat logic" -- "Generate a system for [game action]" - -## What This Skill Does - -Generates Cairo system contracts with: -- `#[dojo::contract]` attribute -- Interface definition with `#[starknet::interface]` -- System implementation -- World access (`world.read_model()`, `world.write_model()`) -- Event emissions with `#[dojo::event]` - -## Quick Start - -**Interactive mode:** -``` -"Create a system for player movement" -``` - -I'll ask about: -- System name -- Functions and their parameters -- Models used -- Authorization requirements - -**Direct mode:** -``` -"Create a move system that updates Position based on Direction" -``` - -## System Structure - -A Dojo contract consists of an interface trait and a contract module: +## Complete System Example ```cairo use dojo_starter::models::{Direction, Position}; @@ -141,11 +35,9 @@ pub mod actions { use super::{IActions, Direction, Position}; use starknet::{ContractAddress, get_caller_address}; use dojo_starter::models::{Vec2, Moves}; - use dojo::model::{ModelStorage, ModelValueStorage}; use dojo::event::EventStorage; - // Define a custom event #[derive(Copy, Drop, Serde)] #[dojo::event] pub struct Moved { @@ -160,17 +52,14 @@ pub mod actions { let mut world = self.world_default(); let player = get_caller_address(); - // Read current position (defaults to zero if not set) let position: Position = world.read_model(player); - // Set initial position let new_position = Position { player, vec: Vec2 { x: position.vec.x + 10, y: position.vec.y + 10 } }; world.write_model(@new_position); - // Set initial moves let moves = Moves { player, remaining: 100, @@ -184,27 +73,20 @@ pub mod actions { let mut world = self.world_default(); let player = get_caller_address(); - // Read current state let position: Position = world.read_model(player); let mut moves: Moves = world.read_model(player); - // Update moves moves.remaining -= 1; moves.last_direction = direction; - // Calculate next position let next = next_position(position, direction); - // Write updated state world.write_model(@next); world.write_model(@moves); - - // Emit event world.emit_event(@Moved { player, direction }); } } - // Internal helper to get world with namespace #[generate_trait] impl InternalImpl of InternalTrait { fn world_default(self: @ContractState) -> dojo::world::WorldStorage { @@ -213,7 +95,6 @@ pub mod actions { } } -// Helper function outside the contract fn next_position(mut position: Position, direction: Direction) -> Position { match direction { Direction::None => { return position; }, @@ -226,36 +107,25 @@ fn next_position(mut position: Position, direction: Direction) -> Position { } ``` -## Key Concepts - -### World Access -Get the world storage using your namespace: -```cairo -let mut world = self.world(@"my_namespace"); -``` +## System Design Patterns -Create a helper function to avoid repeating the namespace: +### Input Validation +Validate inputs before modifying state: ```cairo -#[generate_trait] -impl InternalImpl of InternalTrait { - fn world_default(self: @ContractState) -> dojo::world::WorldStorage { - self.world(@"my_namespace") - } -} -``` +fn move(ref self: ContractState, direction: Direction) { + let mut world = self.world_default(); + let player = get_caller_address(); -### Reading Models -```cairo -let position: Position = world.read_model(player); -``` + let moves: Moves = world.read_model(player); + assert(moves.remaining > 0, 'No moves remaining'); + assert(moves.can_move, 'Movement disabled'); -### Writing Models -```cairo -world.write_model(@Position { player, vec: Vec2 { x: 10, y: 20 } }); + // Proceed with movement +} ``` -### Emitting Events -Define events with `#[dojo::event]`: +### Event Emission +Define events with `#[dojo::event]` and emit after state changes: ```cairo #[derive(Copy, Drop, Serde)] #[dojo::event] @@ -266,24 +136,10 @@ pub struct PlayerMoved { pub to: Vec2, } -// Emit in your function +// Inside a function world.emit_event(@PlayerMoved { player, from: old_pos, to: new_pos }); ``` -### Getting Caller -```cairo -use starknet::get_caller_address; - -let player = get_caller_address(); -``` - -### Generating Unique IDs -```cairo -let entity_id = world.uuid(); -``` - -## System Design - ### Single Responsibility Each system should have one clear purpose: - `MovementSystem`: Handles player/entity movement @@ -291,62 +147,46 @@ Each system should have one clear purpose: - `InventorySystem`: Manages items ### Stateless Design -Systems should be stateless, reading state from models: +Systems read state from models, apply logic, write back: ```cairo fn attack(ref self: ContractState, target: ContractAddress) { let mut world = self.world_default(); let attacker = get_caller_address(); - // Read current state let attacker_stats: Combat = world.read_model(attacker); let mut target_stats: Combat = world.read_model(target); - // Apply logic - target_stats.health -= attacker_stats.damage; + target_stats.health = if target_stats.health > attacker_stats.damage { + target_stats.health - attacker_stats.damage + } else { + 0 + }; - // Write updated state world.write_model(@target_stats); } ``` -### Input Validation -Validate inputs before modifying state: -```cairo -fn move(ref self: ContractState, direction: Direction) { - let mut world = self.world_default(); - let player = get_caller_address(); - - let moves: Moves = world.read_model(player); - assert(moves.remaining > 0, 'No moves remaining'); - assert(moves.can_move, 'Movement disabled'); - - // Proceed with movement -} -``` - ## Permissions -Systems need writer permission to modify models. -Configure in `dojo_dev.toml`: +Systems need writer permission to modify models. Configure in `dojo_dev.toml`: ```toml [writers] +# Namespace-level access "my_namespace" = ["my_namespace-actions"] -``` -Or grant specific model access: -```toml -[writers] +# Or specific model access "my_namespace-Position" = ["my_namespace-actions"] "my_namespace-Moves" = ["my_namespace-actions"] ``` -## Next Steps +## Verification -After creating systems: -1. Use `dojo-test` skill to test system logic -2. Use `dojo-review` skill to check for issues -3. Use `dojo-deploy` skill to deploy your world -4. Use `dojo-client` skill to call systems from frontend +After creating a system, verify it compiles: + +```bash +sozo build +sozo test +``` ## Related Skills diff --git a/skills/dojo-vrf/SKILL.md b/skills/dojo-vrf/SKILL.md index f3df9529..540b34e8 100644 --- a/skills/dojo-vrf/SKILL.md +++ b/skills/dojo-vrf/SKILL.md @@ -1,10 +1,12 @@ -# Cartridge VRF Integration - -Integrate Cartridge's Verifiable Random Function (VRF) for provably fair, atomic randomness in Dojo games. +--- +name: dojo-vrf +description: "Integrate Cartridge VRF for provably fair, atomic randomness in Dojo games. Generate random numbers, dice rolls, weighted selections, and shuffles using onchain verifiable randomness. Use when adding randomness to game mechanics, rolling dice, shuffling cards, or implementing loot drops." +allowed-tools: Read, Write, Edit, Bash, Glob, Grep +--- -## Overview +# Cartridge VRF Integration -Cartridge VRF provides cheap, atomic verifiable randomness for fully onchain games. The VRF request and response are processed within the **same transaction**, enabling synchronous and immediate randomness. +Integrate Cartridge's Verifiable Random Function (VRF) for provably fair, atomic randomness in Dojo games. The VRF request and response are processed within the **same transaction**, enabling synchronous and immediate randomness. ## Contract Addresses @@ -241,15 +243,28 @@ fn shuffle, impl TDrop: Drop>( } ``` -## Important Notes +## Critical Rules - **Always** match `Source` in `request_random` and `consume_random` - `consume_random` must be called within the same transaction - The Paymaster handles proof submission automatically - Works with Cartridge Controller out of the box -## Resources +## Verification + +After integrating VRF, verify the setup: + +```bash +# Build to check compilation +sozo build + +# Run tests with mock provider +sozo test +``` + +## Related Skills -- GitHub: https://github.com/cartridge-gg/vrf -- Voyager (Mainnet): https://voyager.online/contract/0x051fea4450da9d6aee758bdeba88b2f665bcbf549d2c61421aa724e9ac0ced8f -- Voyager (Sepolia): https://sepolia.voyager.online/contract/0x051fea4450da9d6aee758bdeba88b2f665bcbf549d2c61421aa724e9ac0ced8f +- **dojo-system**: Create systems that use VRF randomness +- **dojo-deploy**: Deploy your VRF-enabled contracts +- **dojo-test**: Test randomness with mock provider +- **dojo-client**: Call VRF-enabled systems from frontend diff --git a/skills/dojo-world/SKILL.md b/skills/dojo-world/SKILL.md index 52620b60..d1a7d3d6 100644 --- a/skills/dojo-world/SKILL.md +++ b/skills/dojo-world/SKILL.md @@ -1,58 +1,14 @@ --- name: dojo-world -description: Manage world permissions, namespaces, resource registration, and access control. Use when configuring world ownership, setting up authorization policies, or managing resource permissions. +description: "Manage Dojo world permissions, namespaces, resource registration, and access control. Grant and revoke owner and writer roles via config or Cairo. Use when configuring world ownership, setting up authorization policies, managing resource permissions, or debugging access errors." allowed-tools: Read, Write, Bash, Grep --- # Dojo World Management -Manage your Dojo world's permissions, namespaces, resource registration, and access control policies. +Manage your Dojo world's permissions, namespaces, and access control policies. -## When to Use This Skill - -- "Configure world permissions" -- "Set up namespace access" -- "Grant writer permissions" -- "Manage resource ownership" - -## What This Skill Does - -Handles world management: -- Namespace configuration -- Writer permissions (can write data) -- Owner permissions (can write data + manage permissions) -- Permission hierarchy management - -## Quick Start - -**Configure permissions:** -``` -"Grant writer permission to my system" -``` - -**Check permissions:** -``` -"List permissions for my world" -``` - -## Permission Concepts - -### Permission Types - -**Owner Permission:** -- Write data to the resource -- Grant and revoke permissions to others -- Upgrade the resource -- Set resource metadata - -**Writer Permission:** -- Write data to the resource -- Cannot grant permissions to others -- Cannot upgrade the resource - -**Reading is always permissionless.** - -### Permission Hierarchy +## Permission Hierarchy ``` World Owner (highest) @@ -60,16 +16,21 @@ World Owner (highest) └── Resource Owner / Writer (lowest) ``` -- **World Owner**: Can do anything in the world -- **Namespace Owner**: Can manage all resources in their namespace -- **Resource Owner**: Can manage a specific resource (model/contract/event) -- **Writer**: Can only write data to a resource +| Role | Write Data | Grant Permissions | Upgrade Resource | +|------|-----------|-------------------|------------------| +| Owner | ✅ | ✅ | ✅ | +| Writer | ✅ | ❌ | ❌ | + +Reading is always permissionless. ## Configuration-Based Permissions Set permissions during deployment in `dojo_.toml`: ```toml +[namespace] +default = "my_game" + [writers] # Namespace-level: actions can write to all resources in my_game "my_game" = ["my_game-actions"] @@ -81,41 +42,26 @@ Set permissions during deployment in `dojo_.toml`: "my_game" = ["my_game-admin"] ``` -**Format:** `"" = [""]` +Format: `"" = [""]` ## CLI Permission Management -### Grant Permissions - ```bash # Grant writer permission sozo auth grant writer my_game-Position,my_game-actions # Grant owner permission sozo auth grant owner my_game,my_game-admin -``` - -### Revoke Permissions -```bash # Revoke writer permission sozo auth revoke writer my_game-Position,my_game-actions -# Revoke owner permission -sozo auth revoke owner my_game,my_game-admin -``` - -### List Permissions - -```bash # List all permissions sozo auth list ``` ## Runtime Permission Management (Cairo) -### Grant Permissions - ```cairo use dojo::world::WorldStorage; @@ -130,104 +76,36 @@ world.grant_owner( selector_from_tag!("my_game-GameState"), new_owner_address ); -``` -### Revoke Permissions - -```cairo -// Revoke writer permission +// Revoke permissions world.revoke_writer( selector_from_tag!("my_game-Position"), old_system_address ); -// Revoke owner permission -world.revoke_owner( - selector_from_tag!("my_game-GameState"), - old_owner_address -); -``` - -### Check Permissions - -```cairo -// Check if address is owner +// Check permissions let is_owner = world.is_owner(resource_selector, address); - -// Check if address is writer let can_write = world.is_writer(resource_selector, address); ``` ## Permission Patterns ### Principle of Least Privilege - ```cairo -// Good: Specific permissions for specific functions +// ✅ Specific permissions per system world.grant_writer(selector_from_tag!("my_game-Position"), movement_contract); world.grant_writer(selector_from_tag!("my_game-Health"), combat_contract); -// Bad: Overly broad permissions +// ❌ Overly broad — avoid unless necessary world.grant_owner(selector_from_tag!("my_game"), movement_contract); ``` -### Multi-System Architecture - -```cairo -// Different systems handle different aspects -world.grant_writer(selector_from_tag!("my_game-Position"), movement_system); -world.grant_writer(selector_from_tag!("my_game-Health"), combat_system); -world.grant_writer(selector_from_tag!("my_game-Inventory"), inventory_system); - -// Trading system needs access to Inventory too -world.grant_writer(selector_from_tag!("my_game-Inventory"), trading_system); -``` - -### Namespace-Level Permissions - -Grant access to all resources in a namespace: - -```cairo -// This system can write to ANY resource in "my_game" namespace -world.grant_writer( - selector_from_tag!("my_game"), - system_contract -); -``` - -### Admin System - -```cairo -// Admin has owner permission on namespace -world.grant_owner(selector_from_tag!("my_game"), game_admin); - -// Admin has owner permission on critical resources -world.grant_owner(selector_from_tag!("my_game-GameConfig"), game_admin); -``` - -## Authorization in Systems - -### Public Functions - -Anyone can call: -```cairo -fn spawn(ref self: ContractState) { - let mut world = self.world_default(); - let player = get_caller_address(); - - // No permission check - anyone can spawn - world.write_model(@Position { player, vec: Vec2 { x: 0, y: 0 } }); -} -``` - -### Checking Permissions - +### Authorization Check in Systems ```cairo fn admin_function(ref self: ContractState) { let mut world = self.world_default(); let caller = get_caller_address(); - // Check caller is owner of the namespace assert( world.is_owner(selector_from_tag!("my_game"), caller), 'not authorized' @@ -237,109 +115,40 @@ fn admin_function(ref self: ContractState) { } ``` -## Permission Events - -The World contract emits events when permissions change: - -```cairo -#[derive(Drop, starknet::Event)] -pub struct OwnerUpdated { - #[key] - pub resource: felt252, - #[key] - pub contract: ContractAddress, - pub value: bool, -} - -#[derive(Drop, starknet::Event)] -pub struct WriterUpdated { - #[key] - pub resource: felt252, - #[key] - pub contract: ContractAddress, - pub value: bool, -} -``` - -## Common Scenarios - -### Initial Setup (via config) - -```toml -# dojo_dev.toml -[namespace] -default = "my_game" - -[writers] -# All resources in my_game can be written by actions -"my_game" = ["my_game-actions"] - -[owners] -# Admin system owns the namespace -"my_game" = ["my_game-admin"] -``` - -### Adding New System (runtime) - -```cairo -fn add_new_system(ref self: ContractState, new_system_address: ContractAddress) { - let mut world = self.world_default(); - - // Must be namespace owner to grant permissions - world.grant_writer( - selector_from_tag!("my_game-Position"), - new_system_address - ); -} -``` - ### Transfer Namespace Ownership - ```cairo fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { let mut world = self.world_default(); - // Grant owner to new address world.grant_owner(selector_from_tag!("my_game"), new_owner); - - // Revoke from current owner world.revoke_owner(selector_from_tag!("my_game"), get_caller_address()); } ``` ## Troubleshooting -### "Not authorized" errors -- Check writer permissions are granted -- Verify the system address is correct -- Check if permission is at namespace or resource level +| Error | Check | +|-------|-------| +| "Not authorized" | Writer permissions granted? System address correct? | +| "Permission denied" | You have owner permission to grant/revoke? | +| Unexpected access | Run `sozo auth list` to audit current permissions | -### "Permission denied" -- Check you have owner permission to grant/revoke -- Verify you're calling from the correct account +## Verification -### Debugging Permissions +After configuring permissions, verify the setup: -```cairo -fn debug_permissions(world: @WorldStorage, resource: felt252, address: ContractAddress) { - let is_owner = world.is_owner(resource, address); - let is_writer = world.is_writer(resource, address); +```bash +# List all permissions and review +sozo auth list - // Log or print these values for debugging -} +# Build and test to confirm authorization works +sozo build +sozo test ``` -## Next Steps - -After permission setup: -1. Test all permission checks work correctly -2. Document the permission structure -3. Set up monitoring for permission changes -4. Consider using a multi-sig for production owner accounts - ## Related Skills - **dojo-deploy**: Deploy world first -- **dojo-system**: Add authorization to systems -- **dojo-config**: Configure permissions in profile +- **dojo-system**: Add authorization checks to systems +- **dojo-config**: Configure permissions in deployment profile - **dojo-review**: Audit permission setup From 7ba92ee5efab27cebc9d0bbb66371fed5bff3533 Mon Sep 17 00:00:00 2001 From: rohan-tessl Date: Wed, 25 Mar 2026 16:44:58 +0530 Subject: [PATCH 2/2] ci: add Tessl skill review workflow for SKILL.md PRs --- .github/workflows/skill-review.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/skill-review.yml diff --git a/.github/workflows/skill-review.yml b/.github/workflows/skill-review.yml new file mode 100644 index 00000000..aee21d6b --- /dev/null +++ b/.github/workflows/skill-review.yml @@ -0,0 +1,22 @@ +# Tessl Skill Review — runs on PRs that change any SKILL.md; posts scores as one PR comment. +# Docs: https://github.com/tesslio/skill-review +name: Tessl Skill Review + +on: + pull_request: + branches: [main] + paths: + - "**/SKILL.md" + +jobs: + review: + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + steps: + - uses: actions/checkout@v4 + - uses: tesslio/skill-review@main + # Optional quality gate (off by default — do not enable unless user asked): + # with: + # fail-threshold: 70