diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..1399bcacf --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,21 @@ +# Use Debian Bookworm as a base image +FROM debian:bookworm-slim + +# Avoid warnings by switching to noninteractive +ENV DEBIAN_FRONTEND=noninteractive + +# Update the package list and install minimal utilities +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + curl \ + git \ + ca-certificates \ + unzip \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +RUN curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -s -- -v 2.5.4 +RUN curl https://get.starkli.sh | bash +RUN /root/.starkli/bin/starkliup +# Specify the command to run on container start +CMD ["bash"] \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f1bdc424f..bf19ad023 100755 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,10 +1,13 @@ { "name": "Rust", - "image": "mcr.microsoft.com/devcontainers/rust:0-1-bullseye", + "build": { + "dockerfile": "Dockerfile" + }, "customizations": { "vscode": { "extensions": [ - "starkware.cairo1" + "starkware.cairo1", + "github.copilot" ] } }, @@ -14,5 +17,6 @@ "CAIRO_COMPILER_DIR":"~/.cairo/target/release/", "CAIRO_COMPILER_ARGS":"--add-pythonic-hints" }, - "postCreateCommand":"curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -s -- -v 0.7.0 && curl https://pyenv.run | bash && echo 'export PATH=\"$HOME/.pyenv/bin:$PATH\"' >> ~/.bashrc && echo 'eval \"$(pyenv init -)\"' >> ~/.bashrc && echo 'eval \"$(pyenv virtualenv-init -)\"' >> ~/.bashrc && $HOME/.pyenv/bin/pyenv install 3.9.0 && $HOME/.pyenv/bin/pyenv virtualenv 3.9.0 cairo_venv && echo 'pyenv activate cairo_venv' >> ~/.bashrc && echo 'pip install cairo-lang 2> /dev/null' >> ~/.bashrc" + "postCreateCommand": "git config --global core.editor \"code --wait\" && bash scripts/starkli_setup.sh", + "postStartCommand": "cd contracts && scarb build && scarb test" } \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 67070cfed..d09d1c45d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,92 +4,32 @@ on: push: branches: - main - pull_request: + pull_request: env: - SCARB_VERSION: 0.7.0 + SCARB_VERSION: 2.5.4 jobs: - build-contracts: - name: Build Adventurer + build: + name: Build Contracts runs-on: ubuntu-latest + strategy: + matrix: + contract: [ + adventurer, + game, + loot, + market, + obstacles, + combat, + game_entropy, + game_snapshot, + ] steps: - uses: actions/checkout@v3 - name: Setup Scarb - run: | - curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -s -- -v ${{ env.SCARB_VERSION }} + run: curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -s -- -v ${{ env.SCARB_VERSION }} - - name: Scarb build - run: | - cd contracts/adventurer && scarb build && scarb cairo-test - - build-game: - name: Build Game - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Scarb - run: | - curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -s -- -v ${{ env.SCARB_VERSION }} - - - name: Scarb build - run: | - cd contracts/game && scarb build && scarb cairo-test - - build-loot: - name: Build Loot - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Scarb - run: | - curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -s -- -v ${{ env.SCARB_VERSION }} - - - name: Scarb build - run: | - cd contracts/loot && scarb build && scarb cairo-test - - build-market: - name: Build Market - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Scarb - run: | - curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -s -- -v ${{ env.SCARB_VERSION }} - - - name: Scarb build - run: | - cd contracts/market && scarb build && scarb cairo-test - - build-obstacles: - name: Build Obstacles - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Scarb - run: | - curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -s -- -v ${{ env.SCARB_VERSION }} - - - name: Scarb build - run: | - cd contracts/obstacles && scarb build && scarb cairo-test - - build-combat: - name: Build Combat - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Scarb - run: | - curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | bash -s -- -v ${{ env.SCARB_VERSION }} - - - name: Scarb build - run: | - cd contracts/combat && scarb build && scarb cairo-test + - name: Scarb build ${{ matrix.contract }} + run: cd contracts/${{ matrix.contract }} && scarb build && scarb cairo-test diff --git a/.gitignore b/.gitignore index 8771d4bfc..8e9ca9850 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ contracts/game_entropy/target/CACHEDIR.TAG contracts/game_entropy/target/dev/game_entropy.sierra scripts/stress_test.py +.env diff --git a/README.md b/README.md index f86b6d3a7..130a61a17 100644 --- a/README.md +++ b/README.md @@ -148,127 +148,70 @@ Loot Survivor is an onchain game, designed to be immutable and permanently hoste ### Deploying -#### Set up env - -Follow instructions here: `https://docs.starknet.io/documentation/getting_started/environment_setup/` - - -``` -source ~/cairo_venv/bin/activate -``` - -```bash -export STARKNET_NETWORK=alpha-goerli -export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount -export CAIRO_COMPILER_DIR=~/.cairo/target/release/ -export CAIRO_COMPILER_ARGS=--add-pythonic-hints - -# you will have an account from the Starknet ENV setup -export ACCOUNT_NAME=INSERT_YOUR_ACCOUNT_NAME_HERE -export ADVENTURER_ID=INSERT_YOUR_ADVENTURER_ID_HERE -export STRENGTH=0 -export DEXTERITY=1 -export VITALITY=2 -export INTELLIGENCE=4 -export CHARISMA=5 - -export LORDS_ADDRESS=0x059dac5df32cbce17b081399e97d90be5fba726f97f00638f838613d088e5a47 -export DAO_ADDRESS=0x020b96923a9e60f63a1829d440a03cf680768cadbc8fe737f71380258817d85b - -# nav to dir -cd contracts/game - -# build -scarb build - -# declare -starknet declare --contract target/dev/game_Game.sierra.json --account $ACCOUNT_NAME - -# deploy -# will be in the output of the previous command -starknet deploy --class_hash 0x2958304935054101c0aeab16cf6507adda1c98b4d977af40d59c2ae75f05767 --max_fee 100000000000000000 --input $LORDS_ADDRESS $DAO_ADDRESS --account $ACCOUNT_NAME -``` +#### Setup Starkli Account ```bash -# set contract address -export CONTRACT_ADDRESS=0x06ee32da9f22c736c4ef049719c0021380c302e5d449fbc8acf97489e16a9d05 +bash scripts/starkli_setup.sh ``` +#### Deploy Contract ```bash -starknet invoke --function mint --address $LORDS_ADDRESS --input 0x1feb9c05d31b70a1506decf52a809d57493bfcd5cc85d6a3e9fd54a12d64389 1000000000000000000000 0 --max_fee 10000000000000000 --account $ACCOUNT_NAME - -starknet invoke --function approve --address $LORDS_ADDRESS --input $CONTRACT_ADDRESS 1000000000000000000000 0 --max_fee 10000000000000000 --account $ACCOUNT_NAME +bash scripts/deploy.sh.sh ``` -### Game Actions +### Play -#### Start +#### Mint $lords and approve LS to spend ```bash -starknet invoke --function start --address $CONTRACT_ADDRESS --input 0x020b96923a9e60f63a1829d440a03cf680768cadbc8fe737f71380258817d85b 12 123 0 0 0 --max_fee 10000000000000000 --account $ACCOUNT_NAME +source "/workspaces/loot-survivor/.env" +starkli invoke $LORDS_ADDRESS mint 0x1feb9c05d31b70a1506decf52a809d57493bfcd5cc85d6a3e9fd54a12d64389 1000000000000000000000 0 --account $STARKNET_ACCOUNT --private-key $PRIVATE_KEY +starkli invoke $LORDS_ADDRESS approve $CONTRACT_ADDRESS 1000000000000000000000 0 --account $STARKNET_ACCOUNT --private-key $PRIVATE_KEY ``` -#### Explore +#### Start New Game ```bash -starknet invoke --function explore --address $CONTRACT_ADDRESS --input $ADVENTURER_ID 0 --max_fee 10000000000000000 --account $ACCOUNT_NAME +starkli invoke $CONTRACT_ADDRESS new_game 0x020b96923a9e60f63a1829d440a03cf680768cadbc8fe737f71380258817d85b 12 123 0 0 0 --account $STARKNET_ACCOUNT --private-key $PRIVATE_KEY ``` -#### Attack +#### Explore ```bash -starknet invoke --function attack --address $CONTRACT_ADDRESS --input $ADVENTURER_ID 0 --max_fee 10000000000000000 --account $ACCOUNT_NAME +starkli invoke $CONTRACT_ADDRESS explore $ADVENTURER_ID 0 --account $STARKNET_ACCOUNT --private-key $PRIVATE_KEY ``` -#### Flee +#### Attack Starter Beast ```bash -starknet invoke --function flee --address $CONTRACT_ADDRESS --input $ADVENTURER_ID 0 --max_fee 10000000000000000 --account $ACCOUNT_NAME +starkli invoke $CONTRACT_ADDRESS attack $ADVENTURER_ID 0 --account $STARKNET_ACCOUNT --private-key $PRIVATE_KEY ``` -#### Upgrade Stat (Charisma x 1) +#### Upgrade Adventurer ```bash -starknet invoke --function upgrade_stat --address $CONTRACT_ADDRESS --input $ADVENTURER_ID 0 $CHARISMA 1 --max_fee 10000000000000000 --account $ACCOUNT_NAME +starkli invoke $CONTRACT_ADDRESS upgrade $ADVENTURER_ID 0 $CHARISMA 1 --account $STARKNET_ACCOUNT --private-key $PRIVATE_KEY ``` -### Checking On Your Adventurer +### View Adventurer Details -##### Get full adventurer state +##### Adventurer State ```bash -starknet call --function get_adventurer --address $CONTRACT_ADDRESS --input $ADVENTURER_ID 0 --account $ACCOUNT_NAME +starkli call --watch $CONTRACT_ADDRESS get_adventurer $ADVENTURER_ID 0 --account $STARKNET_ACCOUNT --private-key $PRIVATE_KEY ``` -##### Get adventurer health +##### Get Adventurer Health ```bash -starknet call --function get_health --address $CONTRACT_ADDRESS --input $ADVENTURER_ID 0 --account $ACCOUNT_NAME +starkli call --watch $CONTRACT_ADDRESS get_health $ADVENTURER_ID 0 --account $STARKNET_ACCOUNT --private-key $PRIVATE_KEY ``` -##### Get adventurer gold +##### Get Adventurer Gold ```bash -starknet call --function get_gold --address $CONTRACT_ADDRESS --input $ADVENTURER_ID 0 --account $ACCOUNT_NAME +starkli call --watch $CONTRACT_ADDRESS get_gold $ADVENTURER_ID 0 --account $STARKNET_ACCOUNT --private-key $PRIVATE_KEY ``` -##### Get adventurer xp +##### Get Adventurer XP ```bash -starknet call --function get_xp --address $CONTRACT_ADDRESS --input $ADVENTURER_ID 0 --account $ACCOUNT_NAME +starkli call --watch $CONTRACT_ADDRESS get_xp $ADVENTURER_ID 0 --account $STARKNET_ACCOUNT --private-key $PRIVATE_KEY ``` -##### Get upgradable stat points +##### Get number of stat upgrades available ```bash -starknet call --function get_stat_upgrades_available --address $CONTRACT_ADDRESS --input $ADVENTURER_ID 0 --account $ACCOUNT_NAME -``` - -##### Get base charisma stat (doesn't include boost from items) -```bash -starknet call --function get_base_charisma --address $CONTRACT_ADDRESS --input $ADVENTURER_ID 0 --account $ACCOUNT_NAME -``` - -##### Get charisma stat including item boosts -```bash -starknet call --function get_charisma --address $CONTRACT_ADDRESS --input $ADVENTURER_ID 0 --account $ACCOUNT_NAME -``` - - -# Starkli Deploy - - -starkli declare /contracts/game/target/dev/game_Game.sierra.json --account ./account --keystore ./keys --max-fee 0.01 - -starkli deploy 0x00cccbd15bf27792e7635bd89da237de68b13d29ec01b5cae1da786b276be8a4 $LORDS_ADDRESS $DAO_ADDRESS 0x06fe9215a0f193431f30043e612d921b62331946529ebf5f258949a4b34aa799 --account ./account --keystore ./keys \ No newline at end of file +starkli call --watch $CONTRACT_ADDRESS get_stat_upgrades_available $ADVENTURER_ID 0 --account $STARKNET_ACCOUNT --private-key $PRIVATE_KEY +``` \ No newline at end of file diff --git a/Scarb.lock b/Scarb.lock new file mode 100644 index 000000000..5f8efa234 --- /dev/null +++ b/Scarb.lock @@ -0,0 +1,98 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "arcade_account" +version = "0.1.0" +source = "git+https://github.com/BibliothecaDAO/arcade-account?branch=next#cb3a3c9ca4e56c0fcc89ec5e273b48e1f9050b5f" +dependencies = [ + "openzeppelin", +] + +[[package]] +name = "beasts" +version = "0.1.0" +dependencies = [ + "combat", +] + +[[package]] +name = "combat" +version = "0.1.0" + +[[package]] +name = "game" +version = "0.1.0" +dependencies = [ + "arcade_account", + "game_entropy", + "game_snapshot", + "golden_token", + "lootitems", + "market", + "obstacles", + "openzeppelin", + "survivor", +] + +[[package]] +name = "game_entropy" +version = "0.1.0" + +[[package]] +name = "game_snapshot" +version = "0.1.0" + +[[package]] +name = "golden_token" +version = "0.1.0" +source = "git+https://github.com/BibliothecaDAO/golden-token?branch=next#a9f644453fd097bfe68170f39bed1c8a5d0af73e" +dependencies = [ + "arcade_account", + "openzeppelin", +] + +[[package]] +name = "lootitems" +version = "0.1.0" +dependencies = [ + "combat", +] + +[[package]] +name = "lords" +version = "0.1.0" +dependencies = [ + "openzeppelin", +] + +[[package]] +name = "market" +version = "0.1.0" +dependencies = [ + "combat", + "lootitems", +] + +[[package]] +name = "obstacles" +version = "0.1.0" +dependencies = [ + "combat", +] + +[[package]] +name = "openzeppelin" +version = "0.9.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.9.0#364db5b1aecc1335d2e65db887291d19aa28937d" + +[[package]] +name = "survivor" +version = "0.1.0" +dependencies = [ + "beasts", + "combat", + "lootitems", + "market", + "obstacles", +] diff --git a/Scarb.toml b/Scarb.toml new file mode 100644 index 000000000..26522d90b --- /dev/null +++ b/Scarb.toml @@ -0,0 +1,29 @@ +[workspace] +members = [ + "contracts/adventurer", + "contracts/beasts", + "contracts/combat", + "contracts/game", + "contracts/game_entropy", + "contracts/game_snapshot", + "contracts/loot", + "contracts/lords", + "contracts/market", + "contracts/obstacles", +] +name = "loot_survivor" +version = "0.1.0" +description = "Loot Survivor collection of libraries and contracts for the BibliothecaDAO ecosystem and beyond." +homepage = "https://github.com/BibliothecaDAO/loot-survivor" + +[workspace.dependencies] +starknet = ">=2.5.3" +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.9.0" } +golden_token = { git = "https://github.com/BibliothecaDAO/golden-token", branch = "next" } +arcade_account = { git = "https://github.com/BibliothecaDAO/arcade-account", branch = "next" } + +[workspace.tool.fmt] +sort-module-level-items = true + +[scripts] +all = "scarb build && scarb test" diff --git a/contracts/adventurer/Scarb.lock b/contracts/adventurer/Scarb.lock new file mode 100644 index 000000000..57f6a649b --- /dev/null +++ b/contracts/adventurer/Scarb.lock @@ -0,0 +1,46 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "beasts" +version = "0.1.0" +dependencies = [ + "combat", +] + +[[package]] +name = "combat" +version = "0.1.0" + +[[package]] +name = "lootitems" +version = "0.1.0" +dependencies = [ + "combat", +] + +[[package]] +name = "market" +version = "0.1.0" +dependencies = [ + "combat", + "lootitems", +] + +[[package]] +name = "obstacles" +version = "0.1.0" +dependencies = [ + "combat", +] + +[[package]] +name = "survivor" +version = "0.1.0" +dependencies = [ + "beasts", + "combat", + "lootitems", + "market", + "obstacles", +] diff --git a/contracts/adventurer/Scarb.toml b/contracts/adventurer/Scarb.toml index b245dd882..cf96271a0 100644 --- a/contracts/adventurer/Scarb.toml +++ b/contracts/adventurer/Scarb.toml @@ -1,6 +1,8 @@ [package] name = "survivor" version = "0.1.0" +description = "Adventurer Library" +homepage = "https://github.com/BibliothecaDAO/loot-survivor" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest diff --git a/contracts/adventurer/src/adventurer.cairo b/contracts/adventurer/src/adventurer.cairo index 2a6b0d725..a23df1299 100644 --- a/contracts/adventurer/src/adventurer.cairo +++ b/contracts/adventurer/src/adventurer.cairo @@ -22,7 +22,7 @@ use super::{ 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, MAX_PACKABLE_ITEM_XP, - MAX_PACKABLE_BEAST_HEALTH, MAX_LAST_ACTION_BLOCK + MAX_PACKABLE_BEAST_HEALTH, MAX_LAST_ACTION_BLOCK, MAX_STAT_VALUE_5_BITS }, discovery_constants::DiscoveryEnums::{ExploreResult, DiscoveryType} }, @@ -43,8 +43,8 @@ use beasts::{beast::{ImplBeast, Beast}, constants::BeastSettings}; struct Adventurer { last_action_block: u16, // 9 bits health: u16, // 9 bits - xp: u16, // 13 bits - stats: Stats, // 24 bits + xp: u16, // 14 bits + stats: Stats, // 26 bits gold: u16, // 9 bits weapon: ItemPrimitive, // 21 bits chest: ItemPrimitive, // 21 bits @@ -67,31 +67,32 @@ impl AdventurerPacking of StorePacking { (value.last_action_block.into() + value.health.into() * TWO_POW_9 + value.xp.into() * TWO_POW_18 - + StatsPacking::pack(value.stats).into() * TWO_POW_31 - + value.gold.into() * TWO_POW_55 - + ItemPrimitivePacking::pack(value.weapon).into() * TWO_POW_64 - + ItemPrimitivePacking::pack(value.chest).into() * TWO_POW_85 - + ItemPrimitivePacking::pack(value.head).into() * TWO_POW_106 - + ItemPrimitivePacking::pack(value.waist).into() * TWO_POW_127 - + ItemPrimitivePacking::pack(value.foot).into() * TWO_POW_148 - + ItemPrimitivePacking::pack(value.hand).into() * TWO_POW_169 - + ItemPrimitivePacking::pack(value.neck).into() * TWO_POW_190 - + ItemPrimitivePacking::pack(value.ring).into() * TWO_POW_211 - + value.beast_health.into() * TWO_POW_232 - + value.stat_points_available.into() * TWO_POW_241 - + value.actions_per_block.into() * TWO_POW_244) + + StatsPacking::pack(value.stats).into() * TWO_POW_32 + + value.gold.into() * TWO_POW_58 + + ItemPrimitivePacking::pack(value.weapon).into() * TWO_POW_67 + + ItemPrimitivePacking::pack(value.chest).into() * TWO_POW_88 + + ItemPrimitivePacking::pack(value.head).into() * TWO_POW_109 + + ItemPrimitivePacking::pack(value.waist).into() * TWO_POW_130 + + ItemPrimitivePacking::pack(value.foot).into() * TWO_POW_151 + + ItemPrimitivePacking::pack(value.hand).into() * TWO_POW_172 + + ItemPrimitivePacking::pack(value.neck).into() * TWO_POW_193 + + ItemPrimitivePacking::pack(value.ring).into() * TWO_POW_214 + + value.beast_health.into() * TWO_POW_235 + + value.stat_points_available.into() * TWO_POW_244 + + value.actions_per_block.into() * TWO_POW_247) .try_into() .unwrap() } + fn unpack(value: felt252) -> Adventurer { let packed = value.into(); let (packed, last_action_block) = integer::U256DivRem::div_rem( packed, TWO_POW_9.try_into().unwrap() ); let (packed, health) = integer::U256DivRem::div_rem(packed, TWO_POW_9.try_into().unwrap()); - let (packed, xp) = integer::U256DivRem::div_rem(packed, TWO_POW_13.try_into().unwrap()); - let (packed, stats) = integer::U256DivRem::div_rem(packed, TWO_POW_24.try_into().unwrap()); + let (packed, xp) = integer::U256DivRem::div_rem(packed, TWO_POW_14.try_into().unwrap()); + let (packed, stats) = integer::U256DivRem::div_rem(packed, TWO_POW_26.try_into().unwrap()); let (packed, gold) = integer::U256DivRem::div_rem(packed, TWO_POW_9.try_into().unwrap()); let (packed, weapon) = integer::U256DivRem::div_rem(packed, TWO_POW_21.try_into().unwrap()); let (packed, chest) = integer::U256DivRem::div_rem(packed, TWO_POW_21.try_into().unwrap()); @@ -128,7 +129,7 @@ impl AdventurerPacking of StorePacking { beast_health: beast_health.try_into().unwrap(), stat_points_available: stat_points_available.try_into().unwrap(), actions_per_block: actions_per_block.try_into().unwrap(), - mutated: false, + mutated: false, // This field is not packed/unpacked } } } @@ -938,7 +939,7 @@ impl ImplAdventurer of IAdventurer { hash_span.append(self.xp.into()); hash_span.append(adventurer_entropy); let poseidon = poseidon_hash_span(hash_span.span()); - let (d, r) = integer::U256DivRem::div_rem( + let (_d, r) = integer::U256DivRem::div_rem( poseidon.into(), u256_try_as_non_zero(U128_MAX.into()).unwrap() ); r.try_into().unwrap() @@ -1596,30 +1597,18 @@ impl ImplAdventurer of IAdventurer { fn jewelry_armor_bonus(self: ItemPrimitive, armor_type: Type, base_armor: u16) -> u16 { // qualify no bonus outcomes and return 0 match armor_type { - Type::None(()) => { + Type::None(()) => { return 0; }, + Type::Magic_or_Cloth(()) => { if (self.id != ItemId::Amulet) { return 0; - }, - Type::Magic_or_Cloth(()) => { - if (self.id != ItemId::Amulet) { - return 0; - } - }, - Type::Blade_or_Hide(()) => { - if (self.id != ItemId::Pendant) { - return 0; - } - }, - Type::Bludgeon_or_Metal(()) => { - if (self.id != ItemId::Necklace) { - return 0; - } - }, - Type::Necklace(()) => { + } }, + Type::Blade_or_Hide(()) => { if (self.id != ItemId::Pendant) { return 0; - }, - Type::Ring(()) => { + } }, + Type::Bludgeon_or_Metal(()) => { if (self.id != ItemId::Necklace) { return 0; - } + } }, + Type::Necklace(()) => { return 0; }, + Type::Ring(()) => { return 0; } } // if execution reaches here, the necklace provides a bonus for the armor type @@ -1681,9 +1670,9 @@ impl ImplAdventurer of IAdventurer { 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.strength <= MAX_STAT_VALUE_5_BITS, 'strength overflow'); assert(self.stats.dexterity <= MAX_STAT_VALUE, 'dexterity overflow'); - assert(self.stats.vitality <= MAX_STAT_VALUE, 'vitality overflow'); + assert(self.stats.vitality <= MAX_STAT_VALUE_5_BITS, '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'); @@ -1712,28 +1701,44 @@ impl ImplAdventurer of IAdventurer { hash_span.append(start_hash); poseidon_hash_span(hash_span.span()) } + + /// @title invalidate_adventurer + /// @notice This function invalidates an adventurer by setting its xp to 1 and gold to 0. + /// @dev This function directly modifies the state of the adventurer. + /// @param self The Adventurer struct instance to be invalidated. + #[inline(always)] + fn invalidate_game(ref self: Adventurer) { + self.health = 0; + self.xp = 1; + self.gold = 0; + } } -const TWO_POW_3: u256 = 0x8; -const TWO_POW_4: u256 = 0x10; -const TWO_POW_9: u256 = 0x200; -const TWO_POW_13: u256 = 0x2000; -const TWO_POW_18: u256 = 0x40000; -const TWO_POW_21: u256 = 0x200000; -const TWO_POW_24: u256 = 0x1000000; -const TWO_POW_31: u256 = 0x80000000; -const TWO_POW_55: u256 = 0x80000000000000; -const TWO_POW_64: u256 = 0x10000000000000000; -const TWO_POW_85: u256 = 0x2000000000000000000000; -const TWO_POW_106: u256 = 0x400000000000000000000000000; -const TWO_POW_127: u256 = 0x80000000000000000000000000000000; -const TWO_POW_148: u256 = 0x10000000000000000000000000000000000000; -const TWO_POW_169: u256 = 0x2000000000000000000000000000000000000000000; -const TWO_POW_190: u256 = 0x400000000000000000000000000000000000000000000000; -const TWO_POW_211: u256 = 0x80000000000000000000000000000000000000000000000000000; -const TWO_POW_232: u256 = 0x10000000000000000000000000000000000000000000000000000000000; -const TWO_POW_241: u256 = 0x2000000000000000000000000000000000000000000000000000000000000; -const TWO_POW_244: u256 = 0x10000000000000000000000000000000000000000000000000000000000000; + +const TWO_POW_3: u256 = 0x8; // 2^3 +const TWO_POW_4: u256 = 0x10; // 2^4 +const TWO_POW_9: u256 = 0x200; // 2^9 +const TWO_POW_13: u256 = 0x2000; // 2^13 +const TWO_POW_14: u256 = 0x4000; // 2^14 +const TWO_POW_18: u256 = 0x40000; // 2^18 +const TWO_POW_21: u256 = 0x200000; // 2^21 +const TWO_POW_26: u256 = 0x4000000; // 2^26 +const TWO_POW_27: u256 = 0x8000000; // 2^27 +const TWO_POW_31: u256 = 0x80000000; // 2^31 +const TWO_POW_32: u256 = 0x100000000; // 2^32 +const TWO_POW_58: u256 = 0x400000000000000; // 2^58 +const TWO_POW_67: u256 = 0x80000000000000000; // 2^67 +const TWO_POW_88: u256 = 0x10000000000000000000000; // 2^88 +const TWO_POW_109: u256 = 0x2000000000000000000000000000; // 2^109 +const TWO_POW_130: u256 = 0x400000000000000000000000000000000; // 2^130 +const TWO_POW_151: u256 = 0x80000000000000000000000000000000000000; // 2^151 +const TWO_POW_172: u256 = 0x10000000000000000000000000000000000000000000; // 2^172 +const TWO_POW_193: u256 = 0x2000000000000000000000000000000000000000000000000; // 2^193 +const TWO_POW_214: u256 = 0x400000000000000000000000000000000000000000000000000000; // 2^214 +const TWO_POW_235: u256 = 0x80000000000000000000000000000000000000000000000000000000000; // 2^235 +const TWO_POW_244: u256 = 0x10000000000000000000000000000000000000000000000000000000000000; // 2^244 +const TWO_POW_247: u256 = 0x80000000000000000000000000000000000000000000000000000000000000; // 2^247 + // --------------------------- // ---------- Tests ---------- @@ -1796,7 +1801,7 @@ mod tests { // equip gold ring with G1 let gold_ring = ItemPrimitive { id: ItemId::GoldRing, xp: 1, metadata: 1 }; adventurer.ring = gold_ring; - let bonus = adventurer.ring.jewelry_gold_bonus(base_gold_amount); + let _bonus = adventurer.ring.jewelry_gold_bonus(base_gold_amount); assert(adventurer.ring.jewelry_gold_bonus(base_gold_amount) == 3, 'bonus should be 3'); // increase greatness of gold ring to 10 @@ -1821,7 +1826,7 @@ mod tests { fn test_get_bonus_luck_gas() { // instantiate silver ring let silver_ring = ItemPrimitive { id: ItemId::SilverRing, xp: 1, metadata: 1 }; - let bonus_luck = silver_ring.jewelry_bonus_luck(); + let _bonus_luck = silver_ring.jewelry_bonus_luck(); } #[test] @@ -2530,7 +2535,7 @@ mod tests { let entropy = 1; // check new adventurer (level 1) gets a starter beast - let (beast, beast_seed) = adventurer.get_beast(entropy); + let (beast, _) = adventurer.get_beast(entropy); assert(beast.combat_spec.level == 1, 'beast should be lvl1'); assert(beast.combat_spec.specials.special1 == 0, 'beast should have no special1'); assert(beast.combat_spec.specials.special2 == 0, 'beast should have no special2'); @@ -2538,7 +2543,7 @@ mod tests { let entropy = 2; // check beast is still starter beast with different entropy source - let (beast, beast_seed) = adventurer.get_beast(entropy); + let (beast, _) = adventurer.get_beast(entropy); assert(beast.combat_spec.level == 1, 'beast should be lvl1'); assert(beast.combat_spec.specials.special1 == 0, 'beast should have no special1'); assert(beast.combat_spec.specials.special2 == 0, 'beast should have no special2'); @@ -2547,9 +2552,9 @@ mod tests { // advance adventurer to level 2 adventurer.xp = 4; let entropy = 1; - let (beast1, beast_seed) = adventurer.get_beast(entropy); + let (beast1, _) = adventurer.get_beast(entropy); let entropy = 2; - let (beast2, beast_seed) = adventurer.get_beast(entropy); + let (beast2, _) = adventurer.get_beast(entropy); // verify beasts are the same since the seed did not change assert(beast1.id != beast2.id, 'beasts not unique'); @@ -2558,7 +2563,7 @@ mod tests { #[test] #[available_gas(70320)] fn test_get_greatness_gas() { - let greatness = ImplAdventurer::get_greatness( + let _greatness = ImplAdventurer::get_greatness( ItemPrimitive { id: 1, xp: 400, metadata: 1 } ); } @@ -2766,16 +2771,85 @@ mod tests { } #[test] - #[available_gas(3000000)] + #[available_gas(30020000)] fn test_packing_and_unpacking_adventurer() { let adventurer = Adventurer { last_action_block: 511, health: 511, - xp: 8191, + xp: 16383, + stats: Stats { + strength: 31, + dexterity: 15, + vitality: 31, + intelligence: 15, + wisdom: 15, + charisma: 15, + luck: 15 + }, + gold: 511, + weapon: ItemPrimitive { id: 127, xp: 511, metadata: 31, }, + chest: ItemPrimitive { id: 1, xp: 0, metadata: 0, }, + head: ItemPrimitive { id: 127, xp: 511, metadata: 31, }, + waist: ItemPrimitive { id: 87, xp: 511, metadata: 4, }, + foot: ItemPrimitive { id: 78, xp: 511, metadata: 5, }, + hand: ItemPrimitive { id: 34, xp: 511, metadata: 6, }, + neck: ItemPrimitive { id: 32, xp: 511, metadata: 7, }, + ring: ItemPrimitive { id: 1, xp: 511, metadata: 8, }, + beast_health: 511, + stat_points_available: 7, + actions_per_block: 0, + mutated: false + }; + let packed = AdventurerPacking::pack(adventurer); + let unpacked: Adventurer = AdventurerPacking::unpack(packed); + assert(adventurer.last_action_block == unpacked.last_action_block, 'last_action_block'); + assert(adventurer.health == unpacked.health, 'health'); + assert(adventurer.xp == unpacked.xp, 'xp'); + assert(adventurer.stats.strength == unpacked.stats.strength, 'strength'); + assert(adventurer.stats.dexterity == unpacked.stats.dexterity, 'dexterity'); + assert(adventurer.stats.vitality == unpacked.stats.vitality, 'vitality'); + assert(adventurer.stats.intelligence == unpacked.stats.intelligence, 'intelligence'); + assert(adventurer.stats.wisdom == unpacked.stats.wisdom, 'wisdom'); + assert(adventurer.stats.charisma == unpacked.stats.charisma, 'charisma'); + assert(adventurer.gold == unpacked.gold, 'luck'); + assert(adventurer.weapon.id == unpacked.weapon.id, 'weapon.id'); + assert(adventurer.weapon.xp == unpacked.weapon.xp, 'weapon.xp'); + assert(adventurer.weapon.metadata == unpacked.weapon.metadata, 'weapon.metadata'); + assert(adventurer.chest.id == unpacked.chest.id, 'chest.id'); + assert(adventurer.chest.xp == unpacked.chest.xp, 'chest.xp'); + assert(adventurer.chest.metadata == unpacked.chest.metadata, 'chest.metadata'); + assert(adventurer.head.id == unpacked.head.id, 'head.id'); + assert(adventurer.head.xp == unpacked.head.xp, 'head.xp'); + assert(adventurer.head.metadata == unpacked.head.metadata, 'head.metadata'); + assert(adventurer.waist.id == unpacked.waist.id, 'waist.id'); + assert(adventurer.waist.xp == unpacked.waist.xp, 'waist.xp'); + assert(adventurer.waist.metadata == unpacked.waist.metadata, 'waist.metadata'); + assert(adventurer.foot.id == unpacked.foot.id, 'foot.id'); + assert(adventurer.foot.xp == unpacked.foot.xp, 'foot.xp'); + assert(adventurer.foot.metadata == unpacked.foot.metadata, 'foot.metadata'); + assert(adventurer.hand.id == unpacked.hand.id, 'hand.id'); + assert(adventurer.hand.xp == unpacked.hand.xp, 'hand.xp'); + assert(adventurer.hand.metadata == unpacked.hand.metadata, 'hand.metadata'); + assert(adventurer.neck.id == unpacked.neck.id, 'neck.id'); + assert(adventurer.neck.xp == unpacked.neck.xp, 'neck.xp'); + assert(adventurer.neck.metadata == unpacked.neck.metadata, 'neck.metadata'); + assert(adventurer.ring.id == unpacked.ring.id, 'ring.id'); + assert(adventurer.ring.xp == unpacked.ring.xp, 'ring.xp'); + assert(adventurer.ring.metadata == unpacked.ring.metadata, 'ring.metadata'); + assert(adventurer.beast_health == unpacked.beast_health, 'beast_health'); + assert( + adventurer.stat_points_available == unpacked.stat_points_available, + 'stat_points_available' + ); + + let adventurer = Adventurer { + last_action_block: 511, + health: 511, + xp: 16383, stats: Stats { - strength: 15, + strength: 31, dexterity: 15, - vitality: 15, + vitality: 31, intelligence: 15, wisdom: 15, charisma: 15, @@ -2870,10 +2944,19 @@ mod tests { #[available_gas(3000000)] fn test_pack_protection_overflow_strength() { let mut adventurer = ImplAdventurer::new(ItemId::Wand); - adventurer.stats.strength = MAX_STAT_VALUE + 1; + adventurer.stats.strength = 32; AdventurerPacking::pack(adventurer); } + #[test] + #[available_gas(3000000)] + fn test_pack_protection_strength() { + let mut adventurer = ImplAdventurer::new(ItemId::Wand); + adventurer.stats.strength = 31; + let unpacked: Adventurer = AdventurerPacking::unpack(AdventurerPacking::pack(adventurer)); + assert(unpacked.stats.strength == adventurer.stats.strength, 'strength should be same'); + } + #[test] #[should_panic(expected: ('dexterity overflow',))] #[available_gas(3000000)] @@ -2888,10 +2971,19 @@ mod tests { #[available_gas(3000000)] fn test_pack_protection_overflow_vitality() { let mut adventurer = ImplAdventurer::new(ItemId::Wand); - adventurer.stats.vitality = MAX_STAT_VALUE + 1; + adventurer.stats.vitality = 32; AdventurerPacking::pack(adventurer); } + #[test] + #[available_gas(3000000)] + fn test_pack_protection_vitality() { + let mut adventurer = ImplAdventurer::new(ItemId::Wand); + adventurer.stats.vitality = 31; + let unpacked: Adventurer = AdventurerPacking::unpack(AdventurerPacking::pack(adventurer)); + assert(unpacked.stats.vitality == adventurer.stats.vitality, 'vitality should be same'); + } + #[test] #[should_panic(expected: ('intelligence overflow',))] #[available_gas(3000000)] @@ -3135,7 +3227,7 @@ mod tests { let (previous_level, new_level) = adventurer.increase_adventurer_xp(MAX_XP + 10); assert(adventurer.xp == MAX_XP, 'xp should stop at max xp'); assert(previous_level == 2, 'prev level should be 2'); - assert(new_level == 90, 'new level should be 90'); + assert(new_level == 127, 'new level should be max 127'); // u16 overflow case adventurer.increase_adventurer_xp(65535); @@ -4846,7 +4938,7 @@ mod tests { // overflow case adventurer.ring.xp = 65535; adventurer.neck.xp = 65535; - let luck = adventurer.calculate_luck(bag); + let _luck = adventurer.calculate_luck(bag); assert( adventurer.calculate_luck(bag) == (ITEM_MAX_GREATNESS * 2) + SILVER_RING_G20_LUCK_BONUS, 'should be 60 luck' @@ -5174,4 +5266,14 @@ mod tests { assert(adventurer.is_ambushed(5), 'should be ambushed 5'); assert(!adventurer.is_ambushed(6), 'should not be ambushed 6'); } + + #[test] + #[available_gas(1000000)] + fn test_invalidate_game() { + let mut adventurer = ImplAdventurer::new(ItemId::Wand); + adventurer.invalidate_game(); + assert(adventurer.health == 0, 'adventurer health should be 0'); + assert(adventurer.xp == 1, 'adventurer xp should be 1'); + assert(adventurer.gold == 0, 'adventurer gold should be 0'); + } } diff --git a/contracts/adventurer/src/adventurer_meta.cairo b/contracts/adventurer/src/adventurer_meta.cairo index b7c5b6f4a..5048709a4 100644 --- a/contracts/adventurer/src/adventurer_meta.cairo +++ b/contracts/adventurer/src/adventurer_meta.cairo @@ -20,8 +20,8 @@ impl PackingAdventurerMetadata of StorePacking { (value.start_block.into() + StatsPacking::pack(value.starting_stats).into() * TWO_POW_64 - + interface_camel_u256 * TWO_POW_88 - + value.name.into() * TWO_POW_89) + + interface_camel_u256 * TWO_POW_91 + + value.name.into() * TWO_POW_92) .try_into() .unwrap() } @@ -31,7 +31,7 @@ impl PackingAdventurerMetadata of StorePacking { packed, TWO_POW_64.try_into().unwrap() ); let (packed, starting_stats) = integer::U256DivRem::div_rem( - packed, TWO_POW_24.try_into().unwrap() + packed, TWO_POW_27.try_into().unwrap() ); let (packed, interface_camel_u256) = integer::U256DivRem::div_rem( packed, TWO_POW_1.try_into().unwrap() @@ -68,6 +68,9 @@ const TWO_POW_64: u256 = 0x10000000000000000; const TWO_POW_88: u256 = 0x10000000000000000000000; const TWO_POW_89: u256 = 0x20000000000000000000000; const TWO_POW_128: u256 = 0x100000000000000000000000000000000; +const TWO_POW_91: u256 = 0x80000000000000000000000; // 2^91 +const TWO_POW_92: u256 = 0x100000000000000000000000; // 2^92 +const TWO_POW_27: u256 = 0x8000000; // 2^27 #[cfg(test)] #[test] diff --git a/contracts/adventurer/src/bag.cairo b/contracts/adventurer/src/bag.cairo index cb2e9cf92..f16b76563 100644 --- a/contracts/adventurer/src/bag.cairo +++ b/contracts/adventurer/src/bag.cairo @@ -136,7 +136,7 @@ impl ImplBag of IBag { // @dev This function constructs a new item with the given item_id, sets its metadata using the Adventurer and Bag reference, and adds the item to the bag. fn add_new_item(ref self: Bag, adventurer: Adventurer, item_id: u8) { let mut item = ImplItemPrimitive::new(item_id); - item.set_metadata_id(adventurer, self); + item.set_metadata_id(adventurer, self, false); self.add_item(item); } @@ -514,7 +514,7 @@ mod tests { } #[test] - #[available_gas(56400)] + #[available_gas(66400)] fn test_contains() { let katana = ItemPrimitive { id: ItemId::Katana, xp: 1, metadata: 1 }; let demon_crown = ItemPrimitive { id: ItemId::DemonCrown, xp: 2, metadata: 2 }; @@ -814,7 +814,7 @@ mod tests { #[test] #[should_panic(expected: ('Item not in bag',))] - #[available_gas(6820)] + #[available_gas(7820)] fn test_get_item_not_in_bag() { let item_1 = ItemPrimitive { id: 11, xp: 0, metadata: 0 }; let item_2 = ItemPrimitive { id: 12, xp: 0, metadata: 0 }; @@ -851,7 +851,7 @@ mod tests { } #[test] - #[available_gas(50100)] + #[available_gas(61100)] fn test_get_item() { let item_1 = ItemPrimitive { id: 11, xp: 0, metadata: 0 }; let item_2 = ItemPrimitive { id: 12, xp: 0, metadata: 0 }; diff --git a/contracts/adventurer/src/constants/adventurer_constants.cairo b/contracts/adventurer/src/constants/adventurer_constants.cairo index 4687ec510..289675fcc 100644 --- a/contracts/adventurer/src/constants/adventurer_constants.cairo +++ b/contracts/adventurer/src/constants/adventurer_constants.cairo @@ -4,8 +4,9 @@ const STARTING_HEALTH: u16 = 100; // Adventurer Max Values const MAX_ADVENTURER_HEALTH: u16 = 511; // 9 bits -const MAX_XP: u16 = 8191; // 13 bits +const MAX_XP: u16 = 16383; // 14 bits const MAX_STAT_VALUE: u8 = 15; // 4 bits +const MAX_STAT_VALUE_5_BITS: u8 = 31; // 5 bits const MAX_GOLD: u16 = 511; // 9 bits const MAX_PACKABLE_ITEM_XP: u16 = 511; // 9 bits const MAX_PACKABLE_BEAST_HEALTH: u16 = 511; // 9 bits diff --git a/contracts/adventurer/src/exploration.cairo b/contracts/adventurer/src/exploration.cairo index a821dcec1..edfdde90d 100644 --- a/contracts/adventurer/src/exploration.cairo +++ b/contracts/adventurer/src/exploration.cairo @@ -112,7 +112,7 @@ mod tests { fn test_get_base_discovery_amount_gas() { let level = 1; let entropy = 12345; - let discovery_amount = ExploreUtils::get_base_discovery_amount(level, entropy); + let _discovery_amount = ExploreUtils::get_base_discovery_amount(level, entropy); } #[test] diff --git a/contracts/adventurer/src/item_meta.cairo b/contracts/adventurer/src/item_meta.cairo index eba58a25b..51b8d9739 100644 --- a/contracts/adventurer/src/item_meta.cairo +++ b/contracts/adventurer/src/item_meta.cairo @@ -1,5 +1,6 @@ use starknet::{StorePacking}; -use lootitems::constants::{ItemId, ItemSuffix}; +use lootitems::{constants::{ItemId, ItemSuffix}, loot::ImplLoot}; +use combat::combat::Slot; use super::{ adventurer::{Adventurer, IAdventurer, ImplAdventurer}, item_primitive::ItemPrimitive, stats::Stats, bag::{Bag, IBag} @@ -253,125 +254,197 @@ impl ImplItemSpecials of IItemSpecials { self.mutated = true; } - // @dev This function assigns a metadata ID to an item owned by an adventurer. - // @notice It checks the metadata of each item possessed by the adventurer and the items in the bag. - // If the metadata of any of these items is larger than the current slot (initialized as 1), - // it updates the slot value with that metadata. - // Once all items have been checked, if the updated slot value plus one is less than or equal to the maximum storage slot, - // it assigns the new metadata ID (slot value + 1) to the item. - // If this updated slot value exceeds the maximum storage slot, it triggers a panic with the message 'exceeded metadata storage slots'. - // - // @param self A mutable reference to ItemPrimitive whose metadata ID will be updated. - // @param adventurer An instance of Adventurer that contains various items. - // @param bag An instance of Bag that contains various items. - // - // @throws This function will throw an error if the new metadata ID exceeds the maximum storage slot. - fn set_metadata_id(ref self: ItemPrimitive, adventurer: Adventurer, bag: Bag) { - // adventurer starter weapon has a meta data id of 1 - let mut slot = 1; + /// @title get_recycled_metadata + /// + /// @notice This function is used to find and return the metadata of the first item that has been dropped by the adventurer. + /// An item is considered dropped if its id is 0 but its metadata is not 0. + /// + /// @dev The function iterates through the adventurer's equipment and the bag's items in a specific order. + /// The order is: weapon, head, chest, hand, foot, ring, neck, waist, and then items 1 to 11 in the bag. + /// It returns the metadata of the first item it finds that has been dropped. + /// If no dropped items are found, it returns 0. + /// + /// @param item The Item we are assigning a metadata id to. + /// @param adventurer The adventurer object, which contains the metadata and id for each piece of equipment the adventurer has. + /// @param bag The bag object, which contains the metadata and id for each item in the bag. + /// @param equipping A boolean that indicates whether the item is being equipped or placed in the bag. + /// + /// @return The metadata of the first dropped item found, or 0 if no dropped items are found. + fn get_recycled_metadata( + item: ItemPrimitive, adventurer: Adventurer, bag: Bag, equipping: bool + ) -> u8 { + // if the item is being equipped + if equipping { + // look for available metadata slots in the adventurer's equipment + if (ImplLoot::get_slot(item.id) == Slot::Weapon(()) + && adventurer.weapon.id == 0 + && adventurer.weapon.metadata != 0) { + adventurer.weapon.metadata + } else if (ImplLoot::get_slot(item.id) == Slot::Head(()) + && adventurer.head.id == 0 + && adventurer.head.metadata != 0) { + adventurer.head.metadata + } else if (ImplLoot::get_slot(item.id) == Slot::Chest(()) + && adventurer.chest.id == 0 + && adventurer.chest.metadata != 0) { + adventurer.chest.metadata + } else if (ImplLoot::get_slot(item.id) == Slot::Waist(()) + && adventurer.waist.id == 0 + && adventurer.waist.metadata != 0) { + adventurer.waist.metadata + } else if (ImplLoot::get_slot(item.id) == Slot::Hand(()) + && adventurer.hand.id == 0 + && adventurer.hand.metadata != 0) { + adventurer.hand.metadata + } else if (ImplLoot::get_slot(item.id) == Slot::Foot(()) + && adventurer.foot.id == 0 + && adventurer.foot.metadata != 0) { + adventurer.foot.metadata + } else if (ImplLoot::get_slot(item.id) == Slot::Ring(()) + && adventurer.ring.id == 0 + && adventurer.ring.metadata != 0) { + adventurer.ring.metadata + } else if (ImplLoot::get_slot(item.id) == Slot::Neck(()) + && adventurer.neck.id == 0 + && adventurer.neck.metadata != 0) { + adventurer.neck.metadata + } else { + 0 + } + // otherwise item is going into bag + } else { + // so look for free metadata available in bag + if (bag.item_1.id == 0 && bag.item_1.metadata != 0) { + bag.item_1.metadata + } else if (bag.item_2.id == 0 && bag.item_2.metadata != 0) { + bag.item_2.metadata + } else if (bag.item_3.id == 0 && bag.item_3.metadata != 0) { + bag.item_3.metadata + } else if (bag.item_4.id == 0 && bag.item_4.metadata != 0) { + bag.item_4.metadata + } else if (bag.item_5.id == 0 && bag.item_5.metadata != 0) { + bag.item_5.metadata + } else if (bag.item_6.id == 0 && bag.item_6.metadata != 0) { + bag.item_6.metadata + } else if (bag.item_7.id == 0 && bag.item_7.metadata != 0) { + bag.item_7.metadata + } else if (bag.item_8.id == 0 && bag.item_8.metadata != 0) { + bag.item_8.metadata + } else if (bag.item_9.id == 0 && bag.item_9.metadata != 0) { + bag.item_9.metadata + } else if (bag.item_10.id == 0 && bag.item_10.metadata != 0) { + bag.item_10.metadata + } else if (bag.item_11.id == 0 && bag.item_11.metadata != 0) { + bag.item_11.metadata + } else { + 0 + } + } + } - if adventurer.weapon.metadata >= slot { - slot = adventurer.weapon.metadata; + /// @title get_next_metadata + /// + /// @notice This function is used to find the highest metadata value currently in use by the adventurer's equipment or the bag's items, and then returns the next available metadata value. + /// + /// @dev The function iterates through the adventurer's equipment and the bag's items in a specific order. + /// The order is: weapon, head, chest, hand, foot, ring, neck, waist, and then items 1 to 11 in the bag. + /// It keeps track of the highest metadata value it finds, and then increments it by 1 to get the next available metadata value. + /// If the next available metadata value is greater than the maximum allowed storage slots, it triggers a panic with the error code 'out of metadata storage slots'. + /// + /// @param adventurer The adventurer object, which contains the metadata for each piece of equipment the adventurer has. + /// @param bag The bag object, which contains the metadata for each item in the bag. + /// + /// @return The next available metadata value, or triggers a panic if no more metadata storage slots are available. + fn get_next_metadata(adventurer: Adventurer, bag: Bag) -> u8 { + let mut latest_metadata = 1; + if adventurer.weapon.metadata >= latest_metadata { + latest_metadata = adventurer.weapon.metadata; } - if adventurer.head.metadata >= slot { - slot = adventurer.head.metadata; + if adventurer.head.metadata >= latest_metadata { + latest_metadata = adventurer.head.metadata; } - if adventurer.chest.metadata >= slot { - slot = adventurer.chest.metadata; + if adventurer.chest.metadata >= latest_metadata { + latest_metadata = adventurer.chest.metadata; } - if adventurer.hand.metadata >= slot { - slot = adventurer.hand.metadata; + if adventurer.hand.metadata >= latest_metadata { + latest_metadata = adventurer.hand.metadata; } - if adventurer.foot.metadata >= slot { - slot = adventurer.foot.metadata; + if adventurer.foot.metadata >= latest_metadata { + latest_metadata = adventurer.foot.metadata; } - if adventurer.ring.metadata >= slot { - slot = adventurer.ring.metadata; + if adventurer.ring.metadata >= latest_metadata { + latest_metadata = adventurer.ring.metadata; } - if adventurer.neck.metadata >= slot { - slot = adventurer.neck.metadata; + if adventurer.neck.metadata >= latest_metadata { + latest_metadata = adventurer.neck.metadata; } - if adventurer.waist.metadata >= slot { - slot = adventurer.waist.metadata; + if adventurer.waist.metadata >= latest_metadata { + latest_metadata = adventurer.waist.metadata; } - if bag.item_1.metadata >= slot { - slot = bag.item_1.metadata; + if bag.item_1.metadata >= latest_metadata { + latest_metadata = bag.item_1.metadata; } - if bag.item_2.metadata >= slot { - slot = bag.item_2.metadata; + if bag.item_2.metadata >= latest_metadata { + latest_metadata = bag.item_2.metadata; } - if bag.item_3.metadata >= slot { - slot = bag.item_3.metadata; + if bag.item_3.metadata >= latest_metadata { + latest_metadata = bag.item_3.metadata; } - if bag.item_4.metadata >= slot { - slot = bag.item_4.metadata; + if bag.item_4.metadata >= latest_metadata { + latest_metadata = bag.item_4.metadata; } - if bag.item_5.metadata >= slot { - slot = bag.item_5.metadata; + if bag.item_5.metadata >= latest_metadata { + latest_metadata = bag.item_5.metadata; } - if bag.item_6.metadata >= slot { - slot = bag.item_6.metadata; + if bag.item_6.metadata >= latest_metadata { + latest_metadata = bag.item_6.metadata; } - if bag.item_7.metadata >= slot { - slot = bag.item_7.metadata; + if bag.item_7.metadata >= latest_metadata { + latest_metadata = bag.item_7.metadata; } - if bag.item_8.metadata >= slot { - slot = bag.item_8.metadata; + if bag.item_8.metadata >= latest_metadata { + latest_metadata = bag.item_8.metadata; } - if bag.item_9.metadata >= slot { - slot = bag.item_9.metadata; + if bag.item_9.metadata >= latest_metadata { + latest_metadata = bag.item_9.metadata; } - if bag.item_10.metadata >= slot { - slot = bag.item_10.metadata; + if bag.item_10.metadata >= latest_metadata { + latest_metadata = bag.item_10.metadata; } - if bag.item_11.metadata >= slot { - slot = bag.item_11.metadata; + if bag.item_11.metadata >= latest_metadata { + latest_metadata = bag.item_11.metadata; } // if the slot is less than the max storage slots, assign the new metadata id - if (slot + 1 <= STORAGE::MAX_TOTAL_STORAGE_SPECIALS) { - self.metadata = slot + 1; - // otherwise we have used all the storage slots and need to reuse one from a dropped item - } else if (adventurer.weapon.id == 0 && adventurer.weapon.metadata != 0) { - self.metadata = adventurer.weapon.metadata; - } else if (adventurer.head.id == 0 && adventurer.head.metadata != 0) { - self.metadata = adventurer.head.metadata; - } else if (adventurer.chest.id == 0 && adventurer.chest.metadata != 0) { - self.metadata = adventurer.chest.metadata; - } else if (adventurer.hand.id == 0 && adventurer.hand.metadata != 0) { - self.metadata = adventurer.hand.metadata; - } else if (adventurer.foot.id == 0 && adventurer.foot.metadata != 0) { - self.metadata = adventurer.foot.metadata; - } else if (adventurer.ring.id == 0 && adventurer.ring.metadata != 0) { - self.metadata = adventurer.ring.metadata; - } else if (adventurer.neck.id == 0 && adventurer.neck.metadata != 0) { - self.metadata = adventurer.neck.metadata; - } else if (adventurer.waist.id == 0 && adventurer.waist.metadata != 0) { - self.metadata = adventurer.waist.metadata; - } else if (bag.item_1.id == 0 && bag.item_1.metadata != 0) { - self.metadata = bag.item_1.metadata; - } else if (bag.item_2.id == 0 && bag.item_2.metadata != 0) { - self.metadata = bag.item_2.metadata; - } else if (bag.item_3.id == 0 && bag.item_3.metadata != 0) { - self.metadata = bag.item_3.metadata; - } else if (bag.item_4.id == 0 && bag.item_4.metadata != 0) { - self.metadata = bag.item_4.metadata; - } else if (bag.item_5.id == 0 && bag.item_5.metadata != 0) { - self.metadata = bag.item_5.metadata; - } else if (bag.item_6.id == 0 && bag.item_6.metadata != 0) { - self.metadata = bag.item_6.metadata; - } else if (bag.item_7.id == 0 && bag.item_7.metadata != 0) { - self.metadata = bag.item_7.metadata; - } else if (bag.item_8.id == 0 && bag.item_8.metadata != 0) { - self.metadata = bag.item_8.metadata; - } else if (bag.item_9.id == 0 && bag.item_9.metadata != 0) { - self.metadata = bag.item_9.metadata; - } else if (bag.item_10.id == 0 && bag.item_10.metadata != 0) { - self.metadata = bag.item_10.metadata; - } else if (bag.item_11.id == 0 && bag.item_11.metadata != 0) { - self.metadata = bag.item_11.metadata; + let next_metadata = latest_metadata + 1; + if next_metadata <= STORAGE::MAX_TOTAL_STORAGE_SPECIALS { + next_metadata } else { - panic_with_felt252('out of metadata storage slots'); + panic_with_felt252('out of metadata storage slots') + } + } + + /// @title set_metadata_id + /// + /// @notice This function is used to set the metadata of an item. It first tries to reuse the metadata of a dropped item. If no dropped items are found, it gets the next available metadata value. + /// + /// @dev The function first calls the `get_recycled_metadata` function, which returns the metadata of the first dropped item it finds, or 0 if no dropped items are found. + /// If `get_recycled_metadata` returns a value greater than 0, the function sets the item's metadata to this value. + /// If `get_recycled_metadata` returns 0, the function calls the `get_next_metadata` function, which returns the next available metadata value, and sets the item's metadata to this value. + /// + /// @param self The item object, which contains the metadata for the item. + /// @param adventurer The adventurer object, which contains the metadata for each piece of equipment the adventurer has. + /// @param bag The bag object, which contains the metadata for each item in the bag. + /// + /// @return None. The function modifies the item's metadata in place. + fn set_metadata_id(ref self: ItemPrimitive, adventurer: Adventurer, bag: Bag, equipping: bool) { + let reused_metadata = ImplItemSpecials::get_recycled_metadata( + self, adventurer, bag, equipping + ); + if reused_metadata > 0 { + self.metadata = reused_metadata; + } else { + self.metadata = ImplItemSpecials::get_next_metadata(adventurer, bag); } } } @@ -426,8 +499,7 @@ const TWO_POW_144: u256 = 0x1000000000000000000000000000000000000; mod tests { use lootitems::constants::{ItemId}; use survivor::{ - adventurer::{ImplAdventurer}, item_primitive::ItemPrimitive, stats::Stats, - bag::{Bag, IBag}, + adventurer::{ImplAdventurer}, item_primitive::ItemPrimitive, stats::Stats, bag::{Bag, IBag}, item_meta::{ ImplItemSpecials, ItemSpecialsStorage, ItemSpecials, ItemSpecialsStoragePacking, STORAGE }, @@ -475,7 +547,7 @@ mod tests { } #[test] - #[available_gas(4000000)] + #[available_gas(40000000)] fn test_set_metadata_id() { // start test with a new adventurer wielding a wand let mut adventurer = ImplAdventurer::new(ItemId::Wand); @@ -506,9 +578,9 @@ mod tests { let mut katana = ItemPrimitive { id: ItemId::Katana, xp: 1, metadata: 0 }; let mut demon_crown = ItemPrimitive { id: ItemId::DemonCrown, xp: 1, metadata: 0 }; let mut silk_robe = ItemPrimitive { id: ItemId::SilkRobe, xp: 1, metadata: 0 }; - let mut silver_ring = ItemPrimitive { id: ItemId::SilverRing, xp: 1, metadata: 0 }; - let mut ghost_wand = ItemPrimitive { id: ItemId::GhostWand, xp: 1, metadata: 0 }; - let mut leather_gloves = ItemPrimitive { id: ItemId::LeatherGloves, xp: 1, metadata: 0 }; + let mut demon_hands = ItemPrimitive { id: ItemId::DemonsHands, xp: 1, metadata: 0 }; + let mut shoes = ItemPrimitive { id: ItemId::Shoes, xp: 1, metadata: 0 }; + let mut brightsilk_sash = ItemPrimitive { id: ItemId::BrightsilkSash, xp: 1, metadata: 0 }; let mut silk_gloves = ItemPrimitive { id: ItemId::SilkGloves, xp: 1, metadata: 0 }; let mut linen_gloves = ItemPrimitive { id: ItemId::LinenGloves, xp: 1, metadata: 0 }; let mut crown = ItemPrimitive { id: ItemId::Crown, xp: 1, metadata: 0 }; @@ -519,102 +591,118 @@ mod tests { let mut holy_chestplate = ItemPrimitive { id: ItemId::HolyChestplate, xp: 1, metadata: 0 }; let mut holy_greaves = ItemPrimitive { id: ItemId::HolyGreaves, xp: 1, metadata: 0 }; let mut demonhide_boots = ItemPrimitive { id: ItemId::DemonhideBoots, xp: 1, metadata: 0 }; - let mut holy_gauntlets = ItemPrimitive { id: ItemId::HolyGauntlets, xp: 1, metadata: 0 }; - let mut demonhide_belt = ItemPrimitive { id: ItemId::DemonhideBelt, xp: 1, metadata: 0 }; + let mut _holy_gauntlets = ItemPrimitive { id: ItemId::HolyGauntlets, xp: 1, metadata: 0 }; + let mut _demonhide_belt = ItemPrimitive { id: ItemId::DemonhideBelt, xp: 1, metadata: 0 }; + + // take the common start with T1 Katana + katana.set_metadata_id(adventurer, bag, false); - // get next available specials storage slot - // this should result is the meta data is incrementing for each item - katana.set_metadata_id(adventurer, bag); + // toss starter wand in bag + bag.add_item(starter_wand); + + // equip katana adventurer.equip_item(katana); + + // katana should pickup new metadata pointer 2 assert(katana.metadata == 2, 'wrong katana metadata'); - demon_crown.set_metadata_id(adventurer, bag); + // starter wand should now be item1 in bag and meta data should have moved with it + assert( + bag.item_1.id == starter_wand.id && bag.item_1.metadata == 1, + 'wrong starter wand bag data' + ); + + // proceed to buy armor for the adventurer, verifying meta data pointers for each item + demon_crown.set_metadata_id(adventurer, bag, true); adventurer.equip_item(demon_crown); assert(demon_crown.metadata == 3, 'wrong demon crown metadata'); - silk_robe.set_metadata_id(adventurer, bag); + silk_robe.set_metadata_id(adventurer, bag, true); adventurer.equip_item(silk_robe); assert(silk_robe.metadata == 4, 'wrong silk robe metadata'); - silver_ring.set_metadata_id(adventurer, bag); - adventurer.equip_item(silver_ring); - assert(silver_ring.metadata == 5, 'wrong silver ring metadata'); + demon_hands.set_metadata_id(adventurer, bag, true); + adventurer.equip_item(demon_hands); + assert(demon_hands.metadata == 5, 'wrong demonhands metadata'); - ghost_wand.set_metadata_id(adventurer, bag); - bag.add_item(katana); - adventurer.equip_item(ghost_wand); - assert(ghost_wand.metadata == 6, 'wrong ghost wand metadata'); + shoes.set_metadata_id(adventurer, bag, true); + adventurer.equip_item(shoes); + assert(shoes.metadata == 6, 'wrong shoes metadata'); - leather_gloves.set_metadata_id(adventurer, bag); - adventurer.equip_item(leather_gloves); - assert(leather_gloves.metadata == 7, 'wrong leather gloves metadata'); - - silk_gloves.set_metadata_id(adventurer, bag); - bag.add_item(leather_gloves); - adventurer.equip_item(silk_gloves); - assert(silk_gloves.metadata == 8, 'wrong silk gloves metadata'); + brightsilk_sash.set_metadata_id(adventurer, bag, true); + adventurer.equip_item(brightsilk_sash); + assert(brightsilk_sash.metadata == 7, 'wrong leather gloves metadata'); - linen_gloves.set_metadata_id(adventurer, bag); + // Adventurer now has full armor (we are intentionally deferring jewlery) + // so now start buying items for the bag, filling it up to one less than capacity + silk_gloves.set_metadata_id(adventurer, bag, false); bag.add_item(silk_gloves); - adventurer.equip_item(linen_gloves); - assert(linen_gloves.metadata == 9, 'wrong linen gloves metadata'); + assert( + bag.item_2 == silk_gloves && silk_gloves.metadata == 8, 'wrong silk gloves metadata' + ); - crown.set_metadata_id(adventurer, bag); - bag.add_item(demon_crown); - adventurer.equip_item(crown); - assert(crown.metadata == 10, 'wrong crown metadata'); + linen_gloves.set_metadata_id(adventurer, bag, false); + bag.add_item(linen_gloves); + assert( + bag.item_3 == linen_gloves && linen_gloves.metadata == 9, 'wrong linen gloves metadata' + ); - divine_slippers.set_metadata_id(adventurer, bag); - adventurer.equip_item(divine_slippers); - assert(divine_slippers.metadata == 11, 'wrong divine slippers metadata'); + crown.set_metadata_id(adventurer, bag, false); + bag.add_item(crown); + assert(bag.item_4 == crown && crown.metadata == 10, 'wrong crown metadata'); - warhammer.set_metadata_id(adventurer, bag); - bag.add_item(ghost_wand); - adventurer.equip_item(warhammer); - assert(warhammer.metadata == 12, 'wrong warhammer metadata'); + divine_slippers.set_metadata_id(adventurer, bag, false); + bag.add_item(divine_slippers); + assert( + bag.item_5 == divine_slippers && divine_slippers.metadata == 11, + 'wrong divine slippers metadata' + ); - ancient_helm.set_metadata_id(adventurer, bag); - bag.add_item(crown); - adventurer.equip_item(ancient_helm); - assert(ancient_helm.metadata == 13, 'wrong ancient helm metadata'); + warhammer.set_metadata_id(adventurer, bag, false); + bag.add_item(warhammer); + assert(bag.item_6 == warhammer && warhammer.metadata == 12, 'wrong warhammer metadata'); - divine_robe.set_metadata_id(adventurer, bag); - bag.add_item(silk_robe); - adventurer.equip_item(divine_robe); - assert(divine_robe.metadata == 14, 'wrong divine robe metadata'); + ancient_helm.set_metadata_id(adventurer, bag, false); + bag.add_item(ancient_helm); + assert( + bag.item_7 == ancient_helm && ancient_helm.metadata == 13, 'wrong ancient helm metadata' + ); - holy_chestplate.set_metadata_id(adventurer, bag); + divine_robe.set_metadata_id(adventurer, bag, false); bag.add_item(divine_robe); - adventurer.equip_item(holy_chestplate); - assert(holy_chestplate.metadata == 15, 'wrong holy chestplate metadata'); + assert( + bag.item_8 == divine_robe && divine_robe.metadata == 14, 'wrong divine robe metadata' + ); - holy_greaves.set_metadata_id(adventurer, bag); - bag.add_item(divine_slippers); - adventurer.equip_item(holy_greaves); - assert(holy_greaves.metadata == 16, 'wrong holy greaves metadata'); + holy_chestplate.set_metadata_id(adventurer, bag, false); + bag.add_item(holy_chestplate); + assert( + bag.item_9 == holy_chestplate && holy_chestplate.metadata == 15, + 'wrong holy chestplate metadata' + ); - demonhide_boots.set_metadata_id(adventurer, bag); + holy_greaves.set_metadata_id(adventurer, bag, false); bag.add_item(holy_greaves); - adventurer.equip_item(demonhide_boots); - assert(demonhide_boots.metadata == 17, 'wrong demonhide boots metadata'); - - holy_gauntlets.set_metadata_id(adventurer, bag); - bag.add_item(linen_gloves); - adventurer.equip_item(holy_gauntlets); - assert(holy_gauntlets.metadata == 18, 'wrong holy gauntlets metadata'); + assert( + bag.item_10 == holy_greaves && holy_greaves.metadata == 16, + 'wrong holy greaves metadata' + ); - demonhide_belt.set_metadata_id(adventurer, bag); - adventurer.equip_item(demonhide_belt); - assert(demonhide_belt.metadata == 19, 'wrong demonhide belts metadata'); + demonhide_boots.set_metadata_id(adventurer, bag, false); + bag.add_item(demonhide_boots); + assert( + bag.item_11 == demonhide_boots && demonhide_boots.metadata == 17, + 'wrong demonhide boots metadata' + ); - // do one final pass to make sure none of the meta data got - // altered during equipment swap operations + // before we proceed to test drop and buy, double check meta data pointers + assert(starter_wand.metadata == 1, 'wrong starter wand metadata'); assert(katana.metadata == 2, 'wrong katana metadata'); assert(demon_crown.metadata == 3, 'wrong demon crown metadata'); assert(silk_robe.metadata == 4, 'wrong silk robe metadata'); - assert(silver_ring.metadata == 5, 'wrong silver ring metadata'); - assert(ghost_wand.metadata == 6, 'wrong ghost wand metadata'); - assert(leather_gloves.metadata == 7, 'wrong leather gloves metadata'); + assert(demon_hands.metadata == 5, 'wrong silver ring metadata'); + assert(shoes.metadata == 6, 'wrong ghost wand metadata'); + assert(brightsilk_sash.metadata == 7, 'wrong leather gloves metadata'); assert(silk_gloves.metadata == 8, 'wrong silk gloves metadata'); assert(linen_gloves.metadata == 9, 'wrong linen gloves metadata'); assert(crown.metadata == 10, 'wrong crown metadata'); @@ -625,7 +713,58 @@ mod tests { assert(holy_chestplate.metadata == 15, 'wrong holy chestplate metadata'); assert(holy_greaves.metadata == 16, 'wrong holy greaves metadata'); assert(demonhide_boots.metadata == 17, 'wrong demonhide boots metadata'); - assert(holy_gauntlets.metadata == 18, 'wrong holy gauntlets metadata'); + + // drop wand from bag and verify ID and XP get zero'd out but metadata stays the same + bag.remove_item(starter_wand.id); + assert(bag.item_1.id == 0, 'wand not zerod out'); + assert(bag.item_1.xp == 0, 'wand xp not zerod out'); + assert(bag.item_1.metadata == 1, 'wand metadata should not change'); + + // buy another item for the bag and verify it fills the spot of the dropped wand and uses the same metadata + let mut demonhide_belt = ItemPrimitive { id: ItemId::DemonhideBelt, xp: 1, metadata: 0 }; + demonhide_belt.set_metadata_id(adventurer, bag, false); + bag.add_item(demonhide_belt); + assert( + bag.item_1 == demonhide_belt && demonhide_belt.metadata == 1, + 'wrong demonhide belt metadata' + ); + + // same test but this time dropping two items from bag and verifying the new items fill the spots and use the same metadata + let item_2_metadata = bag.item_2.metadata; + let item_3_metadata = bag.item_3.metadata; + bag.remove_item(bag.item_2.id); + bag.remove_item(bag.item_3.id); + let mut book = ItemPrimitive { id: ItemId::Book, xp: 1, metadata: 0 }; + book.set_metadata_id(adventurer, bag, false); + bag.add_item(book); + let mut tome = ItemPrimitive { id: ItemId::Tome, xp: 1, metadata: 0 }; + tome.set_metadata_id(adventurer, bag, false); + bag.add_item(tome); + assert(bag.item_2 == book && book.metadata == item_2_metadata, 'wrong book metadata'); + assert(bag.item_3 == tome && tome.metadata == item_3_metadata, 'wrong tome metadata'); + + // with the holes filled, purchase remaining jewelry items + + // necklace should pickup metadata 18 + let mut necklace = ItemPrimitive { id: ItemId::Necklace, xp: 1, metadata: 0 }; + necklace.set_metadata_id(adventurer, bag, true); + adventurer.equip_necklace(necklace); + assert(adventurer.neck == necklace && necklace.metadata == 18, 'wrong necklace metadata'); + + // and ring should get the last metadata spot of 19 + let mut gold_ring = ItemPrimitive { id: ItemId::GoldRing, xp: 1, metadata: 0 }; + gold_ring.set_metadata_id(adventurer, bag, true); + adventurer.equip_ring(gold_ring); + assert( + adventurer.ring == gold_ring && gold_ring.metadata == 19, 'wrong gold ring metadata' + ); + + // drop adventurers katana and buy a grimoire and verify the metadata is reused + adventurer.drop_item(adventurer.weapon.id); + let mut grimoire = ItemPrimitive { id: ItemId::Grimoire, xp: 1, metadata: 0 }; + grimoire.set_metadata_id(adventurer, bag, true); + adventurer.equip_item(grimoire); + assert(adventurer.weapon == grimoire && grimoire.metadata == 2, 'wrong grimoire metadata'); } #[test] @@ -648,7 +787,7 @@ mod tests { let divine_robe = ItemPrimitive { id: ItemId::DivineRobe, xp: 1, metadata: 14 }; let holy_chestplate = ItemPrimitive { id: ItemId::HolyChestplate, xp: 1, metadata: 15 }; let holy_greaves = ItemPrimitive { id: ItemId::HolyGreaves, xp: 1, metadata: 16 }; - let demonhide_boots = ItemPrimitive { id: ItemId::DemonhideBoots, xp: 1, metadata: 17 }; + let _demonhide_boots = ItemPrimitive { id: ItemId::DemonhideBoots, xp: 1, metadata: 17 }; let holy_gauntlets = ItemPrimitive { id: ItemId::HolyGauntlets, xp: 1, metadata: 18 }; let demonhide_boots = ItemPrimitive { id: ItemId::DemonhideBoots, xp: 1, metadata: 19 }; @@ -669,7 +808,7 @@ mod tests { let divine_robe_specials = ItemSpecials { special1: 14, special2: 14, special3: 14 }; let holy_chestplate_specials = ItemSpecials { special1: 15, special2: 15, special3: 15 }; let holy_greaves_specials = ItemSpecials { special1: 1, special2: 16, special3: 16 }; - let demonhide_boots_specials = ItemSpecials { special1: 2, special2: 17, special3: 17 }; + let _demonhide_boots_specials = ItemSpecials { special1: 2, special2: 17, special3: 17 }; let holy_gauntlets_specials = ItemSpecials { special1: 3, special2: 18, special3: 18 }; let demonhide_boots_specials = ItemSpecials { special1: 4, special2: 19, special3: 19 }; @@ -794,7 +933,7 @@ mod tests { // this should throw a panic with the error 'metadata id out of bounds' // this test is annotated to expect this error and will fail // if it is not thrown - let meta_data = name_storage1.get_specials(item_11); + let _meta_data = name_storage1.get_specials(item_11); } #[test] @@ -824,7 +963,7 @@ mod tests { // attempt to get specials for this item which has meta data id 0 // this should throw a panic with the error 'metadata id out of bounds' // this test is annotated to expect this error - let meta_data = name_storage1.get_specials(item_11); + let _meta_data = name_storage1.get_specials(item_11); } #[test] @@ -984,7 +1123,7 @@ mod tests { // attempt to set specials for an item with meta data id zero // since this is not possible, we expect set_specials to panic // with 'metadata id out of bounds' - let meta_data = name_storage1.set_specials(item_11, item_11_specials); + let _meta_data = name_storage1.set_specials(item_11, item_11_specials); } #[test] @@ -1016,6 +1155,6 @@ mod tests { // it's meta data exceeds the max storage slot for the special storage system // this should throw a panic with the error 'metadata id out of bounds' // this test is annotated to expect this error - let meta_data = name_storage1.set_specials(item_11, item_11_specials); + let _meta_data = name_storage1.set_specials(item_11, item_11_specials); } } diff --git a/contracts/adventurer/src/leaderboard.cairo b/contracts/adventurer/src/leaderboard.cairo index 5c6389e4e..506222754 100644 --- a/contracts/adventurer/src/leaderboard.cairo +++ b/contracts/adventurer/src/leaderboard.cairo @@ -51,7 +51,7 @@ impl LeaderboardPacking of StorePacking { let mut packed = value.into(); let (packed, first) = integer::U256DivRem::div_rem(packed, TWO_POW_83.try_into().unwrap()); let (packed, second) = integer::U256DivRem::div_rem(packed, TWO_POW_83.try_into().unwrap()); - let (packed, third) = integer::U256DivRem::div_rem(packed, TWO_POW_83.try_into().unwrap()); + let (_packed, third) = integer::U256DivRem::div_rem(packed, TWO_POW_83.try_into().unwrap()); Leaderboard { first: ScorePacking::unpack(first.try_into().unwrap()), diff --git a/contracts/adventurer/src/lib.cairo b/contracts/adventurer/src/lib.cairo index 8f7007090..962f46c8b 100644 --- a/contracts/adventurer/src/lib.cairo +++ b/contracts/adventurer/src/lib.cairo @@ -10,4 +10,4 @@ mod constants { mod discovery_constants; mod adventurer_constants; } -mod leaderboard; \ No newline at end of file +mod leaderboard; diff --git a/contracts/adventurer/src/stats.cairo b/contracts/adventurer/src/stats.cairo index f702cb5e6..e22ff3d96 100644 --- a/contracts/adventurer/src/stats.cairo +++ b/contracts/adventurer/src/stats.cairo @@ -1,10 +1,10 @@ use core::{option::OptionTrait, starknet::{StorePacking}, traits::{TryInto, Into}}; #[derive(Drop, Copy, Serde)] -struct Stats { // 24 storage bits - strength: u8, // 4 bits +struct Stats { // 26 storage bits + strength: u8, // 5 bits dexterity: u8, // 4 bits - vitality: u8, // 4 bits + vitality: u8, // 5 bits intelligence: u8, // 4 bits wisdom: u8, // 4 bits charisma: u8, // 4 bits @@ -23,11 +23,11 @@ impl StatUtils of IStat { impl StatsPacking of StorePacking { fn pack(value: Stats) -> felt252 { (value.strength.into() - + value.dexterity.into() * TWO_POW_4 - + value.vitality.into() * TWO_POW_8 - + value.intelligence.into() * TWO_POW_12 - + value.wisdom.into() * TWO_POW_16 - + value.charisma.into() * TWO_POW_20) + + value.dexterity.into() * TWO_POW_5 + + value.vitality.into() * TWO_POW_9 + + value.intelligence.into() * TWO_POW_14 + + value.wisdom.into() * TWO_POW_18 + + value.charisma.into() * TWO_POW_22) .try_into() .unwrap() } @@ -35,13 +35,13 @@ impl StatsPacking of StorePacking { fn unpack(value: felt252) -> Stats { let packed = value.into(); let (packed, strength) = integer::U256DivRem::div_rem( - packed, TWO_POW_4.try_into().unwrap() + packed, TWO_POW_5.try_into().unwrap() ); let (packed, dexterity) = integer::U256DivRem::div_rem( packed, TWO_POW_4.try_into().unwrap() ); let (packed, vitality) = integer::U256DivRem::div_rem( - packed, TWO_POW_4.try_into().unwrap() + packed, TWO_POW_5.try_into().unwrap() ); let (packed, intelligence) = integer::U256DivRem::div_rem( packed, TWO_POW_4.try_into().unwrap() @@ -61,11 +61,15 @@ impl StatsPacking of StorePacking { } } +const TWO_POW_22: u256 = 0x400000; +const TWO_POW_9: u256 = 0x200; +const TWO_POW_10: u256 = 0x400; +const TWO_POW_5: u256 = 0x20; const TWO_POW_4: u256 = 0x10; const TWO_POW_8: u256 = 0x100; const TWO_POW_12: u256 = 0x1000; -const TWO_POW_16: u256 = 0x10000; -const TWO_POW_20: u256 = 0x100000; +const TWO_POW_14: u256 = 0x4000; +const TWO_POW_18: u256 = 0x40000; const TWO_POW_24: u256 = 0x1000000; // --------------------------- @@ -95,9 +99,9 @@ mod tests { // storage limit test (2^4 - 1 = 15) let stats = Stats { - strength: 15, + strength: 31, dexterity: 15, - vitality: 15, + vitality: 31, intelligence: 15, wisdom: 15, charisma: 15, diff --git a/contracts/beasts/src/beast.cairo b/contracts/beasts/src/beast.cairo index 5a5b8e7dd..833434912 100644 --- a/contracts/beasts/src/beast.cairo +++ b/contracts/beasts/src/beast.cairo @@ -407,7 +407,7 @@ mod tests { assert(beast_id != 0, 'beast should not be zero'); assert(beast_id <= MAX_ID, 'beast higher than max beastid'); - let above_max_beast_id = MAX_ID + 1; + let _above_max_beast_id = MAX_ID + 1; let beast_id = ImplBeast::get_beast_id(max_beast_id); assert(beast_id != 0, 'beast should not be zero'); assert(beast_id <= MAX_ID, 'beast higher than max beastid'); diff --git a/contracts/combat/src/combat.cairo b/contracts/combat/src/combat.cairo index d94e13443..a86a4e36d 100644 --- a/contracts/combat/src/combat.cairo +++ b/contracts/combat/src/combat.cairo @@ -112,21 +112,11 @@ impl ImplCombat of ICombat { fn get_attack_hp(weapon: CombatSpec) -> u16 { match weapon.tier { Tier::None(()) => 0, - Tier::T1(()) => { - weapon.level * WEAPON_TIER_DAMAGE_MULTIPLIER::T1 - }, - Tier::T2(()) => { - weapon.level * WEAPON_TIER_DAMAGE_MULTIPLIER::T2 - }, - Tier::T3(()) => { - weapon.level * WEAPON_TIER_DAMAGE_MULTIPLIER::T3 - }, - Tier::T4(()) => { - weapon.level * WEAPON_TIER_DAMAGE_MULTIPLIER::T4 - }, - Tier::T5(()) => { - weapon.level * WEAPON_TIER_DAMAGE_MULTIPLIER::T5 - } + Tier::T1(()) => { weapon.level * WEAPON_TIER_DAMAGE_MULTIPLIER::T1 }, + Tier::T2(()) => { weapon.level * WEAPON_TIER_DAMAGE_MULTIPLIER::T2 }, + Tier::T3(()) => { weapon.level * WEAPON_TIER_DAMAGE_MULTIPLIER::T3 }, + Tier::T4(()) => { weapon.level * WEAPON_TIER_DAMAGE_MULTIPLIER::T4 }, + Tier::T5(()) => { weapon.level * WEAPON_TIER_DAMAGE_MULTIPLIER::T5 } } } @@ -136,21 +126,11 @@ impl ImplCombat of ICombat { fn get_armor_hp(armor: CombatSpec) -> u16 { match armor.tier { Tier::None(()) => 0, - Tier::T1(()) => { - armor.level * ARMOR_TIER_DAMAGE_MULTIPLIER::T1 - }, - Tier::T2(()) => { - armor.level * ARMOR_TIER_DAMAGE_MULTIPLIER::T2 - }, - Tier::T3(()) => { - armor.level * ARMOR_TIER_DAMAGE_MULTIPLIER::T3 - }, - Tier::T4(()) => { - armor.level * ARMOR_TIER_DAMAGE_MULTIPLIER::T4 - }, - Tier::T5(()) => { - armor.level * ARMOR_TIER_DAMAGE_MULTIPLIER::T5 - } + Tier::T1(()) => { armor.level * ARMOR_TIER_DAMAGE_MULTIPLIER::T1 }, + Tier::T2(()) => { armor.level * ARMOR_TIER_DAMAGE_MULTIPLIER::T2 }, + Tier::T3(()) => { armor.level * ARMOR_TIER_DAMAGE_MULTIPLIER::T3 }, + Tier::T4(()) => { armor.level * ARMOR_TIER_DAMAGE_MULTIPLIER::T4 }, + Tier::T5(()) => { armor.level * ARMOR_TIER_DAMAGE_MULTIPLIER::T5 } } } @@ -167,15 +147,9 @@ impl ImplCombat of ICombat { // get weapon and qualify effectiveness of weapon against armor let weapon_effectiveness = ImplCombat::get_elemental_effectiveness(weapon_type, armor_type); match weapon_effectiveness { - WeaponEffectiveness::Weak(()) => { - damage - elemental_effect - }, - WeaponEffectiveness::Fair(()) => { - damage - }, - WeaponEffectiveness::Strong(()) => { - damage + elemental_effect - } + WeaponEffectiveness::Weak(()) => { damage - elemental_effect }, + WeaponEffectiveness::Fair(()) => { damage }, + WeaponEffectiveness::Strong(()) => { damage + elemental_effect } } } @@ -186,93 +160,51 @@ impl ImplCombat of ICombat { // @return WeaponEffectiveness: the effectiveness of the weapon against the armor fn get_elemental_effectiveness(weapon_type: Type, armor_type: Type) -> WeaponEffectiveness { match weapon_type { - Type::None(()) => { - WeaponEffectiveness::Fair(()) - }, + Type::None(()) => { WeaponEffectiveness::Fair(()) }, // Magic is strong against metal, fair against cloth, and weak against hide Type::Magic_or_Cloth(()) => { match armor_type { // weapon is strong against no armor - Type::None(()) => { - WeaponEffectiveness::Strong(()) - }, - Type::Magic_or_Cloth(()) => { - WeaponEffectiveness::Fair(()) - }, - Type::Blade_or_Hide(()) => { - WeaponEffectiveness::Weak(()) - }, - Type::Bludgeon_or_Metal(()) => { - WeaponEffectiveness::Strong(()) - }, + Type::None(()) => { WeaponEffectiveness::Strong(()) }, + Type::Magic_or_Cloth(()) => { WeaponEffectiveness::Fair(()) }, + Type::Blade_or_Hide(()) => { WeaponEffectiveness::Weak(()) }, + Type::Bludgeon_or_Metal(()) => { WeaponEffectiveness::Strong(()) }, // should not happen but compiler requires exhaustive match - Type::Necklace(()) => { - WeaponEffectiveness::Fair(()) - }, + Type::Necklace(()) => { WeaponEffectiveness::Fair(()) }, // should not happen but compiler requires exhaustive match - Type::Ring(()) => { - WeaponEffectiveness::Fair(()) - } + Type::Ring(()) => { WeaponEffectiveness::Fair(()) } } }, // Blade is strong against cloth, fair against hide, and weak against metal Type::Blade_or_Hide(()) => { match armor_type { // weapon is strong against no armor - Type::None(()) => { - WeaponEffectiveness::Strong(()) - }, - Type::Magic_or_Cloth(()) => { - WeaponEffectiveness::Strong(()) - }, - Type::Blade_or_Hide(()) => { - WeaponEffectiveness::Fair(()) - }, - Type::Bludgeon_or_Metal(()) => { - WeaponEffectiveness::Weak(()) - }, + Type::None(()) => { WeaponEffectiveness::Strong(()) }, + Type::Magic_or_Cloth(()) => { WeaponEffectiveness::Strong(()) }, + Type::Blade_or_Hide(()) => { WeaponEffectiveness::Fair(()) }, + Type::Bludgeon_or_Metal(()) => { WeaponEffectiveness::Weak(()) }, // should not happen but compiler requires exhaustive match - Type::Necklace(()) => { - WeaponEffectiveness::Fair(()) - }, + Type::Necklace(()) => { WeaponEffectiveness::Fair(()) }, // should not happen but compiler requires exhaustive match - Type::Ring(()) => { - WeaponEffectiveness::Fair(()) - } + Type::Ring(()) => { WeaponEffectiveness::Fair(()) } } }, // Bludgeon is strong against hide, fair against metal, and weak against cloth Type::Bludgeon_or_Metal(()) => { match armor_type { // weapon is strong against no armor - Type::None(()) => { - WeaponEffectiveness::Strong(()) - }, - Type::Magic_or_Cloth(()) => { - WeaponEffectiveness::Weak(()) - }, - Type::Blade_or_Hide(()) => { - WeaponEffectiveness::Strong(()) - }, - Type::Bludgeon_or_Metal(()) => { - WeaponEffectiveness::Fair(()) - }, + Type::None(()) => { WeaponEffectiveness::Strong(()) }, + Type::Magic_or_Cloth(()) => { WeaponEffectiveness::Weak(()) }, + Type::Blade_or_Hide(()) => { WeaponEffectiveness::Strong(()) }, + Type::Bludgeon_or_Metal(()) => { WeaponEffectiveness::Fair(()) }, // should not happen but compiler requires exhaustive match - Type::Necklace(()) => { - WeaponEffectiveness::Fair(()) - }, + Type::Necklace(()) => { WeaponEffectiveness::Fair(()) }, // should not happen but compiler requires exhaustive match - Type::Ring(()) => { - WeaponEffectiveness::Fair(()) - } + Type::Ring(()) => { WeaponEffectiveness::Fair(()) } } }, - Type::Necklace(()) => { - WeaponEffectiveness::Fair(()) - }, - Type::Ring(()) => { - WeaponEffectiveness::Fair(()) - }, + Type::Necklace(()) => { WeaponEffectiveness::Fair(()) }, + Type::Ring(()) => { WeaponEffectiveness::Fair(()) }, } } @@ -478,24 +410,12 @@ impl ImplCombat of ICombat { // @return u16: the base reward fn get_base_reward(self: CombatSpec) -> u16 { match self.tier { - Tier::None(()) => { - 0 - }, - Tier::T1(()) => { - (XP_MULTIPLIER::T1 * self.level) / XP_REWARD_DIVISOR - }, - Tier::T2(()) => { - (XP_MULTIPLIER::T2 * self.level) / XP_REWARD_DIVISOR - }, - Tier::T3(()) => { - (XP_MULTIPLIER::T3 * self.level) / XP_REWARD_DIVISOR - }, - Tier::T4(()) => { - (XP_MULTIPLIER::T4 * self.level) / XP_REWARD_DIVISOR - }, - Tier::T5(()) => { - (XP_MULTIPLIER::T5 * self.level) / XP_REWARD_DIVISOR - } + Tier::None(()) => { 0 }, + Tier::T1(()) => { (XP_MULTIPLIER::T1 * self.level) / XP_REWARD_DIVISOR }, + Tier::T2(()) => { (XP_MULTIPLIER::T2 * self.level) / XP_REWARD_DIVISOR }, + Tier::T3(()) => { (XP_MULTIPLIER::T3 * self.level) / XP_REWARD_DIVISOR }, + Tier::T4(()) => { (XP_MULTIPLIER::T4 * self.level) / XP_REWARD_DIVISOR }, + Tier::T5(()) => { (XP_MULTIPLIER::T5 * self.level) / XP_REWARD_DIVISOR } } } @@ -664,7 +584,7 @@ mod tests { fn test_get_enemy_starting_health_max_values() { // test max value case // no need to assert result just make sure it doesn't panic - let enemy_starting_health = ImplCombat::get_enemy_starting_health( + let _enemy_starting_health = ImplCombat::get_enemy_starting_health( 255, 340282366920938463463374607431768211455 ); } @@ -1055,7 +975,7 @@ mod tests { fn test_critical_hit_bonus() { let base_damage = 100; let critical_hit_chance = 100; - let mut damage_multiplier = 1; + let mut _damage_multiplier = 1; // low critical hit damage let mut entropy = 0; @@ -1348,10 +1268,10 @@ mod tests { fn test_calculate_damage() { let minimum_damage = 4; - let min_critical_hit_bonus = 0; + let _min_critical_hit_bonus = 0; let medium_critical_hit_bonus = 1; - let high_critical_hit_bonus = 2; - let max_critical_hit_bonus = 3; + let _high_critical_hit_bonus = 2; + let _max_critical_hit_bonus = 3; // initialize other combat parameters // start with simplest values to reduce number of variables to track @@ -1487,9 +1407,9 @@ mod tests { #[available_gas(750000)] fn test_get_random_level() { let mut adventurer_level = 1; - let range_level_increase = DIFFICULTY_INCREASE_RATE::NORMAL; - let level_multiplier = LEVEL_MULTIPLIER::NORMAL; - let beast_level = ImplCombat::get_random_level(adventurer_level, 0); + let _range_level_increase = DIFFICULTY_INCREASE_RATE::NORMAL; + let _level_multiplier = LEVEL_MULTIPLIER::NORMAL; + let _beast_level = ImplCombat::get_random_level(adventurer_level, 0); adventurer_level = 2; let min_beast_level = ImplCombat::get_random_level(adventurer_level, 0); diff --git a/contracts/game/Scarb.toml b/contracts/game/Scarb.toml index 5a35839a9..0e5775743 100644 --- a/contracts/game/Scarb.toml +++ b/contracts/game/Scarb.toml @@ -3,16 +3,16 @@ name = "game" version = "0.1.0" [dependencies] -starknet = "2.1.0" +starknet.workspace = true lootitems = { path = "../loot" } survivor = { path = "../adventurer" } market = { path = "../market" } obstacles = { path = "../obstacles" } game_entropy = { path = "../game_entropy" } game_snapshot = { path = "../game_snapshot" } -openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.7.0" } -goldenToken = { git = "https://github.com/BibliothecaDAO/golden-token" } -arcade_account = { git = "https://github.com/BibliothecaDAO/arcade-account" } +openzeppelin.workspace = true +golden_token.workspace = true +arcade_account.workspace = true [[target.starknet-contract]] allowed-libfuncs-list.name = "experimental" diff --git a/contracts/game/src/game/constants.cairo b/contracts/game/src/game/constants.cairo index 48486e7f6..c5094d1a7 100644 --- a/contracts/game/src/game/constants.cairo +++ b/contracts/game/src/game/constants.cairo @@ -31,6 +31,11 @@ mod messages { const NOT_OWNER_OF_TOKEN: felt252 = 'Not owner of token'; const MA_PERIOD_LESS_THAN_WEEK: felt252 = 'MA period too small'; const TERMINAL_TIME_REACHED: felt252 = 'terminal time reached'; + const STARTING_ENTROPY_ALREADY_SET: felt252 = 'starting entropy already set'; + const STARTING_ENTROPY_ZERO: felt252 = 'block hash should not be zero'; + const GAME_ALREADY_STARTED: felt252 = 'game already started'; + const STARTING_ENTROPY_IS_VALID: felt252 = 'starting entropy is valid'; + const VALID_BLOCK_HASH_UNAVAILABLE: felt252 = 'valid hash not yet available'; } // TODO: Update for mainnet diff --git a/contracts/game/src/game/interfaces.cairo b/contracts/game/src/game/interfaces.cairo index 8ae13d872..16670860e 100644 --- a/contracts/game/src/game/interfaces.cairo +++ b/contracts/game/src/game/interfaces.cairo @@ -16,6 +16,7 @@ trait IGame { fn new_game( ref self: TContractState, client_reward_address: ContractAddress, weapon: u8, name: u128, golden_token_id: u256, interface_camel: bool ); + fn set_starting_entropy(ref self: TContractState, adventurer_id: felt252, block_hash: felt252); fn explore(ref self: TContractState, adventurer_id: felt252, till_beast: bool); fn attack(ref self: TContractState, adventurer_id: felt252, to_the_death: bool); fn flee(ref self: TContractState, adventurer_id: felt252, to_the_death: bool); @@ -29,6 +30,7 @@ trait IGame { items: Array, ); fn slay_idle_adventurers(ref self: TContractState, adventurer_ids: Array); + fn slay_invalid_adventurers(ref self: TContractState, adventurer_ids: Array); fn rotate_game_entropy(ref self: TContractState); fn update_cost_to_play(ref self: TContractState); fn initiate_price_change(ref self: TContractState); @@ -39,6 +41,8 @@ trait IGame { fn get_adventurer(self: @TContractState, adventurer_id: felt252) -> Adventurer; fn get_adventurer_no_boosts(self: @TContractState, adventurer_id: felt252) -> Adventurer; fn get_adventurer_meta(self: @TContractState, adventurer_id: felt252) -> AdventurerMetadata; + fn get_adventurer_starting_entropy(self: @TContractState, adventurer_id: felt252) -> felt252; + fn get_adventurer_entropy(self: @TContractState, adventurer_id: felt252) -> felt252; fn get_health(self: @TContractState, adventurer_id: felt252) -> u16; fn get_xp(self: @TContractState, adventurer_id: felt252) -> u16; fn get_level(self: @TContractState, adventurer_id: felt252) -> u8; @@ -48,6 +52,7 @@ trait IGame { fn get_actions_per_block(self: @TContractState, adventurer_id: felt252) -> u8; fn get_reveal_block(self: @TContractState, adventurer_id: felt252) -> u64; fn is_idle(self: @TContractState, adventurer_id: felt252) -> (bool, u16); + fn get_contract_calculated_entropy(self: @TContractState, adventurer_id: felt252) -> felt252; // adventurer stats (includes boost) fn get_stats(self: @TContractState, adventurer_id: felt252) -> Stats; diff --git a/contracts/game/src/lib.cairo b/contracts/game/src/lib.cairo index 62764e386..164ed518c 100644 --- a/contracts/game/src/lib.cairo +++ b/contracts/game/src/lib.cairo @@ -44,10 +44,6 @@ mod Game { IERC721, IERC721Dispatcher, IERC721DispatcherTrait, IERC721LibraryDispatcher }; - use goldenToken::ERC721::{ - GoldenToken, GoldenTokenDispatcher, GoldenTokenDispatcherTrait, GoldenTokenLibraryDispatcher - }; - use arcade_account::{ account::interface::{ IMasterControl, IMasterControlDispatcher, IMasterControlDispatcherTrait @@ -121,6 +117,7 @@ mod Game { _cost_to_play: u128, _games_played_snapshot: GamesPlayedSnapshot, _terminal_timestamp: u64, + _starting_entropy: LegacyMap::, } #[event] @@ -169,7 +166,17 @@ mod Game { self._collectible_beasts.write(collectible_beasts); self._terminal_timestamp.write(terminal_timestamp); self._genesis_block.write(starknet::get_block_info().unbox().block_number.into()); - self._genesis_timestamp.write(starknet::get_block_info().unbox().block_timestamp.into()); + + // On mainnet, set genesis timestamp to LSV1.0 genesis to preserve same reward distribution schedule for V1.1 + let chain_id = starknet::get_execution_info().unbox().tx_info.unbox().chain_id; + if chain_id == MAINNET_CHAIN_ID { + self._genesis_timestamp.write(1699552291); + } else { + // on non-mainnet, use the current block timestamp so tests run correctly + self + ._genesis_timestamp + .write(starknet::get_block_info().unbox().block_timestamp.into()); + }; // set the golden token address self._golden_token.write(golden_token_address); @@ -193,7 +200,7 @@ mod Game { // ------------ Impl ------------------------ // // ------------------------------------------ // - #[external(v0)] + #[abi(embed_v0)] impl Game of IGame { /// @title New Game /// @@ -228,6 +235,31 @@ mod Game { _start_game(ref self, weapon, name, interface_camel); } + fn set_starting_entropy( + ref self: ContractState, adventurer_id: felt252, block_hash: felt252 + ) { + // only owner of the adventurer can set starting entropy + _assert_ownership(@self, adventurer_id); + + // player can only call this before starting game (defeating starter beast) + assert(_load_adventurer(@self, adventurer_id).xp == 0, messages::GAME_ALREADY_STARTED); + + // prevent client from trying to set start entropy prior to valid block hash being available + _assert_valid_block_hash_available(@self, adventurer_id); + + // verify the block_hash is not zero + assert(block_hash != 0, messages::STARTING_ENTROPY_ZERO); + + // starting entropy can only be set once + assert( + self._starting_entropy.read(adventurer_id) == 0, + messages::STARTING_ENTROPY_ALREADY_SET + ); + + // save starting entropy + self._starting_entropy.write(adventurer_id, block_hash); + } + /// @title Explore Function /// /// @notice Allows an adventurer to explore @@ -335,9 +367,6 @@ mod Game { @self, immutable_adventurer, adventurer_id, game_entropy ); - // update players last action block - adventurer.set_last_action_block(block_number); - // process attack or apply idle penalty if !idle { // get weapon specials @@ -440,7 +469,7 @@ mod Game { // if adventurer died while attempting to flee, process death if adventurer.health == 0 { - _process_adventurer_death(ref self, adventurer, adventurer_id, beast.id, 0); + _process_adventurer_death(ref self, ref adventurer, adventurer_id, beast.id, 0); } } else { _apply_idle_penalty(ref self, adventurer_id, ref adventurer, num_blocks); @@ -497,7 +526,7 @@ mod Game { // if adventurer died from counter attack, process death if (adventurer.health == 0) { - _process_adventurer_death(ref self, adventurer, adventurer_id, beast.id, 0); + _process_adventurer_death(ref self, ref adventurer, adventurer_id, beast.id, 0); } } @@ -518,9 +547,7 @@ mod Game { /// @param items A u8 Array representing the IDs of the items to drop. fn drop(ref self: ContractState, adventurer_id: felt252, items: Array) { // load player assets - let (mut adventurer, adventurer_entropy, game_entropy, mut bag) = _load_player_assets( - @self, adventurer_id - ); + let (mut adventurer, _, _, mut bag) = _load_player_assets(@self, adventurer_id); // assert action is valid (ownership of item is handled in internal function when we iterate over items) _assert_ownership(@self, adventurer_id); @@ -560,7 +587,7 @@ mod Game { items: Array, ) { // load player assets - let (mut adventurer, adventurer_entropy, game_entropy, mut bag) = _load_player_assets( + let (mut adventurer, _, game_entropy, mut bag) = _load_player_assets( @self, adventurer_id ); @@ -663,6 +690,19 @@ mod Game { } } + fn slay_invalid_adventurers(ref self: ContractState, adventurer_ids: Array) { + let mut adventurer_index: u32 = 0; + loop { + if adventurer_index == adventurer_ids.len() { + break; + } + let adventurer_id = *adventurer_ids.at(adventurer_index); + _slay_invalid_adventurer(ref self, adventurer_id); + adventurer_index += 1; + } + } + + /// @title Rotate Game Entropy Function /// /// @notice Rotates the game entropy @@ -696,6 +736,19 @@ mod Game { fn get_adventurer_meta(self: @ContractState, adventurer_id: felt252) -> AdventurerMetadata { _load_adventurer_metadata(self, adventurer_id) } + fn get_adventurer_starting_entropy( + self: @ContractState, adventurer_id: felt252 + ) -> felt252 { + self._starting_entropy.read(adventurer_id) + } + fn get_contract_calculated_entropy( + self: @ContractState, adventurer_id: felt252 + ) -> felt252 { + _get_starting_block_hash(self, adventurer_id) + } + fn get_adventurer_entropy(self: @ContractState, adventurer_id: felt252) -> felt252 { + _get_adventurer_entropy(self, adventurer_id) + } fn get_bag(self: @ContractState, adventurer_id: felt252) -> Bag { _load_bag(self, adventurer_id) } @@ -1086,7 +1139,27 @@ mod Game { adventurer.health = 0; // handle adventurer death - _process_adventurer_death(ref self, adventurer, adventurer_id, 0, 0,); + _process_adventurer_death(ref self, ref adventurer, adventurer_id, 0, 0,); + + // save adventurer (gg) + _save_adventurer_no_boosts(ref self, adventurer, adventurer_id); + } + + fn _slay_invalid_adventurer(ref self: ContractState, adventurer_id: felt252) { + // unpack adventurer from storage (no need for stat boosts) + let mut adventurer = _load_adventurer_no_boosts(@self, adventurer_id); + + // assert adventurer is not already dead + _assert_not_dead(adventurer); + + // assert adventurer is invalid + _assert_invalid_starting_entropy(@self, adventurer_id); + + // slay adventurer by setting health to 0 + adventurer.health = 0; + + // handle adventurer death + _process_adventurer_death(ref self, ref adventurer, adventurer_id, 0, 0,); // save adventurer (gg) _save_adventurer_no_boosts(ref self, adventurer, adventurer_id); @@ -1105,6 +1178,9 @@ mod Game { // zero out beast health adventurer.beast_health = 0; + // update players last action block + adventurer.set_last_action_block(starknet::get_block_info().unbox().block_number); + // get gold reward and increase adventurers gold let gold_earned = beast.get_gold_reward(beast_seed); let ring_bonus = adventurer.ring.jewelry_gold_bonus(gold_earned); @@ -1156,8 +1232,15 @@ mod Game { owner_address, _load_adventurer_metadata(@self, adventurer_id).interface_camel ); - // adventurers gets the beast - _mint_beast(@self, beast, primary_address); + // check if starting entropy is valid + if _is_starting_entropy_valid(@self, adventurer_id) { + // if starting entropy is valid, mint the beast + _mint_beast(@self, beast, primary_address); + } else { + // if starting entropy is not valid, kill adventurer + adventurer.health = 0; + _process_adventurer_death(ref self, ref adventurer, adventurer_id, beast.id, 0); + } } } @@ -1218,7 +1301,7 @@ mod Game { fn _process_adventurer_death( ref self: ContractState, - adventurer: Adventurer, + ref adventurer: Adventurer, adventurer_id: felt252, beast_id: u8, obstacle_id: u8 @@ -1235,8 +1318,47 @@ mod Game { __event_AdventurerDied(ref self, AdventurerDied { adventurer_state, death_details }); - if _is_top_score(@self, adventurer.xp) { - _update_leaderboard(ref self, adventurer_id, adventurer); + // if starting entropy is valid + if _is_starting_entropy_valid(@self, adventurer_id) { + // and adventurer got a top score + if _is_top_score(@self, adventurer.xp) { + // update the leaderboard + _update_leaderboard(ref self, adventurer_id, adventurer); + } + } else { + // if starting entropy is not valid we invalidate adventurer's game + adventurer.invalidate_game(); + } + } + + fn _assert_invalid_starting_entropy(self: @ContractState, adventurer_id: felt252) { + assert( + !_is_starting_entropy_valid(self, adventurer_id), messages::STARTING_ENTROPY_IS_VALID + ); + } + + // @title Assert Valid Block Hash Availability + // @notice This function asserts that a valid block hash is available for the given adventurer. + // @dev If this function is called prior to 2 blocks from the start of the game for the given adventurer, an exception will be thrown. + // @param adventurer_id The ID of the adventurer for which to check the block hash availability. + fn _assert_valid_block_hash_available(self: @ContractState, adventurer_id: felt252) { + let current_block = starknet::get_block_info().unbox().block_number; + let adventurer_start_block = _load_adventurer_metadata(self, adventurer_id).start_block; + let block_hash_available = adventurer_start_block + 2; + assert(current_block >= block_hash_available, messages::VALID_BLOCK_HASH_UNAVAILABLE); + } + + // @title Check if starting entropy is valid + // @notice This function checks if the starting entropy provided to the contract is equal to the entropy generated by the contract based on the block hash of the block after the player committed to playing the game. + // @dev If no manual starting entropy was provided, the starting entropy is considered valid. If a starting entropy was manually provided, it is compared with the block hash of the block after the player committed to playing the game. + // @param adventurer_id The ID of the adventurer for which to check the starting entropy. + // @return Returns true if the starting entropy is valid, false otherwise. + fn _is_starting_entropy_valid(self: @ContractState, adventurer_id: felt252) -> bool { + let starting_entropy = self._starting_entropy.read(adventurer_id); + if starting_entropy == 0 { + true + } else { + starting_entropy == _get_starting_block_hash(self, adventurer_id) } } @@ -1409,7 +1531,6 @@ mod Game { // use current starknet block number and timestamp as entropy sources let current_block = starknet::get_block_info().unbox().block_number; - let block_timestamp = starknet::get_block_info().unbox().block_timestamp; // randomness for starter beast isn't sensitive so we can use basic entropy let starter_beast_rnd = _get_basic_entropy(adventurer_id, current_block); @@ -1506,6 +1627,14 @@ mod Game { }, ExploreResult::Discovery(()) => { _process_discovery(ref self, ref adventurer, adventurer_id, rnd2); + _explore( + ref self, + ref adventurer, + adventurer_id, + adventurer_entropy, + game_entropy, + explore_till_beast + ) } } @@ -1582,7 +1711,7 @@ mod Game { ); __event_AmbushedByBeast(ref self, adventurer, adventurer_id, beast_battle_details); if (adventurer.health == 0) { - _process_adventurer_death(ref self, adventurer, adventurer_id, beast.id, 0); + _process_adventurer_death(ref self, ref adventurer, adventurer_id, beast.id, 0); return; } } else { @@ -1604,8 +1733,7 @@ mod Game { let armor = adventurer.get_item_at_slot(damage_slot); // get damage from obstalce - let (combat_result, jewlery_armor_bonus) = adventurer - .get_obstacle_damage(obstacle, armor, entropy); + let (combat_result, _) = adventurer.get_obstacle_damage(obstacle, armor, entropy); // pull damage taken out of combat result for easy access let damage_taken = combat_result.total_damage; @@ -1642,7 +1770,7 @@ mod Game { ref self, adventurer, adventurer_id, dodged, obstacle_details ); // process death - _process_adventurer_death(ref self, adventurer, adventurer_id, 0, obstacle.id); + _process_adventurer_death(ref self, ref adventurer, adventurer_id, 0, obstacle.id); // return without granting xp to adventurer or items return; } @@ -1866,7 +1994,7 @@ mod Game { // if adventurer is dead if (adventurer.health == 0) { - _process_adventurer_death(ref self, adventurer, adventurer_id, beast.id, 0); + _process_adventurer_death(ref self, ref adventurer, adventurer_id, beast.id, 0); return; } @@ -1919,7 +2047,7 @@ mod Game { let armor_specials = _get_item_specials(@self, adventurer_id, armor); // process beast attack - let (combat_result, jewlery_armor_bonus) = adventurer + let (combat_result, _jewlery_armor_bonus) = adventurer .defend(beast, armor, armor_specials, entropy); // deduct damage taken from adventurer's health @@ -2071,7 +2199,7 @@ mod Game { let mut unequipped_items = ArrayTrait::::new(); // get a clone of our items to equip to keep ownership for event - let equipped_items = items_to_equip.clone(); + let _equipped_items = items_to_equip.clone(); // for each item we need to equip let mut i: u32 = 0; @@ -2093,7 +2221,7 @@ mod Game { // create new item, equip it, and record if we need unequipped an item let mut new_item = ImplItemPrimitive::new(item_id); - new_item.set_metadata_id(adventurer, bag); + new_item.set_metadata_id(adventurer, bag, true); unequipped_item_id = _equip_item(contract_state, ref adventurer, ref bag, adventurer_id, new_item); } else { @@ -2748,7 +2876,7 @@ mod Game { __event_IdleDeathPenalty(ref self, adventurer, adventurer_id, idle_blocks); // process adventurer death - _process_adventurer_death(ref self, adventurer, adventurer_id, 0, 0); + _process_adventurer_death(ref self, ref adventurer, adventurer_id, 0, 0); } #[inline(always)] fn _lords_address(self: @ContractState) -> ContractAddress { @@ -2814,7 +2942,7 @@ mod Game { let adventurer_entropy = _get_adventurer_entropy(self, adventurer_id); // get beast and beast seed - let (beast, beast_seed) = adventurer.get_beast(adventurer_entropy); + let (beast, _) = adventurer.get_beast(adventurer_entropy); // return beast beast @@ -2883,10 +3011,22 @@ mod Game { #[inline(always)] fn _get_adventurer_entropy(self: @ContractState, adventurer_id: felt252) -> felt252 { - // get the block the adventurer started the game on + // if player used optimistic start and manually set starting hash + let starting_entropy = self._starting_entropy.read(adventurer_id); + if starting_entropy != 0 { + // use it + starting_entropy + } else { + // otherwise use block hash + _get_starting_block_hash(self, adventurer_id) + } + } + + fn _get_starting_block_hash(self: @ContractState, adventurer_id: felt252) -> felt252 { + // otherwise we'll get start entropy from the block hash after the start block let start_block = _load_adventurer_metadata(self, adventurer_id).start_block; - // adventurer_ + // don't force block delay on testnet to remove this source of friction let chain_id = starknet::get_execution_info().unbox().tx_info.unbox().chain_id; if chain_id == MAINNET_CHAIN_ID { _get_mainnet_entropy(adventurer_id, start_block) @@ -2907,7 +3047,7 @@ mod Game { #[inline(always)] fn _get_testnet_entropy(adventurer_id: felt252, start_block: u64) -> felt252 { ImplAdventurer::get_entropy( - adventurer_id, starknet::get_block_hash_syscall(start_block - 10).unwrap_syscall() + adventurer_id, starknet::get_block_hash_syscall(start_block + 1).unwrap_syscall() ) } @@ -3353,12 +3493,8 @@ mod Game { let discovery = Discovery { adventurer_state, amount }; match discovery_type { - DiscoveryType::Gold => { - self.emit(DiscoveredGold { discovery }); - }, - DiscoveryType::Health => { - self.emit(DiscoveredHealth { discovery }); - } + DiscoveryType::Gold => { self.emit(DiscoveredGold { discovery }); }, + DiscoveryType::Health => { self.emit(DiscoveredHealth { discovery }); } } } diff --git a/contracts/game/src/tests/test_game.cairo b/contracts/game/src/tests/test_game.cairo index 917a3c324..ead378dc1 100644 --- a/contracts/game/src/tests/test_game.cairo +++ b/contracts/game/src/tests/test_game.cairo @@ -1,5 +1,6 @@ #[cfg(test)] mod tests { + use debug::PrintTrait; use arcade_account::{Account, TRANSACTION_VERSION}; use arcade_account::tests::utils::helper_contracts::{ ISimpleTestContractDispatcher, ISimpleTestContractDispatcherTrait, simple_test_contract, @@ -22,7 +23,7 @@ mod tests { use openzeppelin::token::erc20::interface::{ IERC20Camel, IERC20CamelDispatcher, IERC20CamelDispatcherTrait, IERC20CamelLibraryDispatcher }; - use openzeppelin::token::erc20::erc20::ERC20; + use openzeppelin::token::erc20::interface::IERC20; use market::market::{ImplMarket, LootWithPrice, ItemPurchase}; use lootitems::{loot::{Loot, ImplLoot, ILoot}, constants::{ItemId}}; use game::{ @@ -38,7 +39,7 @@ mod tests { } }; use openzeppelin::utils::serde::SerializedAppend; - use openzeppelin::tests::mocks::camel20_mock::CamelERC20Mock; + use openzeppelin::presets::erc20::ERC20; use openzeppelin::tests::utils; use combat::{constants::CombatEnums::{Slot, Tier}, combat::ImplCombat}; use survivor::{ @@ -51,6 +52,19 @@ mod tests { }; use beasts::constants::{BeastSettings, BeastId}; + use golden_token::GoldenToken::{ + IGoldenToken, IGoldenTokenDispatcher, IGoldenTokenDispatcherTrait, + IGoldenTokenLibraryDispatcher + }; + + const ADVENTURER_ID: felt252 = 1; + const MAX_LORDS: u256 = 10000000000000000000000000000000000000000; + const APPROVE: u256 = 10000000000000000000000000000000000000000; + const NAME: felt252 = 111; + const SYMBOL: felt252 = 222; + const DEFAULT_NO_GOLDEN_TOKEN: felt252 = 0; + const DAY: u64 = 86400; + fn INTERFACE_ID() -> ContractAddress { contract_address_const::<1>() } @@ -63,62 +77,31 @@ mod tests { contract_address_const::<1>() } - const ADVENTURER_ID: felt252 = 1; - - const MAX_LORDS: u256 = 10000000000000000000000000000000000000000; - const APPROVE: u256 = 10000000000000000000000000000000000000000; - const NAME: felt252 = 111; - const SYMBOL: felt252 = 222; - - const DEFAULT_NO_GOLDEN_TOKEN: felt252 = 0; - - const DAY: u64 = 86400; - fn OWNER() -> ContractAddress { contract_address_const::<10>() } - use goldenToken::ERC721::{ - GoldenToken, GoldenTokenDispatcher, GoldenTokenDispatcherTrait, GoldenTokenLibraryDispatcher - }; - - - fn deploy_golden_token(eth: ContractAddress) -> GoldenTokenDispatcher { + fn deploy_golden_token(eth: ContractAddress) -> IGoldenTokenDispatcher { let mut calldata = ArrayTrait::new(); - calldata.append(NAME); + calldata.append(SYMBOL); calldata.append(OWNER().into()); calldata.append(DAO().into()); calldata.append(eth.into()); - let (golden_token, _) = deploy_syscall( - goldenToken::ERC721::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + let contract_address = utils::deploy(golden_token::GoldenToken::TEST_CLASS_HASH, calldata); - GoldenTokenDispatcher { contract_address: golden_token } + IGoldenTokenDispatcher { contract_address } } - fn deploy_lords() -> ContractAddress { + fn deploy_erc20() -> IERC20CamelDispatcher { let mut calldata = array![]; calldata.append_serde(NAME); calldata.append_serde(SYMBOL); calldata.append_serde(MAX_LORDS); calldata.append_serde(OWNER()); - let lords0 = utils::deploy(CamelERC20Mock::TEST_CLASS_HASH, calldata); - - lords0 - } - - fn deploy_eth() -> ContractAddress { - let mut calldata = array![]; - // we just need an erc20 for ETH, details don't matter for test purposes - calldata.append_serde(NAME); - calldata.append_serde(SYMBOL); - calldata.append_serde(MAX_LORDS); - calldata.append_serde(OWNER()); - utils::deploy(CamelERC20Mock::TEST_CLASS_HASH, calldata) + IERC20CamelDispatcher { contract_address: utils::deploy(ERC20::TEST_CLASS_HASH, calldata) } } @@ -176,40 +159,48 @@ mod tests { utils::deploy(AA_CLASS_HASH(), calldata) } + fn deploy_game( + lords: ContractAddress, golden_token: ContractAddress, terminal_block: u64 + ) -> IGameDispatcher { + let mut calldata = ArrayTrait::new(); + calldata.append(lords.into()); + calldata.append(DAO().into()); + calldata.append(COLLECTIBLE_BEASTS().into()); + calldata.append(golden_token.into()); + calldata.append(terminal_block.into()); + + IGameDispatcher { contract_address: utils::deploy(Game::TEST_CLASS_HASH, calldata) } + } + fn setup( starting_block: u64, starting_timestamp: u64, terminal_block: u64 - ) -> (IGameDispatcher, IERC20CamelDispatcher, GoldenTokenDispatcher, ContractAddress) { + ) -> (IGameDispatcher, IERC20CamelDispatcher, IGoldenTokenDispatcher, ContractAddress) { testing::set_block_number(starting_block); testing::set_block_timestamp(starting_timestamp); // deploy lords, eth, and golden token - let lords = deploy_lords(); - let eth = deploy_eth(); - let golden_token = deploy_golden_token(eth); + let lords = deploy_erc20(); - // format call data and deploy loot survivor - let mut calldata = ArrayTrait::new(); - calldata.append(lords.into()); - calldata.append(DAO().into()); - calldata.append(COLLECTIBLE_BEASTS().into()); - calldata.append(golden_token.contract_address.into()); - calldata.append(terminal_block.into()); - let (address0, _) = deploy_syscall( - Game::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); + // deploy eth + let eth = deploy_erc20(); + + // deploy golden token + let golden_token = deploy_golden_token(eth.contract_address); + + // deploy game + let game = deploy_game( + lords.contract_address, golden_token.contract_address, terminal_block + ); // set contract address (aka caller) to specific address testing::set_contract_address(OWNER()); // transfer lords to caller address and approve - let lords_contract = IERC20CamelDispatcher { contract_address: lords }; - let eth_contract = IERC20CamelDispatcher { contract_address: eth }; - lords_contract.transfer(OWNER(), 100000000000000000000000000000000); - eth_contract.transfer(OWNER(), 100000000000000000000000000000000); + lords.transfer(OWNER(), 100000000000000000000000000000000); + eth.transfer(OWNER(), 100000000000000000000000000000000); // give golden token contract approval to access ETH - eth_contract.approve(golden_token.contract_address, APPROVE.into()); + eth.approve(golden_token.contract_address, APPROVE.into()); // open golden token open edition golden_token.open(); // mint golden token @@ -218,22 +209,20 @@ mod tests { let arcade_account = ArcadeAccountABIDispatcher { contract_address: deploy_arcade_account(Option::None(())) }; + let master_control_dispatcher = IMasterControlDispatcher { contract_address: arcade_account.contract_address }; - master_control_dispatcher.update_whitelisted_contracts(array![(address0, true)]); + master_control_dispatcher + .update_whitelisted_contracts(array![(game.contract_address, true)]); - lords_contract.transfer(arcade_account.contract_address, 1000000000000000000000000); + lords.transfer(arcade_account.contract_address, 1000000000000000000000000); testing::set_contract_address(arcade_account.contract_address); - lords_contract.approve(address0, APPROVE.into()); - ( - IGameDispatcher { contract_address: address0 }, - lords_contract, - golden_token, - arcade_account.contract_address - ) + lords.approve(game.contract_address, APPROVE.into()); + + (game, lords, golden_token, arcade_account.contract_address) } fn add_adventurer_to_game(ref game: IGameDispatcher, golden_token_id: u256) { @@ -250,7 +239,7 @@ mod tests { fn new_adventurer(starting_block: u64, starting_time: u64) -> IGameDispatcher { let terminal_block = 0; - let (mut game, lords, _, _) = setup(starting_block, starting_time, terminal_block); + let (mut game, _, _, _) = setup(starting_block, starting_time, terminal_block); let starting_weapon = ItemId::Wand; let name = 'abcdefghijklmno'; @@ -294,10 +283,17 @@ mod tests { game } - fn new_adventurer_lvl2(starting_block: u64, starting_time: u64) -> IGameDispatcher { + fn new_adventurer_lvl2( + starting_block: u64, starting_time: u64, starting_entropy: felt252 + ) -> IGameDispatcher { // start game let mut game = new_adventurer(starting_block, starting_time); + if (starting_entropy != 0) { + testing::set_block_number(starting_block + 2); + game.set_starting_entropy(ADVENTURER_ID, starting_entropy); + } + // attack starter beast game.attack(ADVENTURER_ID, false); @@ -311,36 +307,33 @@ mod tests { game } - fn new_adventurer_lvl3(starting_block: u64) -> IGameDispatcher { - // start game on lvl 2 - let starting_time = 1696201757; - let mut game = new_adventurer_lvl2(starting_block, starting_time); + fn new_adventurer_lvl3( + starting_block: u64, starting_time: u64, starting_entropy: felt252 + ) -> IGameDispatcher { + let mut game = new_adventurer_lvl2(starting_block, starting_time, starting_entropy); let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart.clone()); // go explore game.explore(ADVENTURER_ID, true); - game.flee(ADVENTURER_ID, false); - game.explore(ADVENTURER_ID, true); - game.flee(ADVENTURER_ID, false); - game.explore(ADVENTURER_ID, true); - game.flee(ADVENTURER_ID, false); - game.explore(ADVENTURER_ID, true); + game.flee(ADVENTURER_ID, true); let adventurer = game.get_adventurer(ADVENTURER_ID); assert(adventurer.get_level() == 3, 'adventurer should be lvl 3'); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); // return game game } fn new_adventurer_lvl4(stat: u8) -> IGameDispatcher { - // start game on lvl 2 - let mut game = new_adventurer_lvl3(123); + // start game on lvl 4 + let starting_time = 1696201757; + let mut game = new_adventurer_lvl3(123, starting_time, 0); // upgrade charisma let shopping_cart = ArrayTrait::::new(); @@ -399,7 +392,6 @@ mod tests { fn new_adventurer_lvl6_equipped(stat: u8) -> IGameDispatcher { let mut game = new_adventurer_lvl5(stat); - let adventurer = game.get_adventurer(ADVENTURER_ID); let weapon_inventory = @game .get_items_on_market_by_slot(ADVENTURER_ID, ImplCombat::slot_to_u8(Slot::Weapon(()))); @@ -463,7 +455,6 @@ mod tests { fn new_adventurer_lvl7_equipped(stat: u8) -> IGameDispatcher { let mut game = new_adventurer_lvl6_equipped(stat); - let STRENGTH: u8 = 0; let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { strength: stat, @@ -487,7 +478,6 @@ mod tests { fn new_adventurer_lvl8_equipped(stat: u8) -> IGameDispatcher { let mut game = new_adventurer_lvl7_equipped(stat); - let STRENGTH: u8 = 0; let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { strength: stat, @@ -511,7 +501,6 @@ mod tests { fn new_adventurer_lvl9_equipped(stat: u8) -> IGameDispatcher { let mut game = new_adventurer_lvl8_equipped(stat); - let STRENGTH: u8 = 0; let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { strength: stat, @@ -535,7 +524,6 @@ mod tests { fn new_adventurer_lvl10_equipped(stat: u8) -> IGameDispatcher { let mut game = new_adventurer_lvl9_equipped(stat); - let STRENGTH: u8 = 0; let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { strength: stat, @@ -562,7 +550,6 @@ mod tests { fn new_adventurer_lvl11_equipped(stat: u8) -> IGameDispatcher { let mut game = new_adventurer_lvl10_equipped(stat); - let STRENGTH: u8 = 0; let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { strength: stat, @@ -622,11 +609,11 @@ mod tests { #[test] #[available_gas(300000000000)] fn test_start() { - let mut game = new_adventurer(1000, 1696201757); - - let adventurer_1 = game.get_adventurer(ADVENTURER_ID); - let adventurer_meta_1 = game.get_adventurer_meta(ADVENTURER_ID); + let game = new_adventurer(1000, 1696201757); + game.get_adventurer(ADVENTURER_ID); + game.get_adventurer_meta(ADVENTURER_ID); } + #[test] #[should_panic(expected: ('Action not allowed in battle', 'ENTRYPOINT_FAILED'))] #[available_gas(900000000)] @@ -638,7 +625,6 @@ mod tests { // is annotated in the test game.explore(ADVENTURER_ID, true); } - #[test] #[should_panic] #[available_gas(90000000)] @@ -719,7 +705,7 @@ mod tests { #[available_gas(63000000)] fn test_cant_flee_outside_battle() { // start adventuer and advance to level 2 - let mut game = new_adventurer_lvl2(1000, 1696201757); + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); // attempt to flee despite not being in a battle // this should trigger a panic 'Not in battle' which is @@ -731,7 +717,7 @@ mod tests { #[available_gas(13000000000)] fn test_flee() { // start game on level 2 - let mut game = new_adventurer_lvl2(1003, 1696201757); + let mut game = new_adventurer_lvl2(1003, 1696201757, 0); // perform upgrade let shopping_cart = ArrayTrait::::new(); @@ -803,7 +789,7 @@ mod tests { #[available_gas(73000000)] fn test_buy_items_without_stat_upgrade() { // mint adventurer and advance to level 2 - let mut game = new_adventurer_lvl2(1000, 1696201757); + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); // get valid item from market let market_items = @game.get_items_on_market(ADVENTURER_ID); @@ -829,7 +815,7 @@ mod tests { #[available_gas(62000000)] fn test_buy_duplicate_item_equipped() { // start new game on level 2 so we have access to the market - let mut game = new_adventurer_lvl2(1000, 1696201757); + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); // get items from market let market_items = @game.get_items_on_market_by_tier(ADVENTURER_ID, 5); @@ -853,7 +839,7 @@ mod tests { #[available_gas(61000000)] fn test_buy_duplicate_item_bagged() { // start new game on level 2 so we have access to the market - let mut game = new_adventurer_lvl2(1000, 1696201757); + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); // get items from market let market_items = @game.get_items_on_market(ADVENTURER_ID); @@ -875,7 +861,7 @@ mod tests { #[should_panic(expected: ('Market item does not exist', 'ENTRYPOINT_FAILED'))] #[available_gas(65000000)] fn test_buy_item_not_on_market() { - let mut game = new_adventurer_lvl2(1000, 1696201757); + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); let mut shopping_cart = ArrayTrait::::new(); shopping_cart.append(ItemPurchase { item_id: 255, equip: false }); let stat_upgrades = Stats { @@ -887,7 +873,7 @@ mod tests { #[test] #[available_gas(65000000)] fn test_buy_and_bag_item() { - let mut game = new_adventurer_lvl2(1000, 1696201757); + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); let market_items = @game.get_items_on_market(ADVENTURER_ID); let item_id = *market_items.at(0); let mut shopping_cart = ArrayTrait::::new(); @@ -904,7 +890,7 @@ mod tests { #[available_gas(71000000)] fn test_buy_items() { // start game on level 2 so we have access to the market - let mut game = new_adventurer_lvl2(1000, 1696201757); + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); // get items from market let market_items = @game.get_items_on_market(ADVENTURER_ID); @@ -915,8 +901,6 @@ mod tests { let mut purchased_waist: u8 = 0; let mut purchased_foot: u8 = 0; let mut purchased_hand: u8 = 0; - let mut purchased_ring: u8 = 0; - let mut purchased_necklace: u8 = 0; let mut shopping_cart = ArrayTrait::::new(); let mut i: u32 = 0; @@ -978,7 +962,6 @@ mod tests { let mut buy_and_equip_tested = false; let mut buy_and_bagged_tested = false; - let mut items_to_equip = ArrayTrait::::new(); // iterate over the items we bought let mut i: u32 = 0; loop { @@ -1007,7 +990,7 @@ mod tests { #[test] #[should_panic(expected: ('Item not in bag', 'ENTRYPOINT_FAILED'))] - #[available_gas(26009820)] + #[available_gas(26022290)] fn test_equip_not_in_bag() { // start new game let mut game = new_adventurer(1000, 1696201757); @@ -1051,14 +1034,11 @@ mod tests { #[available_gas(92000000)] fn test_equip() { // start game on level 2 so we have access to the market - let mut game = new_adventurer_lvl2(1002, 1696201757); + let mut game = new_adventurer_lvl2(1002, 1696201757, 0); // get items from market let market_items = @game.get_items_on_market_by_tier(ADVENTURER_ID, 5); - // get first item on the market - let item_id = *market_items.at(0); - let mut purchased_weapon: u8 = 0; let mut purchased_chest: u8 = 0; let mut purchased_head: u8 = 0; @@ -1174,7 +1154,7 @@ mod tests { #[test] #[available_gas(100000000)] fn test_buy_potions() { - let mut game = new_adventurer_lvl2(1000, 1696201757); + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); // get updated adventurer state let adventurer = game.get_adventurer(ADVENTURER_ID); @@ -1208,7 +1188,7 @@ mod tests { #[should_panic(expected: ('Health already full', 'ENTRYPOINT_FAILED'))] #[available_gas(450000000)] fn test_buy_potions_exceed_max_health() { - let mut game = new_adventurer_lvl2(1000, 1696201757); + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); // get updated adventurer state let adventurer = game.get_adventurer(ADVENTURER_ID); @@ -1235,7 +1215,7 @@ mod tests { #[available_gas(100000000)] fn test_cant_buy_potion_without_stat_upgrade() { // deploy and start new game - let mut game = new_adventurer_lvl2(1000, 1696201757); + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); // upgrade adventurer let shopping_cart = ArrayTrait::::new(); @@ -1395,9 +1375,6 @@ mod tests { // deploy and start new game let mut game = new_adventurer(STARTING_BLOCK_NUMBER, 1696201757); - // get adventurer state - let adventurer = game.get_adventurer(ADVENTURER_ID); - // attack starter beast, resulting in adventurer last action block number being 1 game.attack(ADVENTURER_ID, false); @@ -1444,9 +1421,6 @@ mod tests { // deploy and start new game let mut game = new_adventurer(STARTING_BLOCK_NUMBER, 1696201757); - // get adventurer state - let adventurer = game.get_adventurer(ADVENTURER_ID); - // roll the blockchain back 1 block to simulate mainnet start_game scenario // where the adventurers last_action will be set to 11 blocks in the future // to account for the commit-and-reveal delay @@ -1465,10 +1439,9 @@ mod tests { // deploy and start new game let mut game = new_adventurer(STARTING_BLOCK_NUMBER, 1696201757); - // get adventurer state - let adventurer = game.get_adventurer(ADVENTURER_ID); - let adventurer2 = add_adventurer_to_game(ref game, 0); - let adventurer3 = add_adventurer_to_game(ref game, 0); + // add two adventurers to the game + add_adventurer_to_game(ref game, 0); + add_adventurer_to_game(ref game, 0); // attack starter beast, resulting in adventurer last action block number being 1 game.attack(ADVENTURER_ID, false); @@ -1531,7 +1504,7 @@ mod tests { let adventurer_level = game.get_adventurer(ADVENTURER_ID).get_level(); assert(potion_price == POTION_PRICE * adventurer_level.into(), 'wrong lvl1 potion price'); - let mut game = new_adventurer_lvl2(1000, 1696201757); + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); let potion_price = game.get_potion_price(ADVENTURER_ID); let adventurer_level = game.get_adventurer(ADVENTURER_ID).get_level(); assert(potion_price == POTION_PRICE * adventurer_level.into(), 'wrong lvl2 potion price'); @@ -1690,17 +1663,13 @@ mod tests { // game settings in the constructor. Commenting this out for now so our CI doesn't run it // #[test] // #[available_gas(80000000000)] - // fn test_max_items() { + // fn test_metadata_unique() { // // start game on level 2 so we have access to the market - // let mut game = new_adventurer_lvl2(1000,1696201757); - - // // get items from market - // let mut market_items = @game.get_items_on_market(ADVENTURER_ID); + // let mut game = new_adventurer_lvl2(1000, 1696201757, 0); - // // get first item on the market - // let item_id = *market_items.at(0).item.id; + // // get items from market + // let mut market_item_ids = game.get_items_on_market(ADVENTURER_ID); - // let mut purchased_weapon: u8 = 0; // let mut purchased_chest: u8 = 0; // let mut purchased_head: u8 = 0; // let mut purchased_waist: u8 = 0; @@ -1710,115 +1679,725 @@ mod tests { // let mut purchased_necklace: u8 = 0; // let mut shopping_cart = ArrayTrait::::new(); - // let mut i: u32 = 0; + // let mut bagged_items: u8 = 0; + // loop { - // if i >= market_items.len() { - // break (); - // } - // let market_item = *market_items.at(i).item; - - // // if the item is a weapon and we haven't purchased a weapon yet - // // and the item is a tier 4 or 5 item - // // repeat this for everything - // if (market_item.slot == Slot::Weapon(()) - // && purchased_weapon == 0 - // && market_item.id != 12) { - // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: false }); - // purchased_weapon = market_item.id; - // } else if (market_item.slot == Slot::Chest(()) && purchased_chest == 0) { - // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); - // purchased_chest = market_item.id; - // } else if (market_item.slot == Slot::Head(()) && purchased_head == 0) { - // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); - // purchased_head = market_item.id; - // } else if (market_item.slot == Slot::Waist(()) && purchased_waist == 0) { - // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); - // purchased_waist = market_item.id; - // } else if (market_item.slot == Slot::Foot(()) && purchased_foot == 0) { - // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); - // purchased_foot = market_item.id; - // } else if (market_item.slot == Slot::Hand(()) && purchased_hand == 0) { - // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); - // purchased_hand = market_item.id; - // } else if (market_item.slot == Slot::Ring(()) && purchased_ring == 0) { - // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); - // purchased_ring = market_item.id; - // } else if (market_item.slot == Slot::Neck(()) && purchased_necklace == 0) { - // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); - // purchased_necklace = market_item.id; + // match market_item_ids.pop_front() { + // Option::Some(item_id) => { + // let market_item = ImplLoot::get_item(item_id); + // // fill up all equipment slots + // if (market_item.slot == Slot::Chest(()) && purchased_chest == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_chest = market_item.id; + // } else if (market_item.slot == Slot::Head(()) && purchased_head == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_head = market_item.id; + // } else if (market_item.slot == Slot::Waist(()) && purchased_waist == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_waist = market_item.id; + // } else if (market_item.slot == Slot::Foot(()) && purchased_foot == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_foot = market_item.id; + // } else if (market_item.slot == Slot::Hand(()) && purchased_hand == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_hand = market_item.id; + // } else if (market_item.slot == Slot::Ring(()) && purchased_ring == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_ring = market_item.id; + // } else if (market_item.slot == Slot::Neck(()) && purchased_necklace == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_necklace = market_item.id; + // } else if bagged_items < 11 && item_id != ItemId::Wand { + // shopping_cart + // .append(ItemPurchase { item_id: market_item.id, equip: false }); + // bagged_items += 1; + // } + // }, + // Option::None => { break; }, // } - // i += 1; // }; // assert( - // purchased_weapon != 0 - // && purchased_chest != 0 + // purchased_chest != 0 // && purchased_head != 0 // && purchased_waist != 0 // && purchased_foot != 0 // && purchased_hand != 0 // && purchased_ring != 0 - // && purchased_necklace != 0, + // && purchased_necklace != 0 + // && bagged_items == 11, // 'did not purchase all items' // ); - // let mut i: u32 = 0; + // // buy items in shopping cart which will fully equip the adventurer + // // and fill their bag + // let potions = 0; + // let stat_upgrades = Stats { + // strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 + // }; + // game.upgrade(ADVENTURER_ID, potions, stat_upgrades, shopping_cart); + + // // verify adventurer is fully equipped except for ring + // let updated_adventurer = game.get_adventurer(ADVENTURER_ID); + // assert(updated_adventurer.is_equipped(purchased_chest), 'chest not equipped'); + // assert(updated_adventurer.is_equipped(purchased_head), 'head not equipped'); + // assert(updated_adventurer.is_equipped(purchased_waist), 'waist not equipped'); + // assert(updated_adventurer.is_equipped(purchased_foot), 'foot not equipped'); + // assert(updated_adventurer.is_equipped(purchased_hand), 'hand not equipped'); + // assert(updated_adventurer.is_equipped(purchased_ring), 'ring not equipped'); + // assert(updated_adventurer.is_equipped(purchased_necklace), 'necklace not equipped'); + + // // verify bag is full + // let bag = game.get_bag(ADVENTURER_ID); + // assert(bag.item_1.id != 0, 'bag item1 is empty'); + // assert(bag.item_2.id != 0, 'bag item2 is empty'); + // assert(bag.item_3.id != 0, 'bag item3 is empty'); + // assert(bag.item_4.id != 0, 'bag item4 is empty'); + // assert(bag.item_5.id != 0, 'bag item5 is empty'); + // assert(bag.item_6.id != 0, 'bag item6 is empty'); + // assert(bag.item_7.id != 0, 'bag item7 is empty'); + // assert(bag.item_8.id != 0, 'bag item8 is empty'); + // assert(bag.item_9.id != 0, 'bag item9 is empty'); + // assert(bag.item_10.id != 0, 'bag item10 is empty'); + // assert(bag.item_11.id != 0, 'bag item11 is empty'); + + // // drop first two items in our bag + // game.drop(ADVENTURER_ID, array![bag.item_1.id, bag.item_2.id, updated_adventurer.neck.id]); + + // // get updated bag and verify item_1 is now 0 + // let bag = game.get_bag(ADVENTURER_ID); + // assert(bag.item_1.id == 0, 'bag item1 should be empty'); + // assert(bag.item_2.id == 0, 'bag item2 should be empty'); + + // let adventurer = game.get_adventurer(ADVENTURER_ID); + // assert(adventurer.neck.id == 0, 'neck should be empty'); + + // // advance to next level + // game.explore(ADVENTURER_ID, true); + // game.attack(ADVENTURER_ID, true); + + // let updated_adventurer = game.get_adventurer(ADVENTURER_ID); + // let updated_bag = game.get_bag(ADVENTURER_ID); + + // // get items from market + // let mut market_item_ids = game.get_items_on_market(ADVENTURER_ID); + // let mut shopping_cart = ArrayTrait::::new(); + // loop { - // if i >= market_items.len() { - // break (); - // } - // let market_item = *market_items.at(i).item; - - // if (market_item.id == purchased_weapon - // || market_item.id == purchased_chest - // || market_item.id == purchased_head - // || market_item.id == purchased_waist - // || market_item.id == purchased_foot - // || market_item.id == purchased_hand - // || market_item.id == purchased_ring - // || market_item.id == purchased_necklace - // || shopping_cart.len() == 19) { - // i += 1; - // continue; + // match market_item_ids.pop_front() { + // Option::Some(item_id) => { + // let market_item = ImplLoot::get_item(item_id); + // if shopping_cart.len() < 2 + // && item_id != ItemId::Wand + // && item_id != updated_adventurer.weapon.id + // && item_id != updated_adventurer.chest.id + // && item_id != updated_adventurer.head.id + // && item_id != updated_adventurer.waist.id + // && item_id != updated_adventurer.foot.id + // && item_id != updated_adventurer.hand.id + // && item_id != updated_adventurer.ring.id + // && item_id != updated_bag.item_1.id + // && item_id != updated_bag.item_2.id + // && item_id != updated_bag.item_3.id + // && item_id != updated_bag.item_4.id + // && item_id != updated_bag.item_5.id + // && item_id != updated_bag.item_6.id + // && item_id != updated_bag.item_7.id + // && item_id != updated_bag.item_8.id + // && item_id != updated_bag.item_9.id + // && item_id != updated_bag.item_10.id + // && item_id != updated_bag.item_11.id { + // shopping_cart + // .append(ItemPurchase { item_id: market_item.id, equip: false }); + // } else if shopping_cart.len() == 2 { + // break; + // } + // }, + // Option::None => { break; }, // } + // }; - // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: false }); + // assert(shopping_cart.len() == 2, 'did not purchase enough items'); - // i += 1; + // // purchase items + // let potions = 0; + // let stat_upgrades = Stats { + // strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 // }; + // game.upgrade(ADVENTURER_ID, potions, stat_upgrades, shopping_cart); + + // // verify meta data for necklace and bagged item are different + // let updated_adventurer = game.get_adventurer(ADVENTURER_ID); + // let updated_bag = game.get_bag(ADVENTURER_ID); + // updated_adventurer.neck.metadata.print(); + // updated_bag.item_1.metadata.print(); + // updated_bag.item_2.metadata.print(); + // assert( + // updated_bag.item_1.metadata != updated_bag.item_2.metadata, + // 'neck and item1 share metadata' + // ); + // } + + fn already_owned(item_id: u8, adventurer: Adventurer, bag: Bag) -> bool { + item_id == adventurer.weapon.id + || item_id == adventurer.chest.id + || item_id == adventurer.head.id + || item_id == adventurer.waist.id + || item_id == adventurer.foot.id + || item_id == adventurer.hand.id + || item_id == adventurer.ring.id + || item_id == adventurer.neck.id + || item_id == bag.item_1.id + || item_id == bag.item_2.id + || item_id == bag.item_3.id + || item_id == bag.item_4.id + || item_id == bag.item_5.id + || item_id == bag.item_6.id + || item_id == bag.item_7.id + || item_id == bag.item_8.id + || item_id == bag.item_9.id + || item_id == bag.item_10.id + || item_id == bag.item_11.id + } + + // To run this test we need to increase starting gold so we can buy max number of items + // We either need to use cheat codes to accomplish this or have the contract take in + // game settings in the constructor. Commenting this out for now so our CI doesn't run it + // #[test] + // #[available_gas(80000000000)] + // fn test_max_out_and_recycle_items() { + // // start game on level 2 so we have access to the market + // let mut game = new_adventurer_lvl2(1000, 1696201757, 0); + + // // get items from market + // let mut market_item_ids = game.get_items_on_market(ADVENTURER_ID); + + // let mut purchased_chest: u8 = 0; + // let mut purchased_head: u8 = 0; + // let mut purchased_waist: u8 = 0; + // let mut purchased_foot: u8 = 0; + // let mut purchased_hand: u8 = 0; + // let mut purchased_ring: u8 = 0; + // let mut purchased_necklace: u8 = 0; + // let mut shopping_cart = ArrayTrait::::new(); - // // We intentionally loaded our cart with 19 items which would be one more than max - // // when you add our starter weapon. We did this so we could pop one item off the cart - // // and into this overflow shopping cart which we'll use later - // let mut overflow_item = shopping_cart.pop_front().unwrap(); - // overflow_item.equip = true; - // let mut overflow_shopping_cart = ArrayTrait::::new(); - // overflow_shopping_cart.append(overflow_item); + // let mut bagged_items: u8 = 0; - // // verify we have at least two items in shopping cart - // assert(shopping_cart.len() == 18, 'should be max items'); + // loop { + // match market_item_ids.pop_front() { + // Option::Some(item_id) => { + // let market_item = ImplLoot::get_item(item_id); + // // fill up all equipment slots + // if (market_item.slot == Slot::Chest(()) && purchased_chest == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_chest = market_item.id; + // } else if (market_item.slot == Slot::Head(()) && purchased_head == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_head = market_item.id; + // } else if (market_item.slot == Slot::Waist(()) && purchased_waist == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_waist = market_item.id; + // } else if (market_item.slot == Slot::Foot(()) && purchased_foot == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_foot = market_item.id; + // } else if (market_item.slot == Slot::Hand(()) && purchased_hand == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_hand = market_item.id; + // } else if (market_item.slot == Slot::Ring(()) && purchased_ring == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_ring = market_item.id; + // } else if (market_item.slot == Slot::Neck(()) && purchased_necklace == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_necklace = market_item.id; + // } else if bagged_items < 11 && item_id != ItemId::Wand { + // shopping_cart + // .append(ItemPurchase { item_id: market_item.id, equip: false }); + // bagged_items += 1; + // } + // }, + // Option::None => { break; }, + // } + // }; + + // assert( + // purchased_chest != 0 + // && purchased_head != 0 + // && purchased_waist != 0 + // && purchased_foot != 0 + // && purchased_hand != 0 + // && purchased_ring != 0 + // && purchased_necklace != 0 + // && bagged_items == 11, + // 'did not purchase all items' + // ); // // buy items in shopping cart which will fully equip the adventurer // // and fill their bag - // game.buy_items(ADVENTURER_ID, shopping_cart.clone()); + // let potions = 0; + // let stat_upgrades = Stats { + // strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 + // }; + // game.upgrade(ADVENTURER_ID, potions, stat_upgrades, shopping_cart); + + // // verify adventurer is fully equipped except for ring + // let updated_adventurer = game.get_adventurer(ADVENTURER_ID); + // assert(updated_adventurer.is_equipped(purchased_chest), 'chest not equipped'); + // assert(updated_adventurer.is_equipped(purchased_head), 'head not equipped'); + // assert(updated_adventurer.is_equipped(purchased_waist), 'waist not equipped'); + // assert(updated_adventurer.is_equipped(purchased_foot), 'foot not equipped'); + // assert(updated_adventurer.is_equipped(purchased_hand), 'hand not equipped'); + // assert(updated_adventurer.is_equipped(purchased_ring), 'ring not equipped'); + // assert(updated_adventurer.is_equipped(purchased_necklace), 'necklace not equipped'); + + // // verify bag is full + // let bag = game.get_bag(ADVENTURER_ID); + // assert(bag.item_1.id != 0, 'bag item1 is empty'); + // assert(bag.item_2.id != 0, 'bag item2 is empty'); + // assert(bag.item_3.id != 0, 'bag item3 is empty'); + // assert(bag.item_4.id != 0, 'bag item4 is empty'); + // assert(bag.item_5.id != 0, 'bag item5 is empty'); + // assert(bag.item_6.id != 0, 'bag item6 is empty'); + // assert(bag.item_7.id != 0, 'bag item7 is empty'); + // assert(bag.item_8.id != 0, 'bag item8 is empty'); + // assert(bag.item_9.id != 0, 'bag item9 is empty'); + // assert(bag.item_10.id != 0, 'bag item10 is empty'); + // assert(bag.item_11.id != 0, 'bag item11 is empty'); + + // // drop first two items in our bag + // game.drop(ADVENTURER_ID, array![bag.item_1.id, bag.item_2.id, updated_adventurer.neck.id]); + + // // get updated bag and verify item_1 is now 0 + // let bag = game.get_bag(ADVENTURER_ID); + // assert(bag.item_1.id == 0, 'bag item1 should be empty'); + // assert(bag.item_2.id == 0, 'bag item2 should be empty'); + + // let adventurer = game.get_adventurer(ADVENTURER_ID); + // assert(adventurer.neck.id == 0, 'neck should be empty'); + + // // advance to next level + // game.explore(ADVENTURER_ID, true); + // game.attack(ADVENTURER_ID, true); + + // let updated_adventurer = game.get_adventurer(ADVENTURER_ID); + // let updated_bag = game.get_bag(ADVENTURER_ID); + + // // get items from market + // let mut market_item_ids = game.get_items_on_market(ADVENTURER_ID); + // let mut shopping_cart = ArrayTrait::::new(); - // // drop our weapon and attempt (should free up an item slow) - // let mut items_to_drop = ArrayTrait::::new(); - // items_to_drop.append(12); - // game.drop(ADVENTURER_ID, items_to_drop); + // loop { + // match market_item_ids.pop_front() { + // Option::Some(item_id) => { + // let market_item = ImplLoot::get_item(item_id); + // if shopping_cart.len() < 2 + // && !already_owned(item_id, updated_adventurer, updated_bag) { + // shopping_cart + // .append(ItemPurchase { item_id: market_item.id, equip: false }); + // } else if shopping_cart.len() == 2 { + // break; + // } + // }, + // Option::None => { break; }, + // } + // }; - // game.buy_items(ADVENTURER_ID, overflow_shopping_cart.clone()); + // assert(shopping_cart.len() == 2, 'did not purchase enough items'); + + // // purchase items + // let potions = 0; + // let stat_upgrades = Stats { + // strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 + // }; + // game.upgrade(ADVENTURER_ID, potions, stat_upgrades, shopping_cart); - // // get updated adventurer and bag state + // // verify meta data for necklace and bagged item are different + // let updated_bag = game.get_bag(ADVENTURER_ID); + // assert( + // updated_bag.item_1.metadata != updated_bag.item_2.metadata, + // 'neck and item1 share metadata' + // ); + // assert( + // all_items_have_unique_metadata(updated_adventurer, updated_bag), 'items share metadata' + // ); + + // game.explore(ADVENTURER_ID, true); + // game.attack(ADVENTURER_ID, true); + + // // purchase necklace + // let mut market_item_ids = game.get_items_on_market(ADVENTURER_ID); + // let mut shopping_cart = ArrayTrait::::new(); + // loop { + // match market_item_ids.pop_front() { + // Option::Some(item_id) => { + // let market_item = ImplLoot::get_item(item_id); + // if (market_item.slot == Slot::Neck(()) && purchased_necklace == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // } + // }, + // Option::None => { break; }, + // } + // }; + // game.upgrade(ADVENTURER_ID, potions, stat_upgrades, shopping_cart); + + // // verify all meta data is still unique + // assert( + // all_items_have_unique_metadata(updated_adventurer, updated_bag), 'items share metadata' + // ); + // } + + fn all_items_have_unique_metadata(adventurer: Adventurer, bag: Bag) -> bool { + // @dev: don't judge me too harshly on this hacky implementation because AI bulk generated it in 10 seconds + // and ultimately a more elegant loop would only be minimally more efficient + adventurer.weapon.metadata != adventurer.chest.metadata + && adventurer.weapon.metadata != adventurer.head.metadata + && adventurer.weapon.metadata != adventurer.waist.metadata + && adventurer.weapon.metadata != adventurer.foot.metadata + && adventurer.weapon.metadata != adventurer.hand.metadata + && adventurer.weapon.metadata != adventurer.ring.metadata + && adventurer.weapon.metadata != adventurer.neck.metadata + && adventurer.weapon.metadata != bag.item_1.metadata + && adventurer.weapon.metadata != bag.item_2.metadata + && adventurer.weapon.metadata != bag.item_3.metadata + && adventurer.weapon.metadata != bag.item_4.metadata + && adventurer.weapon.metadata != bag.item_5.metadata + && adventurer.weapon.metadata != bag.item_6.metadata + && adventurer.weapon.metadata != bag.item_7.metadata + && adventurer.weapon.metadata != bag.item_8.metadata + && adventurer.weapon.metadata != bag.item_9.metadata + && adventurer.weapon.metadata != bag.item_10.metadata + && adventurer.weapon.metadata != bag.item_11.metadata + && adventurer.chest.metadata != adventurer.head.metadata + && adventurer.chest.metadata != adventurer.waist.metadata + && adventurer.chest.metadata != adventurer.foot.metadata + && adventurer.chest.metadata != adventurer.hand.metadata + && adventurer.chest.metadata != adventurer.ring.metadata + && adventurer.chest.metadata != adventurer.neck.metadata + && adventurer.chest.metadata != bag.item_1.metadata + && adventurer.chest.metadata != bag.item_2.metadata + && adventurer.chest.metadata != bag.item_3.metadata + && adventurer.chest.metadata != bag.item_4.metadata + && adventurer.chest.metadata != bag.item_5.metadata + && adventurer.chest.metadata != bag.item_6.metadata + && adventurer.chest.metadata != bag.item_7.metadata + && adventurer.chest.metadata != bag.item_8.metadata + && adventurer.chest.metadata != bag.item_9.metadata + && adventurer.chest.metadata != bag.item_10.metadata + && adventurer.chest.metadata != bag.item_11.metadata + && adventurer.head.metadata != adventurer.waist.metadata + && adventurer.head.metadata != adventurer.foot.metadata + && adventurer.head.metadata != adventurer.hand.metadata + && adventurer.head.metadata != adventurer.ring.metadata + && adventurer.head.metadata != adventurer.neck.metadata + && adventurer.head.metadata != bag.item_1.metadata + && adventurer.head.metadata != bag.item_2.metadata + && adventurer.head.metadata != bag.item_3.metadata + && adventurer.head.metadata != bag.item_4.metadata + && adventurer.head.metadata != bag.item_5.metadata + && adventurer.head.metadata != bag.item_6.metadata + && adventurer.head.metadata != bag.item_7.metadata + && adventurer.head.metadata != bag.item_8.metadata + && adventurer.head.metadata != bag.item_9.metadata + && adventurer.head.metadata != bag.item_10.metadata + && adventurer.head.metadata != bag.item_11.metadata + && adventurer.waist.metadata != adventurer.foot.metadata + && adventurer.waist.metadata != adventurer.hand.metadata + && adventurer.waist.metadata != adventurer.ring.metadata + && adventurer.waist.metadata != adventurer.neck.metadata + && adventurer.waist.metadata != bag.item_1.metadata + && adventurer.waist.metadata != bag.item_2.metadata + && adventurer.waist.metadata != bag.item_3.metadata + && adventurer.waist.metadata != bag.item_4.metadata + && adventurer.waist.metadata != bag.item_5.metadata + && adventurer.waist.metadata != bag.item_6.metadata + && adventurer.waist.metadata != bag.item_7.metadata + && adventurer.waist.metadata != bag.item_8.metadata + && adventurer.waist.metadata != bag.item_9.metadata + && adventurer.waist.metadata != bag.item_10.metadata + && adventurer.waist.metadata != bag.item_11.metadata + && adventurer.foot.metadata != adventurer.hand.metadata + && adventurer.foot.metadata != adventurer.ring.metadata + && adventurer.foot.metadata != adventurer.neck.metadata + && adventurer.foot.metadata != bag.item_1.metadata + && adventurer.foot.metadata != bag.item_2.metadata + && adventurer.foot.metadata != bag.item_3.metadata + && adventurer.foot.metadata != bag.item_4.metadata + && adventurer.foot.metadata != bag.item_5.metadata + && adventurer.foot.metadata != bag.item_6.metadata + && adventurer.foot.metadata != bag.item_7.metadata + && adventurer.foot.metadata != bag.item_8.metadata + && adventurer.foot.metadata != bag.item_9.metadata + && adventurer.foot.metadata != bag.item_10.metadata + && adventurer.foot.metadata != bag.item_11.metadata + && adventurer.hand.metadata != adventurer.ring.metadata + && adventurer.hand.metadata != adventurer.neck.metadata + && adventurer.hand.metadata != bag.item_1.metadata + && adventurer.hand.metadata != bag.item_2.metadata + && adventurer.hand.metadata != bag.item_3.metadata + && adventurer.hand.metadata != bag.item_4.metadata + && adventurer.hand.metadata != bag.item_5.metadata + && adventurer.hand.metadata != bag.item_6.metadata + && adventurer.hand.metadata != bag.item_7.metadata + && adventurer.hand.metadata != bag.item_8.metadata + && adventurer.hand.metadata != bag.item_9.metadata + && adventurer.hand.metadata != bag.item_10.metadata + && adventurer.hand.metadata != bag.item_11.metadata + && adventurer.ring.metadata != adventurer.neck.metadata + && adventurer.ring.metadata != bag.item_1.metadata + && adventurer.ring.metadata != bag.item_2.metadata + && adventurer.ring.metadata != bag.item_3.metadata + && adventurer.ring.metadata != bag.item_4.metadata + && adventurer.ring.metadata != bag.item_5.metadata + && adventurer.ring.metadata != bag.item_6.metadata + && adventurer.ring.metadata != bag.item_7.metadata + && adventurer.ring.metadata != bag.item_8.metadata + && adventurer.ring.metadata != bag.item_9.metadata + && adventurer.ring.metadata != bag.item_10.metadata + && adventurer.ring.metadata != bag.item_11.metadata + && adventurer.neck.metadata != bag.item_1.metadata + && adventurer.neck.metadata != bag.item_2.metadata + && adventurer.neck.metadata != bag.item_3.metadata + && adventurer.neck.metadata != bag.item_4.metadata + && adventurer.neck.metadata != bag.item_5.metadata + && adventurer.neck.metadata != bag.item_6.metadata + && adventurer.neck.metadata != bag.item_7.metadata + && adventurer.neck.metadata != bag.item_8.metadata + && adventurer.neck.metadata != bag.item_9.metadata + && adventurer.neck.metadata != bag.item_10.metadata + && adventurer.neck.metadata != bag.item_11.metadata + && bag.item_1.metadata != bag.item_2.metadata + && bag.item_1.metadata != bag.item_3.metadata + && bag.item_1.metadata != bag.item_4.metadata + && bag.item_1.metadata != bag.item_5.metadata + && bag.item_1.metadata != bag.item_6.metadata + && bag.item_1.metadata != bag.item_7.metadata + && bag.item_1.metadata != bag.item_8.metadata + && bag.item_1.metadata != bag.item_9.metadata + && bag.item_1.metadata != bag.item_10.metadata + && bag.item_1.metadata != bag.item_11.metadata + && bag.item_2.metadata != bag.item_3.metadata + && bag.item_2.metadata != bag.item_4.metadata + && bag.item_2.metadata != bag.item_5.metadata + && bag.item_2.metadata != bag.item_6.metadata + && bag.item_2.metadata != bag.item_7.metadata + && bag.item_2.metadata != bag.item_8.metadata + && bag.item_2.metadata != bag.item_9.metadata + && bag.item_2.metadata != bag.item_10.metadata + && bag.item_2.metadata != bag.item_11.metadata + && bag.item_3.metadata != bag.item_4.metadata + && bag.item_3.metadata != bag.item_5.metadata + && bag.item_3.metadata != bag.item_6.metadata + && bag.item_3.metadata != bag.item_7.metadata + && bag.item_3.metadata != bag.item_8.metadata + && bag.item_3.metadata != bag.item_9.metadata + && bag.item_3.metadata != bag.item_10.metadata + && bag.item_3.metadata != bag.item_11.metadata + && bag.item_4.metadata != bag.item_5.metadata + && bag.item_4.metadata != bag.item_6.metadata + && bag.item_4.metadata != bag.item_7.metadata + && bag.item_4.metadata != bag.item_8.metadata + && bag.item_4.metadata != bag.item_9.metadata + && bag.item_4.metadata != bag.item_10.metadata + && bag.item_4.metadata != bag.item_11.metadata + && bag.item_5.metadata != bag.item_6.metadata + && bag.item_5.metadata != bag.item_7.metadata + && bag.item_5.metadata != bag.item_8.metadata + && bag.item_5.metadata != bag.item_9.metadata + && bag.item_5.metadata != bag.item_10.metadata + && bag.item_5.metadata != bag.item_11.metadata + && bag.item_6.metadata != bag.item_7.metadata + && bag.item_6.metadata != bag.item_8.metadata + && bag.item_6.metadata != bag.item_9.metadata + && bag.item_6.metadata != bag.item_10.metadata + && bag.item_6.metadata != bag.item_11.metadata + && bag.item_7.metadata != bag.item_8.metadata + && bag.item_7.metadata != bag.item_9.metadata + && bag.item_7.metadata != bag.item_10.metadata + && bag.item_7.metadata != bag.item_11.metadata + && bag.item_8.metadata != bag.item_9.metadata + && bag.item_8.metadata != bag.item_10.metadata + && bag.item_8.metadata != bag.item_11.metadata + && bag.item_9.metadata != bag.item_10.metadata + && bag.item_9.metadata != bag.item_11.metadata + && bag.item_10.metadata != bag.item_11.metadata + } + + // To run this test we need to increase starting gold so we can buy max number of items + // We either need to use cheat codes to accomplish this or have the contract take in + // game settings in the constructor. Commenting this out for now so our CI doesn't run it + // #[test] + // #[available_gas(80000000000)] + // fn test_metadata_recycling() { + // // start game on level 2 so we have access to the market + // let mut game = new_adventurer_lvl2(1000, 1696201757, 0); + + // // get items from market + // let mut market_item_ids = game.get_items_on_market(ADVENTURER_ID); + + // let mut purchased_chest: u8 = 0; + // let mut purchased_head: u8 = 0; + // let mut purchased_waist: u8 = 0; + // let mut purchased_foot: u8 = 0; + // let mut purchased_hand: u8 = 0; + // let mut purchased_ring: u8 = 0; + // let mut shopping_cart = ArrayTrait::::new(); + + // let mut bagged_items: u8 = 0; + + // loop { + // match market_item_ids.pop_front() { + // Option::Some(item_id) => { + // let market_item = ImplLoot::get_item(item_id); + // // fill up all equipment slots + // if (market_item.slot == Slot::Chest(()) && purchased_chest == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_chest = market_item.id; + // } else if (market_item.slot == Slot::Head(()) && purchased_head == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_head = market_item.id; + // } else if (market_item.slot == Slot::Waist(()) && purchased_waist == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_waist = market_item.id; + // } else if (market_item.slot == Slot::Foot(()) && purchased_foot == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_foot = market_item.id; + // } else if (market_item.slot == Slot::Hand(()) && purchased_hand == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_hand = market_item.id; + // } else if (market_item.slot == Slot::Ring(()) && purchased_ring == 0) { + // shopping_cart.append(ItemPurchase { item_id: market_item.id, equip: true }); + // purchased_ring = market_item.id; + // } else if bagged_items < 11 && item_id != ItemId::Wand { + // shopping_cart + // .append(ItemPurchase { item_id: market_item.id, equip: false }); + // bagged_items += 1; + // } + // }, + // Option::None => { break; }, + // } + // }; + + // assert( + // purchased_chest != 0 + // && purchased_head != 0 + // && purchased_waist != 0 + // && purchased_foot != 0 + // && purchased_hand != 0 + // && purchased_ring != 0 + // && bagged_items == 11, + // 'did not purchase all items' + // ); + + // // buy items in shopping cart which will fully equip the adventurer + // // and fill their bag + // let potions = 0; + // let stat_upgrades = Stats { + // strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 + // }; + // game.upgrade(ADVENTURER_ID, potions, stat_upgrades, shopping_cart); + + // // verify adventurer is fully equipped except for necklace + // let adventurer = game.get_adventurer(ADVENTURER_ID); + // assert(adventurer.is_equipped(purchased_chest), 'chest not equipped'); + // assert(adventurer.is_equipped(purchased_head), 'head not equipped'); + // assert(adventurer.is_equipped(purchased_waist), 'waist not equipped'); + // assert(adventurer.is_equipped(purchased_foot), 'foot not equipped'); + // assert(adventurer.is_equipped(purchased_hand), 'hand not equipped'); + // assert(adventurer.is_equipped(purchased_ring), 'ring not equipped'); + // assert(adventurer.neck.id == 0, 'necklace should not be equipped'); + + // // verify bag is full // let bag = game.get_bag(ADVENTURER_ID); + // assert(bag.item_1.id != 0, 'bag item1 is empty'); + // assert(bag.item_2.id != 0, 'bag item2 is empty'); + // assert(bag.item_3.id != 0, 'bag item3 is empty'); + // assert(bag.item_4.id != 0, 'bag item4 is empty'); + // assert(bag.item_5.id != 0, 'bag item5 is empty'); + // assert(bag.item_6.id != 0, 'bag item6 is empty'); + // assert(bag.item_7.id != 0, 'bag item7 is empty'); + // assert(bag.item_8.id != 0, 'bag item8 is empty'); + // assert(bag.item_9.id != 0, 'bag item9 is empty'); + // assert(bag.item_10.id != 0, 'bag item10 is empty'); + // assert(bag.item_11.id != 0, 'bag item11 is empty'); + + // // record metadata of item_1 and item_2 and then drop them from bag + // let item_1_meta = bag.item_1.metadata; + // let item_2_meta = bag.item_2.metadata; + // game.drop(ADVENTURER_ID, array![bag.item_1.id, bag.item_2.id]); + + // // verify items are dropped + // let bag = game.get_bag(ADVENTURER_ID); + // assert(bag.item_1.id == 0, 'bag item1 should be empty'); + // assert(bag.item_2.id == 0, 'bag item2 should be empty'); + // // but the metadata should be the same + // assert(item_1_meta == bag.item_1.metadata, 'item1 meta should be the same'); + // assert(item_2_meta == bag.item_2.metadata, 'item2 meta should be the same'); + + // // clear next level + // game.explore(ADVENTURER_ID, true); + // game.attack(ADVENTURER_ID, true); + + // // get updated adventurer and bag from contract // let adventurer = game.get_adventurer(ADVENTURER_ID); + // let bag = game.get_bag(ADVENTURER_ID); + + // // get items from market + // let mut market_item_ids = game.get_items_on_market(ADVENTURER_ID); + // let mut shopping_cart = ArrayTrait::::new(); + + // loop { + // match market_item_ids.pop_front() { + // Option::Some(item_id) => { + // // shop for two items + // let market_item = ImplLoot::get_item(item_id); + // if shopping_cart.len() < 2 + // && item_id != ItemId::Wand + // && item_id != adventurer.weapon.id + // && item_id != adventurer.chest.id + // && item_id != adventurer.head.id + // && item_id != adventurer.waist.id + // && item_id != adventurer.foot.id + // && item_id != adventurer.hand.id + // && item_id != adventurer.ring.id + // && item_id != bag.item_1.id + // && item_id != bag.item_2.id + // && item_id != bag.item_3.id + // && item_id != bag.item_4.id + // && item_id != bag.item_5.id + // && item_id != bag.item_6.id + // && item_id != bag.item_7.id + // && item_id != bag.item_8.id + // && item_id != bag.item_9.id + // && item_id != bag.item_10.id + // && item_id != bag.item_11.id { + // shopping_cart + // .append(ItemPurchase { item_id: market_item.id, equip: false }); + // } else if shopping_cart.len() == 2 { + // break; + // } + // }, + // Option::None => { break; }, + // } + // }; + + // // verify shopping cart contains two items + // assert(shopping_cart.len() == 2, 'wrong number of items in cart'); + + // // purchase items + // game.upgrade(ADVENTURER_ID, potions, stat_upgrades, shopping_cart); + + // // get updated bag + // let updated_bag = game.get_bag(ADVENTURER_ID); + + // // verify contract reused metadata slots from dropped items + // assert(item_1_meta == updated_bag.item_1.metadata, 'item1 should use recycled meta'); + // assert(item_2_meta == updated_bag.item_2.metadata, 'item2 should use recycled meta'); // } #[test] #[available_gas(83000000)] fn test_drop_item() { // start new game on level 2 so we have access to the market - let mut game = new_adventurer_lvl2(1000, 1696201757); + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); // get items from market let market_items = @game.get_items_on_market(ADVENTURER_ID); @@ -1872,7 +2451,7 @@ mod tests { #[available_gas(90000000)] fn test_drop_item_without_ownership() { // start new game on level 2 so we have access to the market - let mut game = new_adventurer_lvl2(1000, 1696201757); + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); // intialize an array with 20 items in it let mut drop_list = ArrayTrait::::new(); @@ -1888,8 +2467,7 @@ mod tests { #[available_gas(75000000)] fn test_upgrade_stats() { // deploy and start new game - let mut game = new_adventurer_lvl2(1000, 1696201757); - let CHARISMA_STAT = 5; + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); // get adventurer state let adventurer = game.get_adventurer(ADVENTURER_ID); @@ -1918,11 +2496,7 @@ mod tests { #[available_gas(70000000)] fn test_upgrade_stats_not_enough_points() { // deploy and start new game - let mut game = new_adventurer_lvl2(1000, 1696201757); - - // get adventurer state - let adventurer = game.get_adventurer(ADVENTURER_ID); - let original_charisma = adventurer.stats.charisma; + let mut game = new_adventurer_lvl2(1000, 1696201757, 0); // try to upgrade charisma x2 with only 1 stat available let shopping_cart = ArrayTrait::::new(); @@ -1936,11 +2510,10 @@ mod tests { #[available_gas(75000000)] fn test_upgrade_adventurer() { // deploy and start new game - let mut game = new_adventurer_lvl2(1006, 1696201757); + let mut game = new_adventurer_lvl2(1006, 1696201757, 0); // get original adventurer state let adventurer = game.get_adventurer(ADVENTURER_ID); - let game_entropy = game.get_game_entropy(); let original_charisma = adventurer.stats.charisma; let original_health = adventurer.health; @@ -2005,7 +2578,7 @@ mod tests { fn test_exceed_rate_limit() { let starting_block = 388630; let starting_time = 1699532315; - let mut game = new_adventurer_lvl2(starting_block, starting_time); + let mut game = new_adventurer_lvl2(starting_block, starting_time, 0); testing::set_block_number(starting_block + 1); testing::set_block_timestamp(starting_time + 15); @@ -2042,12 +2615,6 @@ mod tests { game.explore(ADVENTURER_ID, false); testing::set_block_number(starting_block + 10); testing::set_block_timestamp(starting_time + 150); - game.explore(ADVENTURER_ID, false); - testing::set_block_number(starting_block + 11); - testing::set_block_timestamp(starting_time + 165); - game.explore(ADVENTURER_ID, false); - testing::set_block_number(starting_block + 12); - testing::set_block_timestamp(starting_time + 180); // with 15s block intervals, the rate limit will be 2 actions per block // try to do three actions in a single block and verify contract panics @@ -2060,7 +2627,7 @@ mod tests { #[available_gas(944417814)] fn test_exceed_rate_limit_block_rotation() { let starting_block = 1003; - let mut game = new_adventurer_lvl2(starting_block, 1696201757); + let mut game = new_adventurer_lvl2(starting_block, 1696201757, 0); let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 @@ -2068,7 +2635,6 @@ mod tests { game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); game.explore(ADVENTURER_ID, false); - game.explore(ADVENTURER_ID, false); // advancing block resets players action per block starknet::testing::set_block_number(starting_block + 1); @@ -2087,8 +2653,7 @@ mod tests { #[test] #[available_gas(90000000)] fn test_bp_distribution() { - let (mut game, lords) = new_adventurer_with_lords(1000); - let adventurer = game.get_adventurer(ADVENTURER_ID); + let (_, lords) = new_adventurer_with_lords(1000); // stage 0 assert(lords.balanceOf(DAO()) == COST_TO_PLAY.into(), 'wrong stage 1 balance'); @@ -2101,7 +2666,7 @@ mod tests { // DAO doesn't get anything more until stage 2 assert(lords.balanceOf(DAO()) == COST_TO_PLAY.into(), 'wrong stage 1 balance'); - let mut rewards = Rewards { + let mut _rewards = Rewards { DAO: _calculate_payout(REWARD_DISTRIBUTIONS_PHASE1_BP::DAO, COST_TO_PLAY), INTERFACE: _calculate_payout(REWARD_DISTRIBUTIONS_PHASE1_BP::INTERFACE, COST_TO_PLAY), FIRST_PLACE: _calculate_payout( @@ -2132,7 +2697,7 @@ mod tests { #[available_gas(90000000)] #[should_panic(expected: ('price change already initiated', 'ENTRYPOINT_FAILED'))] fn test_initiate_price_change_too_fast() { - let (mut game, lords, _, _) = setup(1000, 1, 0); + let (mut game, _, _, _) = setup(1000, 1, 0); game.initiate_price_change(); game.initiate_price_change(); } @@ -2140,7 +2705,7 @@ mod tests { #[test] #[available_gas(9000000000)] fn test_update_cost_to_play() { - let (mut game, lords, _, _) = setup(1000, 1, 0); + let (mut game, _, _, _) = setup(1000, 1, 0); let original_cost_to_play = game.get_cost_to_play(); // create 10 games during opening week @@ -2239,7 +2804,7 @@ mod tests { let starting_block = 1; let starting_timestamp = 1; let terminal_timestamp = 100; - let (mut game, lords, _, _) = setup(starting_block, starting_timestamp, terminal_timestamp); + let (mut game, _, _, _) = setup(starting_block, starting_timestamp, terminal_timestamp); // add a player to the game add_adventurer_to_game(ref game, 0); @@ -2258,7 +2823,7 @@ mod tests { let starting_block = 1; let starting_timestamp = 1; let terminal_timestamp = 0; - let (mut game, lords, _, _) = setup(starting_block, starting_timestamp, terminal_timestamp); + let (mut game, _, _, _) = setup(starting_block, starting_timestamp, terminal_timestamp); // add a player to the game add_adventurer_to_game(ref game, 0); @@ -2277,9 +2842,7 @@ mod tests { let starting_block = 364063; let starting_timestamp = 1698678554; let terminal_timestamp = 0; - let (mut game, lords, golden_token, arcade_account_address) = setup( - starting_block, starting_timestamp, terminal_timestamp - ); + let (mut game, _, _, _) = setup(starting_block, starting_timestamp, terminal_timestamp); add_adventurer_to_game(ref game, 1); testing::set_block_timestamp(starting_timestamp + DAY); add_adventurer_to_game(ref game, 1); @@ -2292,9 +2855,7 @@ mod tests { let starting_block = 364063; let starting_timestamp = 1698678554; let terminal_timestamp = 0; - let (mut game, lords, golden_token, arcade_account_address) = setup( - starting_block, starting_timestamp, terminal_timestamp - ); + let (mut game, _, _, _) = setup(starting_block, starting_timestamp, terminal_timestamp); assert(game.can_play(1), 'should be able to play'); add_adventurer_to_game(ref game, golden_token_id); assert(!game.can_play(1), 'should not be able to play'); @@ -2312,9 +2873,7 @@ mod tests { let starting_block = 364063; let starting_timestamp = 1698678554; let terminal_timestamp = 0; - let (mut game, lords, golden_token, arcade_account_address) = setup( - starting_block, starting_timestamp, terminal_timestamp - ); + let (mut game, _, _, _) = setup(starting_block, starting_timestamp, terminal_timestamp); add_adventurer_to_game(ref game, golden_token_id); } @@ -2326,9 +2885,7 @@ mod tests { let starting_block = 364063; let starting_timestamp = 1698678554; let terminal_timestamp = 0; - let (mut game, lords, golden_token, arcade_account_address) = setup( - starting_block, starting_timestamp, terminal_timestamp - ); + let (mut game, _, _, _) = setup(starting_block, starting_timestamp, terminal_timestamp); add_adventurer_to_game(ref game, golden_token_id); // roll blockchain forward 1 second less than a day @@ -2362,4 +2919,125 @@ mod tests { let (is_idle, _) = game.is_idle(ADVENTURER_ID); assert(is_idle, 'should be idle'); } + + + #[test] + #[should_panic(expected: ('Not authorized to act', 'ENTRYPOINT_FAILED'))] + fn test_set_starting_entropy_not_owner() { + let mut game = new_adventurer(1000, 1696201757); + testing::set_block_number(1002); + // change to different caller + testing::set_contract_address(contract_address_const::<50>()); + // try to set starting entropy, should revert + game.set_starting_entropy(ADVENTURER_ID, 1); + } + + #[test] + #[should_panic(expected: ('game already started', 'ENTRYPOINT_FAILED'))] + fn test_set_starting_entropy_game_started() { + let mut game = new_adventurer(1000, 1696201757); + testing::set_block_number(1002); + // defeat starter beast + game.attack(ADVENTURER_ID, true); + // then attempt to set starting entropy, should revert + game.set_starting_entropy(ADVENTURER_ID, 1); + } + + + #[test] + #[should_panic(expected: ('valid hash not yet available', 'ENTRYPOINT_FAILED'))] + fn test_set_starting_entropy_before_hash_available() { + let mut game = new_adventurer(1000, 1696201757); + // attempt to set starting entropy before hash is available, should revert + game.set_starting_entropy(ADVENTURER_ID, 1); + } + + #[test] + #[should_panic(expected: ('block hash should not be zero', 'ENTRYPOINT_FAILED'))] + fn test_set_starting_entropy_zero_hash() { + let mut game = new_adventurer(1000, 1696201757); + testing::set_block_number(1002); + // attempt to pass in 0 for starting entropy hash, should revert + game.set_starting_entropy(ADVENTURER_ID, 0); + } + + #[test] + #[should_panic(expected: ('starting entropy already set', 'ENTRYPOINT_FAILED'))] + fn test_set_starting_entropy_double_call() { + let mut game = new_adventurer(1000, 1696201757); + testing::set_block_number(1002); + // attempt to set starting entropy twice, should revert + game.set_starting_entropy(ADVENTURER_ID, 1); + game.set_starting_entropy(ADVENTURER_ID, 1); + } + + #[test] + fn test_set_starting_entropy_basic() { + let mut game = new_adventurer(1000, 1696201757); + testing::set_block_number(1002); + game.set_starting_entropy(ADVENTURER_ID, 123); + // verify starting entropy was set + assert( + game.get_adventurer_starting_entropy(ADVENTURER_ID) == 123, 'wrong starting entropy' + ); + // verify adventurer entropy is using starting entropy + assert(game.get_adventurer_entropy(ADVENTURER_ID) == 123, 'wrong adventurer entropy'); + } + + #[test] + fn test_set_starting_entropy_wrong_hash() { + let wrong_starting_entropy = 12345678910112; + let mut game = new_adventurer_lvl3(1000, 1696201757, wrong_starting_entropy); + testing::set_block_number(1002); + + // go out exploring till beast + game.explore(ADVENTURER_ID, true); + + // record adventurer before death + let pre_death_adventurer = game.get_adventurer(ADVENTURER_ID); + assert(pre_death_adventurer.xp > 0, 'adventurer should have xp'); + assert(pre_death_adventurer.gold > 0, 'adventurer should have gold'); + + // attack beast till death + game.attack(ADVENTURER_ID, true); + + // adventurer died attacking beast + let post_death_adventurer = game.get_adventurer(ADVENTURER_ID); + // because starting entropy was wrong, adventurer's xp and gold should be reset + assert(post_death_adventurer.health == 0, 'adventurer should be dead'); + assert(post_death_adventurer.xp == 1, 'adventurer should have 1 xp'); + assert(post_death_adventurer.gold == 0, 'adventurer should have 0 gold'); + } + + // test slay_invalid_adventurers on adventurer who didn't use optimistic start + #[test] + #[should_panic(expected: ('starting entropy is valid', 'ENTRYPOINT_FAILED'))] + fn test_slay_invalid_adventurer_no_manual_entropy() { + let mut game = new_adventurer_lvl3(1000, 1696201757, 0); + let invalid_adventurers = array![ADVENTURER_ID]; + game.slay_invalid_adventurers(invalid_adventurers); + } + + // test slay_invalid_adventurers on adventurer who used optimistic start with correct hash + #[test] + #[should_panic(expected: ('starting entropy is valid', 'ENTRYPOINT_FAILED'))] + fn test_slay_invalid_adventurer_correct_entropy() { + let mut game = new_adventurer_lvl3( + 1000, 1696201757, 0x54b720c8f3876115e2e0fd5c8f0ed2fbaa8fe24f12c402497400043adc7d26e + ); + let invalid_adventurers = array![ADVENTURER_ID]; + game.slay_invalid_adventurers(invalid_adventurers); + } + + // test slay_invalid_adventurers on adventurer who used optimistic start with wrong hash + #[test] + fn test_slay_invalid_adventurer() { + let mut game = new_adventurer_lvl3(1000, 1696201757, 123456789); + let invalid_adventurers = array![ADVENTURER_ID]; + game.slay_invalid_adventurers(invalid_adventurers); + let adventurer = game.get_adventurer(ADVENTURER_ID); + assert(adventurer.health == 0, 'adventurer should be dead'); + assert(adventurer.xp == 1, 'adventurer should have 1 xp'); + assert(adventurer.gold == 0, 'adventurer should have 0 gold'); + } } diff --git a/contracts/game_entropy/Scarb.lock b/contracts/game_entropy/Scarb.lock new file mode 100644 index 000000000..15fad113b --- /dev/null +++ b/contracts/game_entropy/Scarb.lock @@ -0,0 +1,6 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "game_entropy" +version = "0.1.0" diff --git a/contracts/game_entropy/src/game_entropy.cairo b/contracts/game_entropy/src/game_entropy.cairo index 344eac037..4bdb3d630 100644 --- a/contracts/game_entropy/src/game_entropy.cairo +++ b/contracts/game_entropy/src/game_entropy.cairo @@ -87,7 +87,7 @@ impl ImplGameEntropy of IGameEntropy { block_number_diff * 3600 / block_timestamp_diff } - /// @notice Calculate the current rate of blocks produced per hour, based on a ten-minute window. + /// @notice Calculate the current rate of blocks produced per hour, based on a thirty minute window. /// @return The number of blocks produced per hour. #[inline(always)] fn current_blocks_per_hour(self: GameEntropy) -> u64 { @@ -168,28 +168,28 @@ mod tests { let last_updated_block = 0; let last_updated_time = 0; let next_update_block = 0; - let hash = ImplGameEntropy::get_hash( + let _hash = ImplGameEntropy::get_hash( last_updated_block, last_updated_time, next_update_block ); } #[test] - #[available_gas(14280)] + #[available_gas(24280)] fn test_is_adventurer_idle() { let hash = 0x123; let last_updated_block = 282360; let last_updated_time = 1696209920; - let next_update_block = 282364; + let next_update_block = 282380; let game_entropy = GameEntropy { hash, last_updated_block, last_updated_time, next_update_block, }; - let adventurer_idle_blocks = 3; + let adventurer_idle_blocks = 4; let is_idle = game_entropy.is_adventurer_idle(adventurer_idle_blocks); assert(!is_idle, 'should not be idle'); - let adventurer_idle_blocks = 4; + let adventurer_idle_blocks = 6; let is_idle = game_entropy.is_adventurer_idle(adventurer_idle_blocks); assert(is_idle, 'should be idle'); } @@ -208,7 +208,7 @@ mod tests { // next entropy rotation is in 3 blocks which is 10 minutes // at 1 block per 3mins (20 blocks per hour) - assert(next_entropy_rotation == 4, 'wrong rotation, slow speed'); + assert(next_entropy_rotation == 11, 'wrong rotation, slow speed'); // starknet expects to eventually be producing blocks every 30s (2 per min, 120 per hour) let blocks_per_hour = 120; @@ -216,7 +216,7 @@ mod tests { current_block, blocks_per_hour ); // after this blockspeed, ten minutes is now 20 blocks in the future - assert(next_entropy_rotation == 21, 'wrong rotation, fast speed'); + assert(next_entropy_rotation == 61, 'wrong rotation, fast speed'); } #[test] @@ -229,9 +229,9 @@ mod tests { next_update_block: 282481, }; let blocks_per_hour = game_entropy.current_blocks_per_hour(); - assert(blocks_per_hour == 96, 'wrong blocks per hour') + assert(blocks_per_hour == 32, 'wrong blocks per hour') } - + #[test] #[available_gas(29280)] fn test_calculate_blocks_per_hour() { diff --git a/contracts/lords/Scarb.toml b/contracts/lords/Scarb.toml index 9d5b8e959..1fc9f0431 100644 --- a/contracts/lords/Scarb.toml +++ b/contracts/lords/Scarb.toml @@ -3,8 +3,8 @@ name = "lords" version = "0.1.0" [dependencies] -starknet = "2.1.0" -openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.7.0" } +starknet.workspace = true +openzeppelin.workspace = true [[target.starknet-contract]] allowed-libfuncs-list.name = "experimental" diff --git a/contracts/market/Scarb.toml b/contracts/market/Scarb.toml index eef88cb54..0fc240247 100644 --- a/contracts/market/Scarb.toml +++ b/contracts/market/Scarb.toml @@ -2,8 +2,6 @@ name = "market" version = "0.1.0" -# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest - [dependencies] lootitems = { path = "../loot" } -combat = { path = "../combat" } \ No newline at end of file +combat = { path = "../combat" } diff --git a/contracts/market/src/constants.cairo b/contracts/market/src/constants.cairo index cc0b1090e..b4d95336b 100644 --- a/contracts/market/src/constants.cairo +++ b/contracts/market/src/constants.cairo @@ -1,4 +1,4 @@ -// number of items per level per survivor +// number of items available per stat upgrade const NUMBER_OF_ITEMS_PER_LEVEL: u8 = 21; // number of loot items diff --git a/contracts/market/src/market.cairo b/contracts/market/src/market.cairo index 48627ac89..dcb4ccb02 100644 --- a/contracts/market/src/market.cairo +++ b/contracts/market/src/market.cairo @@ -580,15 +580,13 @@ mod tests { if (i == 100) { break; } - let adventurer_id: felt252 = 1; - let block_number = 839152; + let _adventurer_id: felt252 = 1; + let _block_number = 839152; let xp: u16 = 3; - let stats_points_available: u8 = 4; + let _stats_points_available: u8 = 4; let adventurer_entropy = 1; - let (market_seed, market_offset) = ImplMarket::get_market_seed_and_offset( - adventurer_entropy, xp - ); + let (_, market_offset) = ImplMarket::get_market_seed_and_offset(adventurer_entropy, xp); // assert market offset is within range of items assert(market_offset != 0 && market_offset < NUM_ITEMS, 'offset out of bounds'); @@ -603,9 +601,7 @@ mod tests { let mut i: u128 = 0; loop { let poseidon_hash: felt252 = i.into(); - let (market_seed, market_offset) = ImplMarket::split_hash_into_seed_and_offset( - poseidon_hash - ); + let (_, market_offset) = ImplMarket::split_hash_into_seed_and_offset(poseidon_hash); if (i == 101) { break; } @@ -619,9 +615,7 @@ mod tests { let mut i: u128 = 340282366920938463463374607431768211100; loop { let poseidon_hash: felt252 = i.into(); - let (market_seed, market_offset) = ImplMarket::split_hash_into_seed_and_offset( - poseidon_hash - ); + let (_, market_offset) = ImplMarket::split_hash_into_seed_and_offset(poseidon_hash); if (i == 340282366920938463463374607431768211455) { break; } diff --git a/contracts/obstacles/Scarb.toml b/contracts/obstacles/Scarb.toml index 7b40e05be..baa98a02a 100644 --- a/contracts/obstacles/Scarb.toml +++ b/contracts/obstacles/Scarb.toml @@ -2,8 +2,5 @@ name = "obstacles" version = "0.1.0" -# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest - [dependencies] -# foo = { path = "vendor/foo" } combat = { path = "../combat" } diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 3bbc6d5d8..d2a685270 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,10 +1,12 @@ #!/bin/bash -export STARKNET_NETWORK=alpha-goerli -export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount -export CAIRO_COMPILER_DIR=~/.cairo/target/release/ -export CAIRO_COMPILER_ARGS=--add-pythonic-hints +# Source env vars +ENV_FILE="/workspaces/loot-survivor/.env" +source $ENV_FILE -scarb contracts/game/build +# build game contract +cd /workspaces/loot-survivor/contracts/ +scarb build -starknet declare --contract contracts/game/target/dev/game_Game.sierra.json --account deployer_4 +# declare game contract +starkli declare --watch /workspaces/loot-survivor/target/dev/game_Game.contract_class.json --account $STARKNET_ACCOUNT --private-key $PRIVATE_KEY \ No newline at end of file diff --git a/scripts/starkli_setup.sh b/scripts/starkli_setup.sh new file mode 100644 index 000000000..9f8a5b9f2 --- /dev/null +++ b/scripts/starkli_setup.sh @@ -0,0 +1,67 @@ +ENV_FILE="/workspaces/loot-survivor/.env" + +# If there is already an account in .env, skip that +if grep -q "^ACCOUNT_ADDRESS=" "$ENV_FILE"; then + echo "Account already setup, exiting" + exit +fi + +echo "LORDS_ADDRESS=0x059dac5df32cbce17b081399e97d90be5fba726f97f00638f838613d088e5a47" > $ENV_FILE +echo "DAO_ADDRESS=0x059dac5df32cbce17b081399e97d90be5fba726f97f00638f838613d088e5a47" >> $ENV_FILE + +# these are mainnet contracts. If you are running on testnet, please update these to right contracts +echo "GOLDEN_TOKEN_ADDRESS=0x04f5e296c805126637552cf3930e857f380e7c078e8f00696de4fc8545356b1d" >> $ENV_FILE +echo "BEASTS_ADDRESS=0x0158160018d590d93528995b340260e65aedd76d28a686e9daa5c4e8fad0c5dd" >> $ENV_FILE + +# Useful game constants +echo "ADVENTURER_ID=1" >> $ENV_FILE +echo "STRENGTH=0" >> $ENV_FILE +echo "DEXTERITY=1" >> $ENV_FILE +echo "VITALITY=2" >> $ENV_FILE +echo "WISDOM=3" >> $ENV_FILE +echo "INTELLIGENCE=4" >> $ENV_FILE +echo "CHARISMA=5" >> $ENV_FILE + +# default to sepolia testnet +echo "STARKNET_NETWORK=\"sepolia\"" >> $ENV_FILE +export STARKNET_NETWORK="sepolia" + +# initialize starknet directories +mkdir -p $HOME/.starknet +STARKNET_ACCOUNT=$HOME/.starknet/account +STARKNET_KEYSTORE=$HOME/.starknet/keystore + +# Change directory to starkli +cd /root/.starkli/bin/ + +# Generate keypair +output=$(./starkli signer gen-keypair) + +# Store keys as vars so we can use them and later write to .bashrc +private_key=$(echo "$output" | awk '/Private key/ {print $4}') +public_key=$(echo "$output" | awk '/Public key/ {print $4}') + +# Initialize OZ account and save output +account_output=$(./starkli account oz init $STARKNET_ACCOUNT --private-key $private_key 2>&1) +account_address=$(echo "$account_output" | grep -oE '0x[0-9a-fA-F]+') + +# Deploy Account +./starkli account deploy $STARKNET_ACCOUNT --private-key $private_key + +# Output key and account info +echo "Private Key: $private_key" +echo "Public Key: $public_key" +echo "Account: $account_address" + +# Add keys and account to .bashrc as env vars for easy access in shell +echo "PRIVATE_KEY=\"$private_key\"" >> $ENV_FILE +echo "PUBLIC_KEY=\"$public_key\"" >> $ENV_FILE +echo "ACCOUNT_ADDRESS=\"$account_address\"" >> $ENV_FILE +echo "STARKNET_ACCOUNT=$STARKNET_ACCOUNT" >> $ENV_FILE +echo "STARKNET_KEYSTORE=$STARKNET_KEYSTORE" >> $ENV_FILE + +echo "set -o allexport" >> ~/.bashrc +echo "source $ENV_FILE" >> ~/.bashrc +echo "set +o allexport" >> ~/.bashrc + +source ~/.bashrc \ No newline at end of file diff --git a/ui/package.json b/ui/package.json index 335450dbe..45cb9739b 100644 --- a/ui/package.json +++ b/ui/package.json @@ -12,7 +12,7 @@ "@apollo/client": "^3.7.11", "@cartridge/controller": "^0.3.18", "@fontsource/vt323": "^4.5.10", - "@starknet-react/core": "^2.1.5", + "@starknet-react/core": "^2.5.0", "@svgr/webpack": "^7.0.0", "@types/js-cookie": "^3.0.3", "@types/node": "18.15.11", @@ -37,7 +37,7 @@ "react-responsive": "^9.0.2", "react-transition-group": "^4.4.5", "react-type-animation": "^3.0.1", - "starknet": "^5.19.5", + "starknet": "^6.4.3", "starknetkit": "^1.0.22", "tailwind-merge": "^1.10.0", "tailwindcss": "^3.3.0", diff --git a/ui/src/app/api/api.ts b/ui/src/app/api/api.ts index 251bdc3eb..6f78eee4a 100644 --- a/ui/src/app/api/api.ts +++ b/ui/src/app/api/api.ts @@ -67,3 +67,40 @@ export const getApibaraStatus = async () => { const data = await response.json(); return data; }; + +export const getInterface = async ( + masterAddress: string, + arcade_interface_id: string +) => { + const rpcUrl = process.env.NEXT_PUBLIC_RPC_URL!; + const response = await fetch(rpcUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + method: "starknet_call", + params: [ + { + contract_address: masterAddress, + entry_point_selector: + "0xfe80f537b66d12a00b6d3c072b44afbb716e78dde5c3f0ef116ee93d3e3283", // supports_interface + calldata: [arcade_interface_id], + }, + "pending", + ], + id: 0, + }), + }); + + const data = await response.json(); + + if (response.ok) { + console.log("Interface fetched successfully"); + } else { + console.error("Error in response:", data); + } + + return data; +}; diff --git a/ui/src/app/components/ArcadeDialog.tsx b/ui/src/app/components/ArcadeDialog.tsx index 497349e25..ff57f0938 100644 --- a/ui/src/app/components/ArcadeDialog.tsx +++ b/ui/src/app/components/ArcadeDialog.tsx @@ -23,7 +23,8 @@ import ArcadeLoader from "@/app/components/animations/ArcadeLoader"; import TokenLoader from "@/app/components/animations/TokenLoader"; import TopupInput from "@/app/components/arcade/TopupInput"; import RecoverUndeployed from "@/app/components/arcade/RecoverUndeployed"; -import RecoverArcade from "@/app/components//arcade/RecoverArcade"; +import RecoverArcade from "@/app/components/arcade/RecoverArcade"; +import MigrateAA from "@/app/components/arcade/MigrateAA"; import Storage from "@/app/lib/storage"; import { BurnerStorage } from "@/app/types"; @@ -51,6 +52,7 @@ export const ArcadeDialog = ({ const [fetchedBalances, setFetchedBalances] = useState(false); const [recoverArcade, setRecoverArcade] = useState(false); const [recoverUndeployed, setRecoverUndeployed] = useState(false); + const [migrateAA, setMigrateAA] = useState(false); const [fullDeployment, setFullDeployment] = useState(false); const { account: walletAccount, address, connector } = useAccount(); const showArcadeDialog = useUIStore((state) => state.showArcadeDialog); @@ -177,6 +179,12 @@ export const ArcadeDialog = ({ walletAccount={walletAccount!} updateConnectors={updateConnectors} /> + ) : migrateAA ? ( + ) : ( <>
@@ -216,6 +224,9 @@ export const ArcadeDialog = ({ +
@@ -463,7 +474,7 @@ export const ArcadeAccountCard = ({ /> )} -
+
- {masterAccountAddress == walletAccount?.address && ( - <> - - - - )} {connected && (
+ {masterAccountAddress == walletAccount?.address && ( +
+ + +
+ )}
); diff --git a/ui/src/app/components/arcade/MigrateAA.tsx b/ui/src/app/components/arcade/MigrateAA.tsx new file mode 100644 index 000000000..6a6ba0735 --- /dev/null +++ b/ui/src/app/components/arcade/MigrateAA.tsx @@ -0,0 +1,199 @@ +import { ChangeEvent, useState, useEffect } from "react"; +import { CallData, Contract, ec, hash } from "starknet"; +import { useContract } from "@starknet-react/core"; +import { padAddress, isChecksumAddress } from "@/app/lib/utils"; +import { Button } from "@/app/components/buttons/Button"; +import Storage from "@/app/lib/storage"; +import ArcadeAccount from "@/app/abi/ArcadeAccount.json"; +import { getInterface } from "@/app/api/api"; + +const ARCADE_ACCOUNT_ID: string = "0x4152434144455F4143434F554E545F4944"; + +interface MigrateAAProps { + setMigrateAA: (migrateAA: boolean) => void; + gameContract: Contract; + updateConnectors: () => void; +} + +const MigrateAA = ({ + setMigrateAA, + gameContract, + updateConnectors, +}: MigrateAAProps) => { + const [arcadePrivateKey, setArcadePrivateKey] = useState< + string | undefined + >(); + const [arcadeExists, setArcadeExists] = useState(false); + const [arcadeAddress, setArcadeAddress] = useState(); + const [arcadePublicKey, setArcadePublicKey] = useState(); + const [inputMasterAccount, setInputMasterAccount] = useState< + string | undefined + >(); + const [realMasterAccount, setRealMasterAccount] = useState< + string | undefined + >(); + const [masterInterface, setMasterInterface] = useState(); + + const formattedArcadeAddress = padAddress(padAddress(arcadeAddress ?? "")); + + const storage = Storage.get("burners") || {}; + + const handleMasterAccountChange = ( + e: ChangeEvent + ) => { + const { value } = e.target; + setInputMasterAccount(value); + }; + + const handlePrivateKeyChange = ( + e: ChangeEvent + ) => { + const { value } = e.target; + setArcadePrivateKey(value); + }; + + const { contract: arcadeContract } = useContract({ + address: formattedArcadeAddress, + abi: ArcadeAccount, + }); + + const getMasterAccount = async () => { + try { + const masterAccount = await arcadeContract?.call("get_master_account"); + if (masterAccount) { + setArcadeExists(true); + setRealMasterAccount("0x" + masterAccount.toString(16)); + } + } catch (e) { + console.log(e); + } + }; + + const checkInterface = async () => { + try { + const accountInterface: any = await getInterface( + inputMasterAccount!, + ARCADE_ACCOUNT_ID + ); + if (accountInterface.error) { + setMasterInterface("braavos"); + } else { + setMasterInterface("argentX"); + } + } catch (e) { + console.log(e); + } + }; + + const handleGetArcade = () => { + if (isChecksumAddress(arcadePrivateKey!)) { + const publicKey = ec.starkCurve.getStarkKey(arcadePrivateKey!); + + const constructorAACalldata = CallData.compile({ + _public_key: publicKey, + _master_account: inputMasterAccount!, + }); + + const address = hash.calculateContractAddressFromHash( + publicKey, + process.env.NEXT_PUBLIC_ARCADE_ACCOUNT_CLASS_HASH!, + constructorAACalldata, + 0 + ); + + setArcadeAddress(address); + setArcadePublicKey(publicKey); + } + }; + + const importBurner = () => { + storage[formattedArcadeAddress!] = { + privateKey: arcadePrivateKey, + publicKey: arcadePublicKey, + masterAccount: inputMasterAccount, + masterAccountProvider: masterInterface, + gameContract: gameContract?.address, + active: true, + }; + + Storage.set("burners", storage); + }; + + const arcadeAccountExists = () => { + if (storage) { + return Object.keys(storage).includes(formattedArcadeAddress ?? ""); + } else { + return false; + } + }; + + const isMasterAccount = + padAddress(inputMasterAccount!) === padAddress(realMasterAccount ?? ""); + + useEffect(() => { + if (arcadePrivateKey && inputMasterAccount) { + handleGetArcade(); + } + }, [arcadePrivateKey]); + + useEffect(() => { + if (arcadeAddress) { + getMasterAccount(); + } + }, [arcadeAddress]); + + useEffect(() => { + if (inputMasterAccount) { + checkInterface(); + } + }, [inputMasterAccount]); + + return ( +
+

Import Arcade Account

+

+ Please enter the master account address and private key of the Arcade + Account you would like to import to this client. +

+ + + +
+ ); +}; + +export default MigrateAA; diff --git a/ui/src/app/components/interlude/Lobby.tsx b/ui/src/app/components/interlude/Lobby.tsx index d30a860ef..912b2274c 100644 --- a/ui/src/app/components/interlude/Lobby.tsx +++ b/ui/src/app/components/interlude/Lobby.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { Block } from "starknet"; import { useQueriesStore } from "@/app/hooks/useQueryStore"; import { Adventurer, NullAdventurer } from "@/app/types"; import { useBlock } from "@starknet-react/core"; @@ -19,7 +20,8 @@ export default function Lobby() { }); const handleFilterLobby = (adventurers: Adventurer[]) => { return adventurers.filter( - (adventurer) => adventurer?.revealBlock! > blockData?.block_number! + (adventurer) => + adventurer?.revealBlock! > (blockData as Block)?.block_number! ); }; @@ -78,7 +80,7 @@ export default function Lobby() { key={index} adventurer={adventurer} handleRowSelected={handleRowSelected} - currentBlock={blockData?.block_number!} + currentBlock={(blockData as Block)?.block_number!} /> ) )} diff --git a/ui/src/app/components/leaderboard/ScoreTable.tsx b/ui/src/app/components/leaderboard/ScoreTable.tsx index 73e0925f4..0a252dde8 100644 --- a/ui/src/app/components/leaderboard/ScoreTable.tsx +++ b/ui/src/app/components/leaderboard/ScoreTable.tsx @@ -17,14 +17,24 @@ const ScoreLeaderboardTable = ({ handleFetchProfileData, adventurers, }: ScoreLeaderboardTableProps) => { + const [showAllTime, setShowAllTime] = useState(false); const [currentPage, setCurrentPage] = useState(1); const setScreen = useUIStore((state) => state.setScreen); const setProfile = useUIStore((state) => state.setProfile); + const campaignAdventurers = adventurers.filter( + (score) => score.startBlock! > 942308 + ); + const displayScores = adventurers?.slice( (currentPage - 1) * itemsPerPage, currentPage * itemsPerPage ); + const campaignDisplayScores = campaignAdventurers?.slice( + (currentPage - 1) * itemsPerPage, + currentPage * itemsPerPage + ); + const scoreIds = adventurers?.map((score) => score.id ?? 0); const scoresData = useCustomQuery("topScoresQuery", getScoresInList, { @@ -42,9 +52,27 @@ const ScoreLeaderboardTable = ({ }; }); + const campaignMergedScores = campaignDisplayScores.map((item1) => { + const matchingItem2 = scoresData?.scores.find( + (item2: Score) => item2.adventurerId === item1.id + ); + + return { + ...item1, + ...matchingItem2, + }; + }); + const scoresWithLords = mergedScores; - const totalPages = Math.ceil(adventurers.length / itemsPerPage); + const onMainnet = process.env.NEXT_PUBLIC_NETWORK === "mainnet"; + const onSepolia = process.env.NEXT_PUBLIC_NETWORK === "sepolia"; + + const totalPages = Math.ceil( + (!onMainnet && !onSepolia && !showAllTime + ? campaignAdventurers.length + : adventurers.length) / itemsPerPage + ); let previousXp = -1; let currentRank = 0; @@ -84,7 +112,15 @@ const ScoreLeaderboardTable = ({ return (
-
+
+ {!onMainnet && !onSepolia && ( + + )} {adventurers.length > 0 ? ( <>

@@ -101,7 +137,10 @@ const ScoreLeaderboardTable = ({ - {scoresWithLords.map((adventurer: any, index: number) => ( + {(!onMainnet && !onSepolia && !showAllTime + ? campaignMergedScores + : scoresWithLords + ).map((adventurer: any, index: number) => ( - {adventurers?.length > 10 && ( + {(!onMainnet && !onSepolia && !showAllTime + ? campaignAdventurers.length + : adventurers.length) > 10 && (

); diff --git a/ui/src/app/components/navigation/TxActivity.tsx b/ui/src/app/components/navigation/TxActivity.tsx index f77186de2..095b6b7c1 100644 --- a/ui/src/app/components/navigation/TxActivity.tsx +++ b/ui/src/app/components/navigation/TxActivity.tsx @@ -19,11 +19,11 @@ export const TxActivity = () => { hash: hash ? hash : "0x0", }) as { data: InvokeTransactionReceiptResponse }; - if (data?.status === "ACCEPTED_ON_L2") { + if (data?.finality_status === "ACCEPTED_ON_L2") { setTxAccepted(true); } - if (data?.status === "REJECTED") { + if (data?.execution_status === "REVERTED") { stopLoading("Rejected"); } diff --git a/ui/src/app/components/start/Spawn.tsx b/ui/src/app/components/start/Spawn.tsx index e3c8be915..1e29102d8 100644 --- a/ui/src/app/components/start/Spawn.tsx +++ b/ui/src/app/components/start/Spawn.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { CallData, Contract } from "starknet"; +import { Block, CallData, Contract } from "starknet"; import { useAccount, useConnect, useBlock } from "@starknet-react/core"; import Image from "next/image"; import { TypeAnimation } from "react-type-animation"; @@ -120,7 +120,7 @@ export const Spawn = ({ refetchInterval: false, }); - const currentBlockNumber = blockData?.block_number ?? 0; + const currentBlockNumber = (blockData as Block)?.block_number ?? 0; const [fetchedAverageBlockTime, setFetchedAverageBlockTime] = useState(false); diff --git a/ui/src/app/containers/BeastScreen.tsx b/ui/src/app/containers/BeastScreen.tsx index fd80ae402..f51d073d9 100644 --- a/ui/src/app/containers/BeastScreen.tsx +++ b/ui/src/app/containers/BeastScreen.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import { useBlock } from "@starknet-react/core"; -import { Contract } from "starknet"; +import { Contract, Block } from "starknet"; import { BattleDisplay } from "@/app/components/beast/BattleDisplay"; import { BeastDisplay } from "@/app/components/beast/BeastDisplay"; import useLoadingStore from "@/app/hooks/useLoadingStore"; @@ -171,7 +171,7 @@ export default function BeastScreen({
); - const currentBlockNumber = blockData?.block_number ?? 0; + const currentBlockNumber = (blockData as Block)?.block_number ?? 0; const revealBlockReached = currentBlockNumber >= (adventurer?.revealBlock ?? 0); diff --git a/ui/src/app/containers/InventoryScreen.tsx b/ui/src/app/containers/InventoryScreen.tsx index ff7268ef9..b1a489735 100644 --- a/ui/src/app/containers/InventoryScreen.tsx +++ b/ui/src/app/containers/InventoryScreen.tsx @@ -101,7 +101,7 @@ export default function InventoryScreen({ const gameData = new GameData(); const checkTransacting = (item: string) => { - if (txData?.status == "RECEIVED") { + if (txData?.finality_status !== undefined) { return transactingItemIds?.includes(item); } else { return false; diff --git a/ui/src/app/containers/LeaderboardScreen.tsx b/ui/src/app/containers/LeaderboardScreen.tsx index 4962416bc..6df875e17 100644 --- a/ui/src/app/containers/LeaderboardScreen.tsx +++ b/ui/src/app/containers/LeaderboardScreen.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -import { Contract } from "starknet"; +import { Block, Contract } from "starknet"; import { useBlock } from "@starknet-react/core"; import { getAdventurerByXP, @@ -45,7 +45,7 @@ export default function LeaderboardScreen({ refetchInterval: false, }); - const currentBlock = blockData?.block_number; + const currentBlock = (blockData as Block)?.block_number; const { data, refetch, setData, setIsLoading, setNotLoading } = useQueriesStore(); diff --git a/ui/src/app/containers/Onboarding.tsx b/ui/src/app/containers/Onboarding.tsx index 81e309432..c0b9f84e9 100644 --- a/ui/src/app/containers/Onboarding.tsx +++ b/ui/src/app/containers/Onboarding.tsx @@ -25,6 +25,7 @@ import { useBurner } from "@/app/lib/burner"; import ArcadeLoader from "@/app/components/animations/ArcadeLoader"; import useUIStore, { ScreenPage } from "@/app/hooks/useUIStore"; import { useUiSounds, soundSelector } from "@/app/hooks/useUiSound"; +import TokenLoader from "@/app/components/animations/TokenLoader"; type Section = "connect" | "eth" | "lords" | "arcade"; @@ -47,6 +48,7 @@ interface SectionContentProps { onMainnet: boolean; network: string; mintLords: (lordsAmount: number) => Promise; + setMintingLords: (value: boolean) => void; prefundGames: number; setPrefundGames: (games: number) => void; setFullDeployment: (value: boolean) => void; @@ -75,6 +77,7 @@ const SectionContent = ({ onMainnet, network, mintLords, + setMintingLords, prefundGames, setPrefundGames, setFullDeployment, @@ -278,7 +281,9 @@ const SectionContent = ({ )}&amount=0.001`; window.open(avnuLords, "_blank"); } else { + setMintingLords(true); await mintLords(lordsGameCost * 25); + setMintingLords(false); } }} > @@ -587,6 +592,8 @@ const Onboarding = ({ const [step, setStep] = useState(1); + const [mintingLords, setMintingLords] = useState(false); + const handleOnboarded = useUIStore((state) => state.handleOnboarded); const setScreen = useUIStore((state) => state.setScreen); @@ -622,6 +629,7 @@ const Onboarding = ({ fullDeployment={fullDeployment} showLoader={showLoader} /> + {mintingLords && } {section && ( Promise< | DeclareTransactionReceiptResponse - | RevertedTransactionReceiptResponse - | RejectedTransactionReceiptResponse + | GetTransactionReceiptResponse | undefined >; isToppingUpEth: boolean; diff --git a/ui/src/app/lib/burner.ts b/ui/src/app/lib/burner.ts index 2b6c42e70..4affc6bab 100644 --- a/ui/src/app/lib/burner.ts +++ b/ui/src/app/lib/burner.ts @@ -25,8 +25,7 @@ export const ETH_PREFUND_AMOUNT = isMainnet const rpc_addr = process.env.NEXT_PUBLIC_RPC_URL; const provider = new Provider({ - rpc: { nodeUrl: rpc_addr! }, - sequencer: { baseUrl: rpc_addr! }, + nodeUrl: rpc_addr!, }); interface UseBurnerProps { @@ -311,7 +310,7 @@ export const useBurner = ({ contractAddress: accountAAFinalAdress, entrypoint: "update_whitelisted_calls", calldata: [ - "3", + "4", ethContract?.address ?? "", selector.getSelectorFromName("transfer"), "1", @@ -321,6 +320,9 @@ export const useBurner = ({ lordsContract?.address ?? "", selector.getSelectorFromName("transfer"), "1", + lordsContract?.address ?? "", + selector.getSelectorFromName("mint"), // needed for testnet deployment + "1", ], }, ]; diff --git a/ui/src/app/lib/utils/syscalls.ts b/ui/src/app/lib/utils/syscalls.ts index 392338c21..f48aac3dd 100644 --- a/ui/src/app/lib/utils/syscalls.ts +++ b/ui/src/app/lib/utils/syscalls.ts @@ -3,7 +3,7 @@ import { InvokeTransactionReceiptResponse, Contract, AccountInterface, - RevertedTransactionReceiptResponse, + GetTransactionReceiptResponse, Provider, } from "starknet"; import { GameData } from "@/app/lib/data/GameData"; @@ -38,8 +38,7 @@ import { TRANSACTION_WAIT_RETRY_INTERVAL } from "@/app/lib/constants"; const rpc_addr = process.env.NEXT_PUBLIC_RPC_URL; const provider = new Provider({ - rpc: { nodeUrl: rpc_addr! }, - sequencer: { baseUrl: rpc_addr! }, + nodeUrl: rpc_addr!, }); export interface SyscallsProps { @@ -348,11 +347,11 @@ export function syscalls({ }); // Handle if the tx was reverted if ( - (receipt as RevertedTransactionReceiptResponse).execution_status === + (receipt as GetTransactionReceiptResponse).execution_status === "REVERTED" ) { throw new Error( - (receipt as RevertedTransactionReceiptResponse).revert_reason + (receipt as GetTransactionReceiptResponse).revert_reason ); } // Here we need to process the StartGame event first and use the output for AmbushedByBeast event @@ -463,11 +462,11 @@ export function syscalls({ }); // Handle if the tx was reverted if ( - (receipt as RevertedTransactionReceiptResponse).execution_status === + (receipt as GetTransactionReceiptResponse).execution_status === "REVERTED" ) { throw new Error( - (receipt as RevertedTransactionReceiptResponse).revert_reason + (receipt as GetTransactionReceiptResponse).revert_reason ); } const events = await parseEvents( @@ -733,11 +732,11 @@ export function syscalls({ }); // Handle if the tx was reverted if ( - (receipt as RevertedTransactionReceiptResponse).execution_status === + (receipt as GetTransactionReceiptResponse).execution_status === "REVERTED" ) { throw new Error( - (receipt as RevertedTransactionReceiptResponse).revert_reason + (receipt as GetTransactionReceiptResponse).revert_reason ); } // reset battles by tx hash @@ -1001,11 +1000,11 @@ export function syscalls({ }); // Handle if the tx was reverted if ( - (receipt as RevertedTransactionReceiptResponse).execution_status === + (receipt as GetTransactionReceiptResponse).execution_status === "REVERTED" ) { throw new Error( - (receipt as RevertedTransactionReceiptResponse).revert_reason + (receipt as GetTransactionReceiptResponse).revert_reason ); } // Add optimistic data @@ -1207,11 +1206,11 @@ export function syscalls({ }); // Handle if the tx was reverted if ( - (receipt as RevertedTransactionReceiptResponse).execution_status === + (receipt as GetTransactionReceiptResponse).execution_status === "REVERTED" ) { throw new Error( - (receipt as RevertedTransactionReceiptResponse).revert_reason + (receipt as GetTransactionReceiptResponse).revert_reason ); } // Add optimistic data @@ -1359,11 +1358,11 @@ export function syscalls({ }); // Handle if the tx was reverted if ( - (receipt as RevertedTransactionReceiptResponse).execution_status === + (receipt as GetTransactionReceiptResponse).execution_status === "REVERTED" ) { throw new Error( - (receipt as RevertedTransactionReceiptResponse).revert_reason + (receipt as GetTransactionReceiptResponse).revert_reason ); } const events = await parseEvents( @@ -1444,11 +1443,11 @@ export function syscalls({ }); // Handle if the tx was reverted if ( - (receipt as RevertedTransactionReceiptResponse).execution_status === + (receipt as GetTransactionReceiptResponse).execution_status === "REVERTED" ) { throw new Error( - (receipt as RevertedTransactionReceiptResponse).revert_reason + (receipt as GetTransactionReceiptResponse).revert_reason ); } const events = await parseEvents( diff --git a/ui/src/app/page.tsx b/ui/src/app/page.tsx index ce6acd1fd..28c2a4f7e 100644 --- a/ui/src/app/page.tsx +++ b/ui/src/app/page.tsx @@ -576,7 +576,6 @@ function Home({ updateConnectors }: HomeProps) { multicall={multicall} mintLords={mintLords} lordsBalance={lordsBalance} - arcadeConnectors={arcadeConnectors} gameContract={gameContract!} costToPlay={costToPlay!} /> diff --git a/ui/yarn.lock b/ui/yarn.lock index 3cbd0bb30..239d63de0 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -1263,22 +1263,22 @@ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.2.4.tgz#5abda92fe12b9829bf7951c4a221282c56041144" integrity sha512-0MffFmyv7tBLlji01qc0IaPP/LVExzvj7/R5x1Jph1bTAIj4Vu81yFQWHHQAP6r4ff9Ukj1mBK6MDNVXm7Tcvw== -"@noble/curves@~1.2.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz" - integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== +"@noble/curves@~1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" + integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== dependencies: - "@noble/hashes" "1.3.2" + "@noble/hashes" "1.3.3" "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz" integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== -"@noble/hashes@1.3.2", "@noble/hashes@~1.3.2": - version "1.3.2" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz" - integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== +"@noble/hashes@1.3.3", "@noble/hashes@~1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" + integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== "@noble/secp256k1@1.7.1", "@noble/secp256k1@~1.7.0": version "1.7.1" @@ -1427,36 +1427,6 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@rometools/cli-darwin-arm64@12.1.3": - version "12.1.3" - resolved "https://registry.npmjs.org/@rometools/cli-darwin-arm64/-/cli-darwin-arm64-12.1.3.tgz" - integrity sha512-AmFTUDYjBuEGQp/Wwps+2cqUr+qhR7gyXAUnkL5psCuNCz3807TrUq/ecOoct5MIavGJTH6R4aaSL6+f+VlBEg== - -"@rometools/cli-darwin-x64@12.1.3": - version "12.1.3" - resolved "https://registry.yarnpkg.com/@rometools/cli-darwin-x64/-/cli-darwin-x64-12.1.3.tgz#e5bbf02afb1aab7447e743092245dea992b4b29f" - integrity sha512-k8MbWna8q4LRlb005N2X+JS1UQ+s3ZLBBvwk4fP8TBxlAJXUz17jLLu/Fi+7DTTEmMhM84TWj4FDKW+rNar28g== - -"@rometools/cli-linux-arm64@12.1.3": - version "12.1.3" - resolved "https://registry.yarnpkg.com/@rometools/cli-linux-arm64/-/cli-linux-arm64-12.1.3.tgz#e75b01b74c134edc811e21fa7e1e440602930d59" - integrity sha512-X/uLhJ2/FNA3nu5TiyeNPqiD3OZoFfNfRvw6a3ut0jEREPvEn72NI7WPijH/gxSz55znfQ7UQ6iM4DZumUknJg== - -"@rometools/cli-linux-x64@12.1.3": - version "12.1.3" - resolved "https://registry.yarnpkg.com/@rometools/cli-linux-x64/-/cli-linux-x64-12.1.3.tgz#2b9f4a68079783f275d4d27df83e4fa2220ec6fc" - integrity sha512-csP17q1eWiUXx9z6Jr/JJPibkplyKIwiWPYNzvPCGE8pHlKhwZj3YHRuu7Dm/4EOqx0XFIuqqWZUYm9bkIC8xg== - -"@rometools/cli-win32-arm64@12.1.3": - version "12.1.3" - resolved "https://registry.yarnpkg.com/@rometools/cli-win32-arm64/-/cli-win32-arm64-12.1.3.tgz#714acb67ac4ea4c15e2bc6aea4dd290c76c8efc6" - integrity sha512-RymHWeod57EBOJY4P636CgUwYA6BQdkQjh56XKk4pLEHO6X1bFyMet2XL7KlHw5qOTalzuzf5jJqUs+vf3jdXQ== - -"@rometools/cli-win32-x64@12.1.3": - version "12.1.3" - resolved "https://registry.yarnpkg.com/@rometools/cli-win32-x64/-/cli-win32-x64-12.1.3.tgz#b4f53491d2ca8f1234b3613b7cc73418ad8d76bb" - integrity sha512-yHSKYidqJMV9nADqg78GYA+cZ0hS1twANAjiFibQdXj9aGzD+s/IzIFEIi/U/OBLvWYg/SCw0QVozi2vTlKFDQ== - "@rushstack/eslint-patch@^1.1.3": version "1.2.0" resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz" @@ -1467,6 +1437,11 @@ resolved "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz" integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== +"@scure/base@~1.1.3": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.5.tgz#1d85d17269fe97694b9c592552dd9e5e33552157" + integrity sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ== + "@scure/bip32@1.1.5": version "1.1.5" resolved "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz" @@ -1484,13 +1459,13 @@ "@noble/hashes" "~1.2.0" "@scure/base" "~1.1.0" -"@scure/starknet@~0.3.0": - version "0.3.0" - resolved "https://registry.npmjs.org/@scure/starknet/-/starknet-0.3.0.tgz" - integrity sha512-Ma66yZlwa5z00qI5alSxdWtIpky5LBhy22acVFdoC5kwwbd9uDyMWEYzWHdNyKmQg9t5Y2UOXzINMeb3yez+Gw== +"@scure/starknet@~1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@scure/starknet/-/starknet-1.0.0.tgz#4419bc2fdf70f3dd6cb461d36c878c9ef4419f8c" + integrity sha512-o5J57zY0f+2IL/mq8+AYJJ4Xpc1fOtDhr+mFQKbHnYFmm3WQrC+8zj2HEgxak1a+x86mhmBC1Kq305KUpVf0wg== dependencies: - "@noble/curves" "~1.2.0" - "@noble/hashes" "~1.3.2" + "@noble/curves" "~1.3.0" + "@noble/hashes" "~1.3.3" "@stablelib/aead@^1.0.1": version "1.0.1" @@ -1626,17 +1601,17 @@ "@stablelib/random" "^1.0.2" "@stablelib/wipe" "^1.0.1" -"@starknet-react/chains@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@starknet-react/chains/-/chains-0.1.3.tgz#8e5d248f860b850b0b03e1059d6a4010d4801818" - integrity sha512-dSpLgDS02PmPzFVoW07EBVRbsX+h7MH7DYx2FF7vJv/J6eMGY7KtSupJW9R6ys0iADAxSg0UDcZr7syuy+b/Pg== +"@starknet-react/chains@^0.1.7": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@starknet-react/chains/-/chains-0.1.7.tgz#58503379e2ffabe33b4f6e0f2aef775e84745a4d" + integrity sha512-UNh97I1SvuJKaAhKOmpEk8JcWuZWMlPG/ba2HcvFYL9x/47BKndJ+Da9V+iJFtkHUjreVnajT1snsaz1XMG+UQ== -"@starknet-react/core@^2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@starknet-react/core/-/core-2.1.5.tgz#a6a68f5223bd42190f3235e9737a66f47f4b3c07" - integrity sha512-lHBZMVFkz7XzyCy24Ca7/b55AkJU+alTD5520lBEbqYYOUAALdKCVp8+rlwPYv5Z8pkJzqRLoB//X6mTsky6Lw== +"@starknet-react/core@^2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@starknet-react/core/-/core-2.5.0.tgz#77da3204f2207e3b4b25bfef40ee80df12651534" + integrity sha512-nH5Vf0oAeqn0+rerUpaAzgbd9z+sBKYZNk6pgFX2s1BNCuRGXyJDoVkQoouVeGKdJO2M8jyMDUVWQn9rE1dDqw== dependencies: - "@starknet-react/chains" "^0.1.3" + "@starknet-react/chains" "^0.1.7" "@tanstack/react-query" "^5.0.1" eventemitter3 "^5.0.1" immutable "^4.3.4" @@ -1754,17 +1729,17 @@ dependencies: tslib "^2.4.0" -"@tanstack/query-core@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.14.0.tgz#96a88030b7f104a37d42e549dd7453373ba7879f" - integrity sha512-OEri9fVDYT8XEqgh/dc6fFp1niyqu+MDY+Vp/LwU+scdk9xQLZ7KdUMEUh/sqTEjRM5BlFzAhAv+EIYcvSxt0Q== +"@tanstack/query-core@5.28.4": + version "5.28.4" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.28.4.tgz#fa416532f8b33ca8608d40bb9728b60e2e1a38dc" + integrity sha512-uQZqOFqLWUvXNIQZ63XdKzg22NtHzgCBUfDmjDHi3BoF+nUYeBNvMi/xFPtFrMhqRzG2Ir4mYaGsWZzmiEjXpA== "@tanstack/react-query@^5.0.1": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.14.0.tgz#d2c79ee88f473cd859b9a7885d2f5c33ca3bd749" - integrity sha512-+qCooNZr7aGr6a0UEblfEgDSO1y+H7h7JnT4nUlbyfgCGE695lmBiqTciuW1C1Jr6J6Z2bwyd6YmBDKFKszWhA== + version "5.28.4" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.28.4.tgz#32e56ca4fd08513a906fe6323908f0e38ffccbba" + integrity sha512-BErcoB/QQG6YwLSUKnaGxF+lSc270RH2w3kMBpG0i4YzDCsFs2pdxPX1WVknQvFk9bNgukMb158hc2Zb4SdwSA== dependencies: - "@tanstack/query-core" "5.14.0" + "@tanstack/query-core" "5.28.4" "@trpc/client@^10.38.1": version "10.44.1" @@ -2120,15 +2095,14 @@ dependencies: tslib "^2.3.0" -abi-wan-kanabi@^1.0.1, abi-wan-kanabi@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/abi-wan-kanabi/-/abi-wan-kanabi-1.0.3.tgz" - integrity sha512-Xwva0AnhXx/IVlzo3/kwkI7Oa7ZX7codtcSn+Gmoa2PmjGPF/0jeVud9puasIPtB7V50+uBdUj4Mh3iATqtBvg== +abi-wan-kanabi@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/abi-wan-kanabi/-/abi-wan-kanabi-2.2.1.tgz#367050c57b9e66a7cf977453d85579ad1fd8af36" + integrity sha512-W3RNuu2tG10W4AY63uq89JX/MsZSOxvpmsitQ3pbdVn3e8RxXR2oegN0QmGpgfyT0KlPdreydHsqq/u+2Pt2PQ== dependencies: - abi-wan-kanabi "^1.0.1" + ansicolors "^0.3.2" + cardinal "^2.1.1" fs-extra "^10.0.0" - rome "^12.1.3" - typescript "^4.9.5" yargs "^17.7.2" acorn-jsx@^5.3.2: @@ -2158,7 +2132,7 @@ ajv@^6.10.0, ajv@^6.12.4: ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-styles@^3.2.1: @@ -2175,6 +2149,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansicolors@^0.3.2, ansicolors@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" + integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz" @@ -2445,6 +2424,14 @@ caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.300014 resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz" integrity sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw== +cardinal@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" + integrity sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw== + dependencies: + ansicolors "~0.3.2" + redeyed "~2.1.0" + cbor-extract@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.1.1.tgz" @@ -2526,7 +2513,7 @@ clipboardy@^3.0.0: cliui@^8.0.1: version "8.0.1" - resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: string-width "^4.2.0" @@ -2552,7 +2539,7 @@ color-convert@^1.9.0: color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" @@ -2564,7 +2551,7 @@ color-name@1.1.3: color-name@~1.1.4: version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== commander@^4.0.0: @@ -2895,7 +2882,7 @@ elliptic@^6.5.4: emoji-regex@^8.0.0: version "8.0.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== emoji-regex@^9.2.2: @@ -3018,9 +3005,9 @@ es-to-primitive@^1.2.1: is-symbol "^1.0.2" escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== escape-string-regexp@^1.0.5: version "1.0.5" @@ -3214,6 +3201,11 @@ espree@^9.5.1: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.0" +esprima@~4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + esquery@^1.4.2: version "1.5.0" resolved "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz" @@ -3331,6 +3323,14 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fetch-cookie@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-3.0.1.tgz#6a77f7495e1a639ae019db916a234db8c85d5963" + integrity sha512-ZGXe8Y5Z/1FWqQ9q/CrJhkUD73DyBU9VF0hBQmEO/wPHe4A9PKTjplFDLeFX8aOsYypZUcX5Ji/eByn3VCVO3Q== + dependencies: + set-cookie-parser "^2.4.8" + tough-cookie "^4.0.0" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" @@ -3385,7 +3385,7 @@ fraction.js@^4.2.0: fs-extra@^10.0.0: version "10.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== dependencies: graceful-fs "^4.2.0" @@ -3429,7 +3429,7 @@ gensync@^1.0.0-beta.2: get-caller-file@^2.0.5: version "2.0.5" - resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: @@ -3705,9 +3705,9 @@ ignore@^5.2.0: integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== immutable@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f" - integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA== + version "4.3.5" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.5.tgz#f8b436e66d59f99760dc577f5c99a4fd2a5cc5a0" + integrity sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw== import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" @@ -3844,7 +3844,7 @@ is-extglob@^2.1.1: is-fullwidth-code-point@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: @@ -3987,7 +3987,7 @@ isexe@^2.0.0: isomorphic-fetch@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA== dependencies: node-fetch "^2.6.1" @@ -4076,7 +4076,7 @@ jsonc-parser@^3.2.0: jsonfile@^6.0.1: version "6.1.0" - resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== dependencies: universalify "^2.0.0" @@ -4198,10 +4198,10 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lossless-json@^2.0.8: - version "2.0.11" - resolved "https://registry.npmjs.org/lossless-json/-/lossless-json-2.0.11.tgz" - integrity sha512-BP0vn+NGYvzDielvBZaFain/wgeJ1hTvURCqtKvhr1SCPePdaaTanmmcplrHfEJSJOUql7hk4FHwToNJjWRY3g== +lossless-json@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lossless-json/-/lossless-json-4.0.1.tgz#d45229e3abb213a0235812780ca894ea8c5b2c6b" + integrity sha512-l0L+ppmgPDnb+JGxNLndPtJZGNf6+ZmVaQzoxQm3u6TXmhdnsA+YtdVR8DjzZd/em58686CQhOFDPewfJ4l7MA== lru-cache@^10.0.2: version "10.1.0" @@ -4385,7 +4385,7 @@ node-fetch-native@^1.4.0, node-fetch-native@^1.4.1: node-fetch@^2.6.1: version "2.7.0" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" @@ -4590,7 +4590,7 @@ p-locate@^5.0.0: pako@^2.0.4: version "2.1.0" - resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== parent-module@^1.0.0: @@ -4785,11 +4785,21 @@ prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +psl@^1.1.33: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + punycode@^2.1.0: version "2.3.0" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== +punycode@^2.1.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + query-string@7.1.3, query-string@^7.1.1: version "7.1.3" resolved "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz" @@ -4800,6 +4810,11 @@ query-string@7.1.3, query-string@^7.1.1: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" @@ -4901,6 +4916,13 @@ real-require@^0.1.0: resolved "https://registry.npmjs.org/real-require/-/real-require-0.1.0.tgz" integrity sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg== +redeyed@~2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" + integrity sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ== + dependencies: + esprima "~4.0.0" + redis-errors@^1.0.0, redis-errors@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz" @@ -4967,9 +4989,14 @@ regjsparser@^0.9.1: require-directory@^2.1.1: version "2.1.1" - resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" @@ -5010,18 +5037,6 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -rome@^12.1.3: - version "12.1.3" - resolved "https://registry.npmjs.org/rome/-/rome-12.1.3.tgz" - integrity sha512-e+ff72hxDpe/t5/Us7YRBVw3PBET7SeczTQNn6tvrWdrCaAw3qOukQQ+tDCkyFtS4yGsnhjrJbm43ctNbz27Yg== - optionalDependencies: - "@rometools/cli-darwin-arm64" "12.1.3" - "@rometools/cli-darwin-x64" "12.1.3" - "@rometools/cli-linux-arm64" "12.1.3" - "@rometools/cli-linux-x64" "12.1.3" - "@rometools/cli-win32-arm64" "12.1.3" - "@rometools/cli-win32-x64" "12.1.3" - run-applescript@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz" @@ -5079,6 +5094,11 @@ semver@^7.3.7: dependencies: lru-cache "^6.0.0" +set-cookie-parser@^2.4.8: + version "2.6.0" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz#131921e50f62ff1a66a461d7d62d7b21d5d15a51" + integrity sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ== + shallow-equal@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz" @@ -5164,17 +5184,20 @@ starknet@^4.17.1: ts-custom-error "^3.3.1" url-join "^4.0.1" -starknet@^5.19.5: - version "5.19.5" - resolved "https://registry.npmjs.org/starknet/-/starknet-5.19.5.tgz" - integrity sha512-S7V4ifyYd+ApsIwYTd7YA5U2Px+NZkCsQPnmgY/wkc5LLFKhYMNpzHQ5nIA15p70AwtSXCcsEBnHNRBOuci13Q== +starknet@^6.4.3: + version "6.5.0" + resolved "https://registry.yarnpkg.com/starknet/-/starknet-6.5.0.tgz#1b984dcf6e4f1960a64d83a84391e98b9926b345" + integrity sha512-3W7cpMPE6u1TAjZoT1gfqAtTpSTkAFXwwVbt9IG3oyk8gxBwzpadcMXZ5JRBOv9p06qfnivRkWl2Q1B4tIrSAg== dependencies: - "@noble/curves" "~1.2.0" - "@scure/starknet" "~0.3.0" - abi-wan-kanabi "^1.0.3" + "@noble/curves" "~1.3.0" + "@scure/base" "~1.1.3" + "@scure/starknet" "~1.0.0" + abi-wan-kanabi "^2.2.1" + fetch-cookie "^3.0.0" isomorphic-fetch "^3.0.0" - lossless-json "^2.0.8" + lossless-json "^4.0.1" pako "^2.0.4" + ts-mixer "^6.0.3" url-join "^4.0.1" starknetkit@^1.0.22: @@ -5218,7 +5241,7 @@ strict-uri-encode@^2.0.0: string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" @@ -5275,7 +5298,7 @@ string_decoder@^1.1.1: strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" @@ -5458,9 +5481,19 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tough-cookie@^4.0.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" + integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + tr46@~0.0.3: version "0.0.3" - resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== trpc-browser@^1.3.2: @@ -5485,6 +5518,11 @@ ts-invariant@^0.10.3: dependencies: tslib "^2.1.0" +ts-mixer@^6.0.3: + version "6.0.4" + resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.4.tgz#1da39ceabc09d947a82140d9f09db0f84919ca28" + integrity sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA== + tsconfig-paths@^3.14.1: version "3.14.2" resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz" @@ -5538,11 +5576,6 @@ typescript@5.0.2: resolved "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz" integrity sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw== -typescript@^4.9.5: - version "4.9.5" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== - ufo@^1.3.0, ufo@^1.3.1, ufo@^1.3.2: version "1.3.2" resolved "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz" @@ -5604,10 +5637,15 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== unstorage@^1.9.0: version "1.10.1" @@ -5662,9 +5700,17 @@ uri-js@^4.2.2: url-join@^4.0.1: version "4.0.1" - resolved "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + use-isomorphic-layout-effect@^1.1.0: version "1.1.2" resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz" @@ -5696,17 +5742,17 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2: webidl-conversions@^3.0.0: version "3.0.1" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== whatwg-fetch@^3.4.1: - version "3.6.19" - resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz" - integrity sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw== + version "3.6.20" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" + integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== whatwg-url@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== dependencies: tr46 "~0.0.3" @@ -5759,7 +5805,7 @@ word-wrap@^1.2.3: wrap-ansi@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -5778,7 +5824,7 @@ ws@^7.5.1: y18n@^5.0.5: version "5.0.8" - resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^3.0.2: @@ -5798,12 +5844,12 @@ yaml@^2.1.1: yargs-parser@^21.1.1: version "21.1.1" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@^17.7.2: version "17.7.2" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1"