dojo-review

$npx mdskill add dojoengine/book/dojo-review

Audit Dojo code for security, performance, and pattern violations.

  • Detects missing traits, incorrect keys, and gas optimization gaps.
  • Uses Read, Grep, and Glob tools to scan codebases.
  • Analyzes model structures and system implementations for issues.
  • Reports findings directly through interactive or direct command modes.
SKILL.md
.github/skills/dojo-reviewView on GitHub ↗
---
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.
allowed-tools: Read, Grep, Glob
---

# Dojo Code Review

Review your Dojo code for common issues, security concerns, and optimization opportunities.

## When to Use This Skill

- "Review my Dojo code"
- "Check this system for issues"
- "Audit my models"
- "Review before deploying"

## What This Skill Does

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:**
- Required trait derivations (`Drop`, `Serde`)
- Key fields defined correctly (`#[key]`)
- Keys come before data fields
- Appropriate field types
- Small, focused models (ECS principle)

**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!
}

// ✅ Keys first
struct Example {
    #[key]
    id: u32,
    data: u32,
}
```

### 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

**Common issues:**
```cairo
// ❌ No input validation
fn set_health(ref self: ContractState, health: u8) {
    // Could be zero or invalid!
}

// ✅ Input validation
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),
        'not authorized'
    );
}
```

### Security Review

**Checks:**
- Authorization on sensitive functions
- Integer overflow/underflow
- Access control for model writes
- State consistency

**Common vulnerabilities:**
```cairo
// ❌ Integer underflow
health.current -= damage;  // Could underflow if damage > current!

// ✅ Safe subtraction
health.current = if health.current > damage {
    health.current - damage
} else {
    0
};

// ❌ Missing ownership check
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
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

**Optimization opportunities:**
```cairo
// ❌ Multiple reads of same model
let pos: Position = world.read_model(player);
let x = pos.x;
let pos2: Position = world.read_model(player);  // Duplicate!
let y = pos2.y;

// ✅ Single read
let pos: Position = world.read_model(player);
let x = pos.x;
let y = pos.y;

// ❌ Oversized types
struct Position {
    #[key]
    player: ContractAddress,
    x: u128,  // Overkill for coordinates
    y: u128,
}

// ✅ Appropriate types
struct Position {
    #[key]
    player: ContractAddress,
    x: u32,  // Sufficient for most games
    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
```cairo
// ❌ Everything in one model
#[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
}

// ✅ Separate concerns (ECS pattern)
Position { player, x, y }
Stats { player, health, mana }
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
```cairo
// ❌ No events
fn transfer(ref self: ContractState, to: ContractAddress, amount: u256) {
    // Transfer logic but no event
}

// ✅ Emit events for indexing
fn transfer(ref self: ContractState, to: ContractAddress, amount: u256) {
    // Transfer logic
    world.emit_event(@Transferred { from, to, amount });
}
```

## Next Steps

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

## Related Skills

- **dojo-model**: Fix model issues
- **dojo-system**: Fix system issues
- **dojo-test**: Add missing tests
- **dojo-deploy**: Deploy after review
More from dojoengine/book