Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 89 additions & 1 deletion src/helpers/gear.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::models::gear::GearType;
use crate::models::gear::{GearType, GearDetails, GearDetailsImpl};
use origami_random::dice::{Dice, DiceTrait};

// Helper function to calculate upgrade multipliers based on level
Expand Down Expand Up @@ -167,3 +167,91 @@ pub fn get_min_xp_needed(gear_type: GearType) -> u256 {
}
}

// Helper function to generate random GearDetails
pub fn random_gear_details() -> GearDetails {
let mut gear_type = random_geartype();

if gear_type == GearType::None {
// Fallback to a sane default; alternatively re-roll with a different seed.
gear_type = GearType::Weapon;
}

let min_xp_needed = get_min_xp_needed(gear_type);
let max_upgrade_level = get_max_upgrade_level(gear_type);

// Generate random base damage (between 10 and 100 for simplicity)
let mut dice = DiceTrait::new(90, 'DAMAGE_SEED');
let base_damage: u64 = (10 + dice.roll()).into();

GearDetails {
gear_type, min_xp_needed, base_damage, max_upgrade_level, total_count: 1, variation_ref: 0,
}
}

// Helper function to validate GearDetails array
pub fn validate_gear_details_array(details: @Array<GearDetails>) -> bool {
let mut i = 0;
let mut valid = true;

while i < details.len() {
let gear_details = *details.at(i);
if !gear_details.validate() {
valid = false;
break;
}
i += 1;
};

valid
}

// Helper function to generate multiple GearDetails for batch spawning
pub fn generate_batch_gear_details(amount: u32) -> Array<GearDetails> {
let mut result = array![];
let mut i = 0;

while i < amount {
result.append(random_gear_details());
i += 1;
};

result
}

// Tests for helper functions
#[cfg(test)]
mod tests {
use super::{
GearDetailsImpl, GearType, random_gear_details, validate_gear_details_array,
generate_batch_gear_details,
};

#[test]
fn test_random_gear_details() {
let gear = random_gear_details();
assert(gear.validate(), 'Random gear should be valid');
assert(gear.gear_type != GearType::None, 'Gear type should not be None');
assert(gear.base_damage >= 10 && gear.base_damage <= 100, 'Base damage out of range');
assert(gear.total_count == 1, 'Total count should be 1');
}

#[test]
#[should_panic(expected: 'Gear type cannot be None')]
fn test_validate_gear_details_array() {
let mut details = array![
GearDetailsImpl::new(GearType::Sword, 30, 50, 10, 1, 1),
GearDetailsImpl::new(GearType::Helmet, 15, 0, 5, 1, 0),
];
assert(validate_gear_details_array(@details), 'Valid array should pass');

// Add invalid gear details
details.append(GearDetailsImpl::new(GearType::None, 5, 10, 1, 1, 0));
}

#[test]
fn test_generate_batch_gear_details() {
let details = generate_batch_gear_details(3);
assert(details.len() == 3, 'Should generate 3 gear details');
assert(validate_gear_details_array(@details), 'Batch should be valid');
}
}
116 changes: 116 additions & 0 deletions src/models/gear.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ pub enum GearType {
Drone // 0x800001
}

#[derive(Drop, Copy, Serde)]
pub struct GearDetails {
// Type of gear (e.g., Weapon, Helmet, Vehicle)
pub gear_type: GearType,
// Minimum XP required to use the gear
pub min_xp_needed: u256,
// Base damage value for the gear (0 for non-damaging items)
pub base_damage: u64,
// Maximum upgrade level for the gear
pub max_upgrade_level: u64,
// Initial total count for fungible items (default 1 for non-fungible)
pub total_count: u64,
// Reference to variation (e.g., specific model like "Iron Sword")
pub variation_ref: u256,
}

#[derive(Drop, Copy, Serde, Default)]
pub struct GearProperties {
asset_id: u256,
Expand Down Expand Up @@ -166,6 +182,52 @@ pub impl GearImpl of GearTrait {
}
}

// Implementation of GearDetails with validation and default behavior
#[generate_trait]
pub impl GearDetailsImpl of GearDetailsTrait {
// Creates a new GearDetails instance with validation
fn new(
gear_type: GearType,
min_xp_needed: u256,
base_damage: u64,
max_upgrade_level: u64,
total_count: u64,
variation_ref: u256,
) -> GearDetails {
// Validate inputs
assert(gear_type != GearType::None, 'Gear type cannot be None');
assert(min_xp_needed >= 0, 'Min XP cannot be negative');
assert(base_damage <= 1000, 'Base damage exceeds max (1000)');
assert(max_upgrade_level > 0, 'Max upgrade level must be > 0');
assert(total_count > 0, 'Total count must be > 0');

GearDetails {
gear_type, min_xp_needed, base_damage, max_upgrade_level, total_count, variation_ref,
}
}

// Validates the GearDetails instance
fn validate(self: @GearDetails) -> bool {
*self.gear_type != GearType::None
&& *self.min_xp_needed >= 0
&& *self.base_damage <= 1000
&& *self.max_upgrade_level > 0
&& *self.total_count > 0
}

// Creates a default GearDetails for testing or fallback
fn default() -> GearDetails {
GearDetails {
gear_type: GearType::Weapon,
min_xp_needed: 5,
base_damage: 10,
max_upgrade_level: 1,
total_count: 1,
variation_ref: 0,
}
}
}

// Implementation of conversion from GearType to felt252
impl GearTypeIntoFelt252 of Into<GearType, felt252> {
fn into(self: GearType) -> felt252 {
Expand Down Expand Up @@ -425,3 +487,57 @@ impl OptionArrayTupleImpl of Clone<Option<Array<(u256, u256)>>> {
}
}
}

// Tests for GearDetails struct
#[cfg(test)]
mod tests {
use super::{GearDetails, GearDetailsImpl, GearType};

#[test]
fn test_valid_gear_details() {
let gear = GearDetailsImpl::new(GearType::Sword, 30, 50, 10, 1, 1);
assert!(gear.validate(), "Valid gear should pass validation");
assert(gear.gear_type == GearType::Sword, 'Gear type mismatch');
assert(gear.min_xp_needed == 30, 'Min XP mismatch');
assert(gear.base_damage == 50, 'Base damage mismatch');
assert(gear.max_upgrade_level == 10, 'Max upgrade level mismatch');
assert(gear.total_count == 1, 'Total count mismatch');
assert(gear.variation_ref == 1, 'Variation ref mismatch');
}

#[test]
#[should_panic(expected: ('Gear type cannot be None',))]
fn test_invalid_gear_type() {
GearDetailsImpl::new(GearType::None, 5, 10, 1, 1, 0);
}

#[test]
#[should_panic(expected: ('Base damage exceeds max (1000)',))]
fn test_invalid_base_damage() {
GearDetailsImpl::new(GearType::Weapon, 5, 1001, 1, 1, 0);
}

#[test]
#[should_panic(expected: ('Max upgrade level must be > 0',))]
fn test_invalid_max_upgrade_level() {
GearDetailsImpl::new(GearType::Weapon, 5, 10, 0, 1, 0);
}

#[test]
#[should_panic(expected: ('Total count must be > 0',))]
fn test_invalid_total_count() {
GearDetailsImpl::new(GearType::Weapon, 5, 10, 1, 0, 0);
}

#[test]
fn test_default_gear_details() {
let gear = GearDetailsImpl::default();
assert(gear.validate(), 'Default gear should be valid');
assert(gear.gear_type == GearType::Weapon, 'Default gear type mismatch');
assert(gear.min_xp_needed == 5, 'Default min XP mismatch');
assert(gear.base_damage == 10, 'Default base damage mismatch');
assert!(gear.max_upgrade_level == 1, "Default max upgrade level mismatch");
assert(gear.total_count == 1, 'Default total count mismatch');
assert(gear.variation_ref == 0, 'Default variation ref mismatch');
}
}
44 changes: 32 additions & 12 deletions src/systems/core.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
///
/// Spawn tournamemnts and side quests here, if necessary.

use coa::models::gear::Gear;
use coa::models::gear::{Gear, GearDetails};
#[starknet::interface]
pub trait ICore<TContractState> {
fn spawn_items(ref self: TContractState, amount: u256);
fn spawn_items(ref self: TContractState, gear_details: Array<GearDetails>);
// move to market only items that have been spawned.
// if caller is admin, check spawned items and relocate
// if caller is player,
Expand Down Expand Up @@ -74,8 +74,8 @@ pub mod CoreActions {

#[abi(embed_v0)]
pub impl CoreActionsImpl of super::ICore<ContractState> {
//@ryzen-xp
fn spawn_items(ref self: ContractState, mut amount: u256) {
//@ryzen-xp, @truthixify
fn spawn_items(ref self: ContractState, gear_details: Array<GearDetails>) {
let caller = get_caller_address();
let mut world = self.world_default();
let contract: Contract = world.read_model(COA_CONTRACTS);
Expand All @@ -86,23 +86,43 @@ pub mod CoreActions {
};

let mut items = array![];
let mut i = 0;

while amount != 0 {
let mut gear: Gear = self.random_gear_generator();

assert(!gear.spawned, 'Gear_already_spawned');
while i < gear_details.len() {
let details = *gear_details.at(i);
assert(details.validate(), 'Invalid gear details');
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Confirm validation enforces safe mint bounds (non-zero, sane caps).

Ensure details.validate() guarantees:

  • total_count > 0 and within ERC1155 mint limits,
  • max_upgrade_level and min_xp_needed within expected ranges,
  • variation_ref and base_damage valid for gear_type.

Run:

Also applies to: 117-119


🏁 Script executed:

#!/bin/bash
# Inspect GearDetails.validate implementation and invariants
rg -n -C3 'impl\s+.*GearDetails' src models helpers
rg -n -C3 '\bfn\s+validate\s*\(' src models helpers

Length of output: 1006


Validate must enforce total_count, max_upgrade_level, and variation_ref constraints
validate() currently only checks

  • gear_type ≠ None
  • min_xp_needed ≥ 0
  • base_damage ≤ 1000

Add checks for:

  • total_count > 0 and ≤ ERC1155_MAX_MINT
  • max_upgrade_level within allowed range
  • variation_ref valid for the given gear_type

File: src/models/gear.cairo, fn validate (around lines 210–213)


let item_id: u256 = self.generate_incremental_ids(details.gear_type.into());
let item_type: felt252 = details.gear_type.into();
Comment on lines +95 to +96
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify GearType → u256 mapping suits ID partitioning.

generate_incremental_ids groups by item_id.high; confirm Into for GearType sets the gear type in the high 128 bits (low = 0), else IDs won’t partition per type.

Run:


🏁 Script executed:

#!/bin/bash
# Locate GearType->u256 conversion
rg -n -C3 'impl\s+(Into|From)<\s*u256\s*>\s+for\s+GearType' src models helpers
rg -n -C3 'fn\s+into\(\)\s*->\s*u256' src models helpers

Length of output: 189


#!/bin/bash
# 1. List all Cairo files containing GearType
rg -l 'GearType' -g '*.cairo'

# 2. For each of those files, search for any Into/From<u256> impls or into() calls
for f in $(rg -l 'GearType' -g '*.cairo'); do
  echo "=== $f ==="
  rg -n -C3 'impl\s+(Into|From)<\s*u256\s*>\s+for\s+GearType' "$f"
  rg -n -C3 'into\(' "$f" | grep -i GearType || true
done

🏁 Script executed:

#!/bin/bash
# 1. Locate GearType definition
rg -l 'enum GearType' -g '*.cairo'
rg -n -C3 'enum GearType' -g '*.cairo'

# 2. Search for any explicit impl converting GearType to u256
rg -n -C3 'impl\s+(Into|From)<\s*u256\s*>\s+for\s+GearType' -g '*.cairo'

# 3. Inspect generate_incremental_ids to verify how high/low bits are used
rg -n -C3 'func generate_incremental_ids' -g '*.cairo'

Length of output: 347


Map GearType into high 128 bits for proper ID partitioning. The call details.gear_type.into() converts the enum to a u256 with its numeric value in the low 128 bits (high=0), but generate_incremental_ids groups IDs by the high 128 bits—so all items end up in the same partition. In src/systems/core.cairo (lines 95–96), replace the direct into() call with an explicit high-bit assignment, for example:

let type_prefix: u256 = u256{ high: details.gear_type.into(), low: 0 };
let item_id: u256 = self.generate_incremental_ids(type_prefix);
🤖 Prompt for AI Agents
In src/systems/core.cairo around lines 95–96, the call details.gear_type.into()
is being converted into the low 128 bits, so generate_incremental_ids sees all
zeros in the high 128 bits and groups all items together; update the code to
construct a u256 with the gear type placed in the high 128 bits and low set to
0, then pass that u256 as the prefix to generate_incremental_ids (i.e., build a
u256 with high = details.gear_type.into() and low = 0 before calling
generate_incremental_ids).


let mut gear = Gear {
id: item_id,
item_type,
asset_id: item_id,
variation_ref: details.variation_ref,
total_count: details.total_count,
in_action: false,
upgrade_level: 0,
owner: contract_address_const::<0>(),
max_upgrade_level: details.max_upgrade_level,
min_xp_needed: details.min_xp_needed,
spawned: false,
};

assert(!gear.spawned, 'Gear already spawned');
gear.spawned = true;
gear.owner = contract_address_const::<0>();
world.write_model(@gear);

items.append(gear.id);
amount -= 1;
// mint to warehouse
erc1155_dispatcher.mint(contract.warehouse, gear.id, 1, array![].span());
erc1155_dispatcher
.mint(contract.warehouse, gear.id, details.total_count.into(), array![].span());
i += 1;
};

let event = GearSpawned { admin: caller, items };
world.emit_event(@event);
}

// move to market only items that have been spawned.
// if caller is admin, check spawned items and relocate
// if caller is player,
Expand Down
Loading