Skip to content

Commit

Permalink
adds packing overflow protection for adventurer (#388)
Browse files Browse the repository at this point in the history
  • Loading branch information
loothero authored Oct 7, 2023
1 parent e10959b commit f6a9282
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 70 deletions.
244 changes: 229 additions & 15 deletions contracts/adventurer/src/adventurer.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ use super::{
adventurer_constants::{
STARTING_GOLD, StatisticIndex, POTION_PRICE, STARTING_HEALTH, CHARISMA_POTION_DISCOUNT,
MINIMUM_ITEM_PRICE, MINIMUM_POTION_PRICE, HEALTH_INCREASE_PER_VITALITY, MAX_GOLD,
MAX_STAT_VALUE, MAX_STAT_UPGRADES, MAX_XP, MAX_ADVENTURER_BLOCKS, ITEM_MAX_GREATNESS,
ITEM_MAX_XP, MAX_ADVENTURER_HEALTH, CHARISMA_ITEM_DISCOUNT, ClassStatBoosts,
MAX_STAT_VALUE, MAX_STAT_UPGRADE_POINTS, MAX_XP, MAX_ADVENTURER_BLOCKS,
ITEM_MAX_GREATNESS, ITEM_MAX_XP, MAX_ADVENTURER_HEALTH, CHARISMA_ITEM_DISCOUNT,
MAX_BLOCK_COUNT, STAT_UPGRADE_POINTS_PER_LEVEL, NECKLACE_G20_BONUS_STATS,
SILVER_RING_G20_LUCK_BONUS, BEAST_SPECIAL_NAME_LEVEL_UNLOCK, U128_MAX,
JEWELRY_BONUS_BEAST_GOLD_PERCENT, JEWELRY_BONUS_CRITICAL_HIT_PERCENT_PER_GREATNESS,
JEWELRY_BONUS_NAME_MATCH_PERCENT_PER_GREATNESS, NECKLACE_ARMOR_BONUS,
MINIMUM_DAMAGE_FROM_BEASTS, OBSTACLE_CRITICAL_HIT_CHANCE, BEAST_CRITICAL_HIT_CHANCE,
SILVER_RING_LUCK_BONUS_PER_GREATNESS, MINIMUM_DAMAGE_FROM_OBSTACLES,
MINIMUM_DAMAGE_TO_BEASTS, MAX_ACTIONS_PER_BLOCK
MINIMUM_DAMAGE_TO_BEASTS, MAX_ACTIONS_PER_BLOCK, MAX_PACKABLE_ITEM_XP,
MAX_PACKABLE_BEAST_HEALTH, MAX_LAST_ACTION_BLOCK
},
discovery_constants::DiscoveryEnums::{ExploreResult, DiscoveryType}
}
Expand Down Expand Up @@ -60,6 +61,8 @@ struct Adventurer {

impl AdventurerPacking of StorePacking<Adventurer, felt252> {
fn pack(value: Adventurer) -> felt252 {
value.prepack_overflow_assertions();

(value.last_action_block.into()
+ value.health.into() * TWO_POW_9
+ value.xp.into() * TWO_POW_18
Expand Down Expand Up @@ -501,25 +504,25 @@ impl ImplAdventurer of IAdventurer {
}

// @notice Grants stat upgrades to the Adventurer.
// @dev The function will add the specified value to the stat_points_available up to the maximum limit of MAX_STAT_UPGRADES.
// @dev The function will add the specified value to the stat_points_available up to the maximum limit of MAX_STAT_UPGRADE_POINTS.
// @param value The amount of stat points to be added to the Adventurer.
#[inline(always)]
fn increase_stat_points_available(ref self: Adventurer, amount: u8) {
// check for u8 overflow
if (u8_overflowing_add(self.stat_points_available, amount).is_ok()) {
// if overflow is ok
// check if added amount is less than or equal to max upgrade points
if (self.stat_points_available + amount <= MAX_STAT_UPGRADES) {
if (self.stat_points_available + amount <= MAX_STAT_UPGRADE_POINTS) {
// if it is, add upgrade points to adventurer and return
self.stat_points_available += amount;
return;
}
}

// fall through is to return MAX_STAT_UPGRADES
// fall through is to return MAX_STAT_UPGRADE_POINTS
// this will happen either in a u8 overflow case
// or if the upgrade points being added exceeds max upgrade points
self.stat_points_available = MAX_STAT_UPGRADES
self.stat_points_available = MAX_STAT_UPGRADE_POINTS
}

// @notice Increase the Adventurer's strength stat.
Expand Down Expand Up @@ -1707,6 +1710,31 @@ impl ImplAdventurer of IAdventurer {
fn reset_actions_per_block(ref self: Adventurer) {
self.actions_per_block = 1;
}

#[inline(always)]
fn prepack_overflow_assertions(self: Adventurer) {
assert(self.health <= MAX_ADVENTURER_HEALTH, 'health overflow');
assert(self.xp <= MAX_XP, 'xp overflow');
assert(self.gold <= MAX_GOLD, 'gold overflow');
assert(self.stats.strength <= MAX_STAT_VALUE, 'strength overflow');
assert(self.stats.dexterity <= MAX_STAT_VALUE, 'dexterity overflow');
assert(self.stats.vitality <= MAX_STAT_VALUE, 'vitality overflow');
assert(self.stats.charisma <= MAX_STAT_VALUE, 'charisma overflow');
assert(self.stats.intelligence <= MAX_STAT_VALUE, 'intelligence overflow');
assert(self.stats.wisdom <= MAX_STAT_VALUE, 'wisdom overflow');
assert(self.weapon.xp <= MAX_PACKABLE_ITEM_XP, 'weapon xp overflow');
assert(self.chest.xp <= MAX_PACKABLE_ITEM_XP, 'chest xp overflow');
assert(self.head.xp <= MAX_PACKABLE_ITEM_XP, 'head xp overflow');
assert(self.waist.xp <= MAX_PACKABLE_ITEM_XP, 'waist xp overflow');
assert(self.foot.xp <= MAX_PACKABLE_ITEM_XP, 'foot xp overflow');
assert(self.hand.xp <= MAX_PACKABLE_ITEM_XP, 'hand xp overflow');
assert(self.neck.xp <= MAX_PACKABLE_ITEM_XP, 'neck xp overflow');
assert(self.ring.xp <= MAX_PACKABLE_ITEM_XP, 'ring xp overflow');
assert(self.beast_health <= MAX_PACKABLE_BEAST_HEALTH, 'beast health overflow');
assert(self.stat_points_available <= MAX_STAT_UPGRADE_POINTS, 'stat points avail overflow');
assert(self.last_action_block <= MAX_LAST_ACTION_BLOCK, 'last action block overflow');
assert(self.actions_per_block <= MAX_ACTIONS_PER_BLOCK, 'actions per block overflow');
}
}

const TWO_POW_3: u256 = 0x8;
Expand Down Expand Up @@ -1753,11 +1781,12 @@ mod tests {
adventurer_constants::{
STARTING_GOLD, StatisticIndex, POTION_PRICE, STARTING_HEALTH,
CHARISMA_POTION_DISCOUNT, MINIMUM_ITEM_PRICE, MINIMUM_POTION_PRICE,
HEALTH_INCREASE_PER_VITALITY, MAX_GOLD, MAX_STAT_VALUE, MAX_STAT_UPGRADES, MAX_XP,
MAX_ADVENTURER_BLOCKS, ITEM_MAX_GREATNESS, ITEM_MAX_XP, MAX_ADVENTURER_HEALTH,
CHARISMA_ITEM_DISCOUNT, ClassStatBoosts, MAX_BLOCK_COUNT,
HEALTH_INCREASE_PER_VITALITY, MAX_GOLD, MAX_STAT_VALUE, MAX_STAT_UPGRADE_POINTS,
MAX_XP, MAX_ADVENTURER_BLOCKS, ITEM_MAX_GREATNESS, ITEM_MAX_XP,
MAX_ADVENTURER_HEALTH, CHARISMA_ITEM_DISCOUNT, MAX_BLOCK_COUNT,
SILVER_RING_G20_LUCK_BONUS, JEWELRY_BONUS_NAME_MATCH_PERCENT_PER_GREATNESS,
NECKLACE_ARMOR_BONUS, SILVER_RING_LUCK_BONUS_PER_GREATNESS
NECKLACE_ARMOR_BONUS, SILVER_RING_LUCK_BONUS_PER_GREATNESS, MAX_ACTIONS_PER_BLOCK,
MAX_PACKABLE_ITEM_XP, MAX_PACKABLE_BEAST_HEALTH, MAX_LAST_ACTION_BLOCK
},
discovery_constants::DiscoveryEnums::{ExploreResult, DiscoveryType}
}
Expand Down Expand Up @@ -2832,6 +2861,186 @@ mod tests {
);
}

#[test]
#[should_panic(expected: ('health overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_health() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.health = MAX_ADVENTURER_HEALTH + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('gold overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_gold() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.gold = MAX_GOLD + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('xp overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_xp() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.xp = MAX_XP + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('strength overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_strength() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.stats.strength = MAX_STAT_VALUE + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('dexterity overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_dexterity() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.stats.dexterity = MAX_STAT_VALUE + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('vitality overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_vitality() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.stats.vitality = MAX_STAT_VALUE + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('intelligence overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_intelligence() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.stats.intelligence = MAX_STAT_VALUE + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('wisdom overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_wisdom() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.stats.wisdom = MAX_STAT_VALUE + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('weapon xp overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_weapon_xp() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.weapon.xp = MAX_PACKABLE_ITEM_XP + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('chest xp overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_chest_xp() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.chest.xp = MAX_PACKABLE_ITEM_XP + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('head xp overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_head_xp() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.head.xp = MAX_PACKABLE_ITEM_XP + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('waist xp overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_waist_xp() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.waist.xp = MAX_PACKABLE_ITEM_XP + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('foot xp overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_foot_xp() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.foot.xp = MAX_PACKABLE_ITEM_XP + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('hand xp overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_hand_xp() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.hand.xp = MAX_PACKABLE_ITEM_XP + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('neck xp overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_neck_xp() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.neck.xp = MAX_PACKABLE_ITEM_XP + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('ring xp overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_ring_xp() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.ring.xp = MAX_PACKABLE_ITEM_XP + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('beast health overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_beast_health() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.beast_health = MAX_PACKABLE_BEAST_HEALTH + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('stat points avail overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_stat_points_available() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.stat_points_available = MAX_STAT_UPGRADE_POINTS + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('actions per block overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_actions_per_block() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.actions_per_block = MAX_ACTIONS_PER_BLOCK + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[should_panic(expected: ('last action block overflow',))]
#[available_gas(3000000)]
fn test_pack_protection_overflow_last_action_block() {
let mut adventurer = ImplAdventurer::new(12, 0, 0, 0);
adventurer.last_action_block = MAX_LAST_ACTION_BLOCK + 1;
AdventurerPacking::pack(adventurer);
}

#[test]
#[available_gas(2000000)]
fn test_new_adventurer() {
Expand Down Expand Up @@ -2976,19 +3185,24 @@ mod tests {
);

// max stat upgrade value case
adventurer.increase_stat_points_available(MAX_STAT_UPGRADES);
assert(adventurer.stat_points_available == MAX_STAT_UPGRADES, 'stat points should be max');
adventurer.increase_stat_points_available(MAX_STAT_UPGRADE_POINTS);
assert(
adventurer.stat_points_available == MAX_STAT_UPGRADE_POINTS, 'stat points should be max'
);

// pack and unpack at max value to ensure our max values are correct for packing
let unpacked: Adventurer = AdventurerPacking::unpack(AdventurerPacking::pack(adventurer));
assert(
unpacked.stat_points_available == MAX_STAT_UPGRADES, 'stat point should still be max'
unpacked.stat_points_available == MAX_STAT_UPGRADE_POINTS,
'stat point should still be max'
);

// extreme/overflow case
adventurer.stat_points_available = 255;
adventurer.increase_stat_points_available(255);
assert(adventurer.stat_points_available == MAX_STAT_UPGRADES, 'stat points should be max');
assert(
adventurer.stat_points_available == MAX_STAT_UPGRADE_POINTS, 'stat points should be max'
);
}

#[test]
Expand Down
8 changes: 4 additions & 4 deletions contracts/adventurer/src/adventurer_utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use core::{
use super::{
constants::{
adventurer_constants::{
MAX_STAT_VALUE, U128_MAX, ClassStatBoosts, STARTING_HEALTH,
HEALTH_INCREASE_PER_VITALITY, MAX_ADVENTURER_HEALTH
MAX_STAT_VALUE, U128_MAX, STARTING_HEALTH, HEALTH_INCREASE_PER_VITALITY,
MAX_ADVENTURER_HEALTH
},
discovery_constants::DiscoveryEnums::{ExploreResult, DiscoveryType}
},
Expand Down Expand Up @@ -317,8 +317,8 @@ mod tests {
use survivor::{
constants::{
adventurer_constants::{
MAX_STAT_VALUE, U128_MAX, ClassStatBoosts, STARTING_HEALTH,
HEALTH_INCREASE_PER_VITALITY, MAX_ADVENTURER_HEALTH
MAX_STAT_VALUE, U128_MAX, STARTING_HEALTH, HEALTH_INCREASE_PER_VITALITY,
MAX_ADVENTURER_HEALTH
},
discovery_constants::DiscoveryEnums::{ExploreResult, DiscoveryType}
},
Expand Down
Loading

1 comment on commit f6a9282

@vercel
Copy link

@vercel vercel bot commented on f6a9282 Oct 7, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.