dojo-review
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDojo Code Review
Dojo代码审查
Review your Dojo code for common issues, security concerns, and optimization opportunities.
审查你的Dojo代码,找出常见问题、安全隐患以及优化空间。
When to Use This Skill
何时使用该Skill
- "Review my Dojo code"
- "Check this system for issues"
- "Audit my models"
- "Review before deploying"
- “审查我的Dojo代码”
- “检查这个系统的问题”
- “审计我的模型”
- “部署前审查”
What This Skill Does
该Skill的功能
Analyzes your code for:
- Model design patterns
- System implementation issues
- Security vulnerabilities
- Gas optimization opportunities
- Test coverage gaps
- Common mistakes
分析你的代码,涵盖以下方面:
- 模型设计模式
- 系统实现问题
- 安全漏洞
- Gas优化空间
- 测试覆盖缺口
- 常见错误
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 my Dojo project"我会询问以下内容:
- 审查范围(模型、系统、测试、全部)
- 重点关注领域(安全、性能)
直接模式:
"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,
}检查项:
- 必需的trait派生(、
Drop)Serde - 正确定义关键字段(属性)
#[key] - 关键字段位于数据字段之前
- 合适的字段类型
- 小巧且职责单一的模型(ECS原则)
常见问题:
cairo
// ❌ 缺少必需的trait
#[dojo::model]
struct Position { ... }
// ✅ 包含必需的trait
#[derive(Drop, Serde)]
#[dojo::model]
struct Position { ... }
// ❌ 关键字段在数据字段之后
struct Example {
data: u32,
#[key]
id: u32, // 必须放在前面!
}
// ✅ 关键字段在前
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'
);
}检查项:
- 正确定义接口()
#[starknet::interface] - 合约使用属性
#[dojo::contract] - 使用命名空间访问World()
self.world(@"namespace") - 输入验证
- 事件触发
- 错误信息
常见问题:
cairo
// ❌ 无输入验证
fn set_health(ref self: ContractState, health: u8) {
// 可能为零或无效值!
}
// ✅ 输入验证
fn set_health(ref self: ContractState, health: u8) {
assert(health > 0 && health <= 100, 'invalid health');
}
// ❌ 敏感函数未做权限检查
fn admin_function(ref self: ContractState) {
// 任何人都可以调用!
}
// ✅ 权限检查
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);
}检查项:
- 敏感函数的权限检查
- 整数溢出/下溢处理
- 模型写入的访问控制
- 状态一致性
常见漏洞:
cairo
// ❌ 整数下溢
health.current -= damage; // 如果damage大于current,会发生下溢!
// ✅ 安全减法
health.current = if health.current > damage {
health.current - damage
} else {
0
};
// ❌ 缺少所有权检查
fn transfer_nft(ref self: ContractState, token_id: u256, to: ContractAddress) {
// 任何人都可以转移他人的NFT!
let mut nft: NFT = world.read_model(token_id);
nft.owner = to;
world.write_model(@nft);
}
// ✅ 所有权检查
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
Gas优化
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,
}检查项:
- 最小化模型读写次数
- 高效的数据类型
- 避免不必要的计算
优化机会:
cairo
// ❌ 多次读取同一模型
let pos: Position = world.read_model(player);
let x = pos.x;
let pos2: Position = world.read_model(player); // 重复读取!
let y = pos2.y;
// ✅ 单次读取
let pos: Position = world.read_model(player);
let x = pos.x;
let y = pos.y;
// ❌ 类型过大
struct Position {
#[key]
player: ContractAddress,
x: u128, // 坐标用这个类型太冗余
y: u128,
}
// ✅ 合适的类型
struct Position {
#[key]
player: ContractAddress,
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() { ... }检查项:
- 模型的单元测试
- 系统的集成测试
- 边界场景覆盖
- 失败场景测试
覆盖缺口:
cairo
// 缺失的测试:
// - 边界值(最大生命值、零生命值)
// - 未授权访问
// - 无效输入
// - 完整工作流集成
// 补充:
#[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 and
DropSerde - Key fields have attribute
#[key] - Keys come before data fields
- Models are small and focused
- Field types are appropriate size
- Custom types derive
Introspect
- 所有模型都派生和
DropSerde - 关键字段带有属性
#[key] - 关键字段位于数据字段之前
- 模型小巧且职责单一
- 字段类型大小合适
- 自定义类型派生
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
- 接口使用
#[starknet::interface] - 合约使用
#[dojo::contract] - 访问World使用正确的命名空间
- 所有参数都有输入验证
- 错误信息清晰
- 重要操作触发事件
- 必要时验证调用者身份
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]
- 模型有单元测试
- 系统有集成测试
- 测试边界场景
- 使用测试失败场景
#[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 }cairo
// ❌ 所有内容放在一个模型里
#[dojo::model]
struct Player {
#[key] player: ContractAddress,
x: u32, y: u32, // 位置
health: u8, mana: u8, // 属性
gold: u32, items: u8, // 背包
level: u8, xp: u32, // 进度
}
// ✅ 分离关注点(ECS模式)
Position { player, x, y }
Stats { player, health, mana }
Inventory { player, gold, items }
Progress { player, level, xp }Missing world_default Helper
缺少world_default助手函数
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();
// ...
}cairo
// ❌ 重复使用命名空间
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");
// ...
}
// ✅ 使用内部助手函数
#[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 });
}cairo
// ❌ 无事件触发
fn transfer(ref self: ContractState, to: ContractAddress, amount: u256) {
// 转移逻辑但无事件
}
// ✅ 触发事件用于索引
fn transfer(ref self: ContractState, to: ContractAddress, amount: u256) {
// 转移逻辑
world.emit_event(@Transferred { from, to, amount });
}Next Steps
后续步骤
After code review:
- Fix identified issues
- Add missing tests
- Re-run review to verify fixes
- Use skill to ensure tests pass
dojo-test - Use skill when ready
dojo-deploy
代码审查完成后:
- 修复发现的问题
- 补充缺失的测试
- 重新运行审查以验证修复效果
- 使用Skill确保测试通过
dojo-test - 准备就绪后使用Skill
dojo-deploy
Related Skills
相关Skills
- dojo-model: Fix model issues
- dojo-system: Fix system issues
- dojo-test: Add missing tests
- dojo-deploy: Deploy after review
- dojo-model: 修复模型问题
- dojo-system: 修复系统问题
- dojo-test: 补充缺失的测试
- dojo-deploy: 审查后部署