Skip to content
Empty file modified .devcontainer/install-tools.sh
100644 β†’ 100755
Empty file.
2 changes: 2 additions & 0 deletions src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod systems {
pub mod armour;
pub mod pet;
pub mod tournament;
pub mod guild;
pub mod session;
pub mod position;
}
Expand All @@ -29,6 +30,7 @@ pub mod models {
pub mod vehicle_stats;
pub mod pet_stats;
pub mod tournament;
pub mod guild;
pub mod session;
pub mod weapon {
pub mod blunt;
Expand Down
132 changes: 132 additions & 0 deletions src/models/guild.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use starknet::ContractAddress;
use core::num::traits::zero::Zero;
use crate::helpers::base::ContractAddressDefault;

// --- ENUMS ---

#[derive(Drop, Copy, Serde, Debug, Default, PartialEq, Introspect)]
pub enum GuildRole {
#[default]
Member,
Officer,
Leader,
}

// --- MODELS ---

#[dojo::model]
#[derive(Drop, Copy, Serde, Debug, Default, PartialEq)]
pub struct Guild {
#[key]
pub id: u256,
pub name: felt252,
pub leader: ContractAddress,
pub level: u32,
pub experience: u256,
pub member_count: u32,
pub max_members: u32,
pub created_at: u64,
pub description: felt252,
}

#[dojo::model]
#[derive(Drop, Copy, Serde, Debug, PartialEq, Default)]
pub struct GuildMember {
#[key]
pub guild_id: u256,
#[key]
pub player_id: ContractAddress,
pub role: GuildRole,
pub joined_at: u64,
pub contribution: u256,
}

#[dojo::model]
#[derive(Drop, Copy, Serde, Debug, PartialEq, Default)]
pub struct PlayerGuildMembership {
#[key]
pub player_id: ContractAddress,
pub guild_id: u256,
}

#[dojo::model]
#[derive(Drop, Copy, Serde, Debug, PartialEq, Default)]
pub struct GuildInvite {
#[key]
pub guild_id: u256,
#[key]
pub player_id: ContractAddress,
pub invited_by: ContractAddress,
pub created_at: u64,
pub expires_at: u64,
pub is_accepted: bool,
}

// --- EVENTS ---

#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct GuildCreated {
#[key]
pub guild_id: u256,
pub leader: ContractAddress,
pub name: felt252,
}

#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct GuildJoined {
#[key]
pub guild_id: u256,
pub player_id: ContractAddress,
pub role: GuildRole,
}

#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct GuildLeft {
#[key]
pub guild_id: u256,
pub player_id: ContractAddress,
}

#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct GuildInviteSent {
#[key]
pub guild_id: u256,
pub player_id: ContractAddress,
pub invited_by: ContractAddress,
}

#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct GuildInviteAccepted {
#[key]
pub guild_id: u256,
pub player_id: ContractAddress,
}

#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct GuildLevelUp {
#[key]
pub guild_id: u256,
pub new_level: u32,
}

// --- HELPERS & ERRORS ---

pub mod Errors {
pub const GUILD_NOT_FOUND: felt252 = 'Guild not found';
pub const PLAYER_ALREADY_IN_GUILD: felt252 = 'Player already in guild';
pub const PLAYER_NOT_IN_GUILD: felt252 = 'Player not in guild';
pub const NOT_GUILD_LEADER: felt252 = 'Not the guild leader';
pub const NOT_GUILD_OFFICER: felt252 = 'Not a guild officer';
pub const GUILD_FULL: felt252 = 'Guild is full';
pub const INVALID_GUILD_NAME: felt252 = 'Invalid guild name';
pub const INSUFFICIENT_PERMISSIONS: felt252 = 'Insufficient permissions';
pub const INVITE_NOT_FOUND: felt252 = 'Invite not found';
pub const INVITE_EXPIRED: felt252 = 'Invite expired';
pub const ALREADY_INVITED: felt252 = 'Player already invited';
}
15 changes: 15 additions & 0 deletions src/models/player.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@ pub impl PlayerImpl of PlayerTrait {
self.mint(CREDITS, erc1155_address, amount);
}

#[inline(always)]
fn deduct_credits(ref self: Player, amount: u256, erc1155_address: ContractAddress) {
self.check();
assert(amount > 0, 'INVALID AMOUNT');
assert(self.get_credits(erc1155_address) >= amount, 'Insufficient credits');
self.burn(CREDITS, erc1155_address, amount);
}
Comment on lines +167 to +173
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Potential reentrancy vulnerability in deduct_credits

The function checks the balance before burning, creating a potential race condition. Between the balance check and the burn operation, the player's credits could be modified by another transaction, leading to an incorrect burn amount.

Apply this diff to make the operation atomic:

 fn deduct_credits(ref self: Player, amount: u256, erc1155_address: ContractAddress) {
     self.check();
     assert(amount > 0, 'INVALID AMOUNT');
-    assert(self.get_credits(erc1155_address) >= amount, 'Insufficient credits');
+    // Let the burn function handle insufficient balance checks
     self.burn(CREDITS, erc1155_address, amount);
 }

The ERC1155 contract should handle the balance validation atomically during the burn operation.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[inline(always)]
fn deduct_credits(ref self: Player, amount: u256, erc1155_address: ContractAddress) {
self.check();
assert(amount > 0, 'INVALID AMOUNT');
assert(self.get_credits(erc1155_address) >= amount, 'Insufficient credits');
self.burn(CREDITS, erc1155_address, amount);
}
#[inline(always)]
fn deduct_credits(ref self: Player, amount: u256, erc1155_address: ContractAddress) {
self.check();
assert(amount > 0, 'INVALID AMOUNT');
// Let the burn function handle insufficient balance checks
self.burn(CREDITS, erc1155_address, amount);
}
πŸ€– Prompt for AI Agents
In src/models/player.cairo around lines 167 to 173, the separate balance check
before calling burn creates a race/reentrancy window; remove the explicit
get_credits check and rely on the ERC1155 burn call to atomically validate and
decrement the balance (i.e., keep self.check(), assert(amount > 0), then call
self.burn(CREDITS, erc1155_address, amount) without a prior balance assertion),
and ensure the ERC1155 burn implementation enforces sufficient-balance
validation and reverts with a clear error message so the entire deduction is
atomic.


#[inline(always)]
fn mint(ref self: Player, id: u256, erc1155_address: ContractAddress, amount: u256) {
// to mint anything fungible
Expand All @@ -172,6 +180,13 @@ pub impl PlayerImpl of PlayerTrait {
erc1155mint(erc1155_address).mint(self.id, id, amount, array![].span());
}

#[inline(always)]
fn burn(ref self: Player, id: u256, erc1155_address: ContractAddress, amount: u256) {
// to burn anything fungible
let erc1155_mintable = IERC1155MintableDispatcher { contract_address: erc1155_address };
erc1155_mintable.burn(self.id, id, amount);
}

#[inline(always)]
fn mint_batch(ref self: Player) {}

Expand Down
8 changes: 3 additions & 5 deletions src/models/tournament.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use starknet::ContractAddress;
use core::option::Option;
use core::num::traits::zero::Zero;
use crate::helpers::base::ContractAddressDefault;

// --- ENUMS ---

Expand Down Expand Up @@ -28,8 +29,10 @@ pub struct Config {
pub id: u8,
pub admin: ContractAddress,
pub next_tournament_id: u256,
pub next_guild_id: u256,
pub erc1155_address: ContractAddress,
pub credit_token_id: u256,
pub default_guild_max_members: u32,
}

#[dojo::model]
Expand Down Expand Up @@ -157,11 +160,6 @@ pub struct PrizeClaimed {


// --- HELPERS & ERRORS ---
impl ContractAddressDefault of Default<ContractAddress> {
fn default() -> ContractAddress {
Zero::zero()
}
}

pub mod Errors {
pub const NOT_ADMIN: felt252 = 'Caller is not the admin';
Expand Down
Loading
Loading