Contract

0x6502770B7eCD88653E9A9e17c9FA13ef0859052c

Overview

S Balance

Sonic LogoSonic LogoSonic Logo0 S

S Value

-

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

Parent Transaction Hash Block From To
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
ItemControllerHelper

Compiler Version
v0.8.23+commit.f704f362

Optimization Enabled:
Yes with 50 runs

Other Settings:
istanbul EvmVersion, BSL 1.1 license
File 1 of 48 : ItemControllerHelper.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../interfaces/IItemControllerHelper.sol";
import "../lib/ItemControllerHelperLib.sol";
import "../proxy/Controllable.sol";
import "../relay/ERC2771Context.sol";

contract ItemControllerHelper is Controllable, ERC2771Context, IItemControllerHelper {
  using EnumerableSet for EnumerableSet.AddressSet;

  //region ------------------------ Constants

  /// @notice Version of the contract
  string public constant VERSION = "1.0.0";
  //endregion ------------------------ Constants

  //region ------------------------ Initializer
  function init(address controller_) external initializer {
    __Controllable_init(controller_);
  }
  //endregion ------------------------ Initializer

  //region ------------------------ Views
  function listUnionConfigsLength() external view returns (uint) {
    return ItemControllerHelperLib.listUnionConfigsLength();
  }

  function unionConfigIdByIndex(uint index) external view returns (uint) {
    return ItemControllerHelperLib.unionConfigIdByIndex(index);
  }

  function unionConfig(uint configId) external view returns (IItemControllerHelper.UnionConfig memory) {
    return ItemControllerHelperLib.unionConfig(configId);
  }

  function keyPassItem() external view returns (address) {
    return ItemControllerHelperLib.keyPassItem();
  }

  function getGlobalParamValue(uint8 globalParam) external view returns (uint) {
    return ItemControllerHelperLib.getGlobalParamValue(globalParam);
  }
  //endregion ------------------------ Views

  //region ------------------------ Deployer actions
  function addUnionConfig(address[] memory items, uint[] memory count, address itemToMint) external {
    ItemControllerHelperLib.addUnionConfig(IController(controller()), items, count, itemToMint);
  }

  function updateUnionConfig(uint configId, address[] memory items, uint[] memory count, address itemToMint) external {
    ItemControllerHelperLib.updateUnionConfig(IController(controller()), configId, items, count, itemToMint);
  }
  
  function removeUnionConfig(uint configId) external {
    ItemControllerHelperLib.removeUnionConfig(IController(controller()), configId);
  }

  function setUnionKeyPassItem(address item) external {
  ItemControllerHelperLib.setUnionKeyPassItem(IController(controller()), item);
  }
  //endregion ------------------------ Deployer actions

  //region ------------------------ Item controller actions
  /// @notice Check if list of {items} passed by {user} fit to config, ensure that user is able to combine items
  /// @param user User that is going to combine items
  /// @param items List of items to destroy. Should contain exactly same items as the config. Items order doesn't matter
  /// @param tokens List of tokens of each item to destroy. Length of the tokens should fit to count specified in config
  /// @dev itemTokenIds[count tokens of i-th item][countItems]
  /// @return Item to mint in exchange of {items}
  function prepareToCombine(address user, uint configId, address[] memory items, uint[][] memory tokens) external view returns (address) {
    return ItemControllerHelperLib.prepareToCombine(user, configId, items, tokens);
  }

  //endregion ------------------------ Item controller actions
}

File 2 of 48 : IAppErrors.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

/// @notice All errors of the app
interface IAppErrors {

  //region ERC20Errors
  /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     */
  error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);

  /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
  error ERC20InvalidSender(address sender);

  /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
  error ERC20InvalidReceiver(address receiver);

  /**
     * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     * @param allowance Amount of tokens a `spender` is allowed to operate with.
     * @param needed Minimum amount required to perform a transfer.
     */
  error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

  /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
  error ERC20InvalidApprover(address approver);

  /**
   * @dev Indicates a failure with the `spender` to be approved. Used in approvals.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     */
  error ERC20InvalidSpender(address spender);

  //endregion ERC20Errors

  //region ERC721Errors

  /**
  * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20.
     * Used in balance queries.
     * @param owner Address of the current owner of a token.
     */
  error ERC721InvalidOwner(address owner);

  /**
   * @dev Indicates a `tokenId` whose `owner` is the zero address.
     * @param tokenId Identifier number of a token.
     */
  error ERC721NonexistentToken(uint256 tokenId);

  /**
   * @dev Indicates an error related to the ownership over a particular token. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param tokenId Identifier number of a token.
     * @param owner Address of the current owner of a token.
     */
  error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);

  /**
   * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
  error ERC721InvalidSender(address sender);

  /**
   * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
  error ERC721InvalidReceiver(address receiver);

  /**
   * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param tokenId Identifier number of a token.
     */
  error ERC721InsufficientApproval(address operator, uint256 tokenId);

  /**
   * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
  error ERC721InvalidApprover(address approver);

  /**
   * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
  error ERC721InvalidOperator(address operator);

  //endregion ERC721Errors

  error ZeroAddress();
  error ZeroValueNotAllowed();
  error ZeroToken();
  error LengthsMismatch();
  error NotEnoughBalance();
  error NotEnoughAllowance();
  error EmptyNameNotAllowed();
  error NotInitialized();
  error AlreadyInitialized();
  error ReentrancyGuardReentrantCall();
  error TooLongString();
  error AlreadyDeployed(address deployed);

  //region Restrictions
  error ErrorNotDeployer(address sender);
  error ErrorNotGoc();
  error NotGovernance(address sender);
  error ErrorOnlyEoa();
  error NotEOA(address sender);
  error ErrorForbidden(address sender);
  error AdminOnly();
  error ErrorNotItemController(address sender);
  error ErrorNotHeroController(address sender);
  error ErrorNotDungeonFactory(address sender);
  error ErrorNotObjectController(address sender);
  error ErrorNotStoryController();
  error ErrorNotAllowedSender();
  error MintNotAllowed();
  //endregion Restrictions

  //region PackingLib
  error TooHighValue(uint value);
  error IntValueOutOfRange(int value);
  error OutOfBounds(uint index, uint length);
  error UnexpectedValue(uint expected, uint actual);
  error WrongValue(uint newValue, uint actual);
  error IntOutOfRange(int value);
  error ZeroValue();
  /// @notice packCustomDataChange requires an input string with two zero bytes at the beginning
  ///         0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0000
  /// This error happens if these bytes are not zero
  error IncompatibleInputString();
  error IncorrectOtherItemTypeKind(uint8 kind);
  //endregion PackingLib

  //region Hero
  error ErrorHeroIsNotRegistered(address heroToken);
  error ErrorHeroIsDead(address heroToken, uint heroTokenId);
  error ErrorHeroNotInDungeon();
  error HeroInDungeon();
  error ErrorNotOwner(address token, uint tokenId);
  error Staked(address heroToken, uint heroId);
  error NameTaken();
  error TooBigName();
  error WrongSymbolsInTheName();
  error NoPayToken(address token, uint payTokenAmount);
  error AlreadyHaveReinforcement();
  /// @notice SIP-001 - Reinforcement requires 3 skills
  error ErrorReinforcementRequiresThreeSkills();
  error WrongTier(uint tier);
  error NotEnoughNgLevel(uint8 ngLevel);
  error NgpNotActive(address hero);
  error RebornNotAllowed();
  error AlreadyPrePaidHero();
  //endregion Hero

  //region Dungeon
  error ErrorDungeonIsFreeAlready();
  error ErrorNoEligibleDungeons();
  error ErrorDungeonBusy();
  error ErrorNoDungeonsForBiome(uint8 heroBiome);
  error ErrorDungeonCompleted();
  error ErrorAlreadyInDungeon();
  error NotEnoughTokens(uint balance, uint expectedBalance);
  error DungeonAlreadySpecific(uint16 dungNum);
  error DungeonAlreadySpecific2(uint16 dungNum);
  error WrongSpecificDungeon();
  //endregion Dungeon

  //region Items
  error ErrorItemNotEligibleForTheSlot(uint itemType, uint8 itemSlot);
  error ErrorItemSlotBusyHand(uint8 slot);
  error ErrorItemSlotBusy();
  error ErrorItemNotInSlot();
  error ErrorConsumableItemIsUsed(address item);
  error ErrorCannotRemoveItemFromMap();
  error ErrorCannotRemoveDataFromMap();
  error EquippedItemsExist();
  error ItemEquipped(address item, uint itemId);
  error ZeroItemMetaType();
  error NotZeroOtherItemMetaType();
  error ZeroLevel();
  error ItemTypeChanged();
  error ItemMetaTypeChanged();
  error UnknownItem(address item);
  error ErrorEquipForbidden();
  error EquipForbiddenInDungeon();
  error TakeOffForbiddenInDungeon();
  error Consumable(address item);
  error NotConsumable(address item);
  error Broken(address item);
  error ZeroLife();
  error RequirementsToItemAttributes();
  error NotEquipped(address item);
  error ZeroDurability();
  error ZeroAugmentation();
  error TooHighAgLevel(uint8 augmentationLevel);
  error UseForbiddenZeroPayToken();
  error IncorrectMinMaxAttributeRange(int32 min, int32 max);
  error SameIdsNotAllowed();
  error ZeroFragility();
  error OtherTypeItemNotRepairable();
  error NotOther();
  error DoubleItemUsageForbidden(uint itemIndex, address[] items);
  error ItemAlreadyUsedInSlot(address item, uint8 equippedSlot);
  error WrongWayToRegisterItem();
  error UnionItemNotFound(address item);
  error WrongListUnionItemTokens(address item, uint countTokens, uint requiredCountTokens);
  error UnknownUnionConfig(uint unionConfigId);
  error UserHasNoKeyPass(address user, address keyPassItem);
  error MaxValue(uint value);
  error UnexpectedOtherItem(address item);
  error NotExist();
  //endregion Items

  //region Stages
  error ErrorWrongStage(uint stage);
  error ErrorNotStages();
  //endregion Stages

  //region Level
  error ErrorWrongLevel(uint heroLevel);
  error ErrorLevelTooLow(uint heroLevel);
  error ErrorHeroLevelStartFrom1();
  error ErrorWrongLevelUpSum();
  error ErrorMaxLevel();
  //endregion Level

  //region Treasure
  error ErrorNotValidTreasureToken(address treasureToken);
  //endregion Treasure

  //region State
  error ErrorPaused();
  error ErrorNotReady();
  error ErrorNotObject1();
  error ErrorNotObject2();
  error ErrorNotCompleted();
  //endregion State

  //region Biome
  error ErrorNotBiome();
  error ErrorIncorrectBiome(uint biome);
  error TooHighBiome(uint biome);
  //endregion Biome

  //region Misc
  error ErrorWrongMultiplier(uint multiplier);
  error ErrorNotEnoughMana(uint32 mana, uint requiredMana);
  error ErrorExperienceMustNotDecrease();
  error ErrorNotEnoughExperience();
  error ErrorNotChances();
  error ErrorNotEligible(address heroToken, uint16 dungNum);
  error ErrorZeroKarmaNotAllowed();
  //endregion Misc

  //region GOC
  error GenObjectIdBiomeOverflow(uint8 biome);
  error GenObjectIdSubTypeOverflow(uint subType);
  error GenObjectIdIdOverflow(uint id);
  error UnknownObjectTypeGoc1(uint8 objectType);
  error UnknownObjectTypeGoc2(uint8 objectType);
  error UnknownObjectTypeGocLib1(uint8 objectType);
  error UnknownObjectTypeGocLib2(uint8 objectType);
  error UnknownObjectTypeForSubtype(uint8 objectSubType);
  error FightDelay();
  error ZeroChance();
  error TooHighChance(uint32 chance);
  error TooHighRandom(uint random);
  error EmptyObjects();
  error ObjectNotFound();
  error WrongGetObjectTypeInput();
  error WrongChances(uint32 chances, uint32 maxChances);
  //endregion GOC

  //region Story
  error PageNotRemovedError(uint pageId);
  error NotItem1();
  error NotItem2();
  error NotRandom(uint32 random);
  error NotHeroData();
  error NotGlobalData();
  error ZeroStoryIdRemoveStory();
  error ZeroStoryIdStoryAction();
  error ZeroStoryIdAction();
  error NotEnoughAmount(uint balance, uint requiredAmount);
  error NotAnswer();
  error AnswerStoryIdMismatch(uint16 storyId, uint16 storyIdFromAnswerHash);
  error AnswerPageIdMismatch(uint16 pageId, uint16 pageIdFromAnswerHash);
  //endregion Story

  //region FightLib
  error NotMagic();
  error NotAType(uint atype);
  //endregion FightLib

  //region MonsterLib
  error NotYourDebuffItem();
  error UnknownAttackType(uint attackType);
  error NotYourAttackItem();
  /// @notice The skill item cannot be used because it doesn't belong either to the hero or to the hero's helper
  error NotYourBuffItem();
  //endregion MonsterLib

  //region GameToken
  error ApproveToZeroAddress();
  error MintToZeroAddress();
  error TransferToZeroAddress();
  error TransferAmountExceedsBalance(uint balance, uint value);
  error InsufficientAllowance();
  error BurnAmountExceedsBalance();
  error NotMinter(address sender);
  //endregion GameToken

  //region NFT
  error TokenTransferNotAllowed();
  error IdOverflow(uint id);
  error NotExistToken(uint tokenId);
  error EquippedItemIsNotAllowedToTransfer(uint tokenId);
  //endregion NFT

  //region CalcLib
  error TooLowX(uint x);
  //endregion CalcLib

  //region Controller
  error NotFutureGovernance(address sender);
  //endregion Controller

  //region Oracle
  error OracleWrongInput();
  //endregion Oracle

  //region ReinforcementController
  error AlreadyStaked();
  error MaxFee(uint8 fee);
  error MinFee(uint8 fee);
  error StakeHeroNotStats();
  error NotStaked();
  error NoStakedHeroes();
  error GuildHelperNotAvailable(uint guildId, address helper, uint helperId);
  error HelperNotAvailableInGivenBiome();
  //endregion ReinforcementController

  //region SponsoredHero
  error InvalidHeroClass();
  error ZeroAmount();
  error InvalidProof();
  error NoHeroesAvailable();
  error AlreadyRegistered();
  //endregion SponsoredHero

  //region SacraRelay
  error SacraRelayNotOwner();
  error SacraRelayNotDelegator();
  error SacraRelayNotOperator();
  error SacraRelayInvalidChainId(uint callChainId, uint blockChainId);
  error SacraRelayInvalidNonce(uint callNonce, uint txNonce);
  error SacraRelayDeadline();
  error SacraRelayDelegationExpired();
  error SacraRelayNotAllowed();
  error SacraRelayInvalidSignature();
  /// @notice This error is generated when custom error is caught
  /// There is no info about custom error in SacraRelay
  /// but you can decode custom error by selector, see tests
  error SacraRelayNoErrorSelector(bytes4 selector, string tracingInfo);
  /// @notice This error is generated when custom error is caught
  /// There is no info about custom error in SacraRelay
  /// but you can decode custom error manually from {errorBytes} as following:
  /// if (keccak256(abi.encodeWithSignature("MyError()")) == keccak256(errorBytes)) { ... }
  error SacraRelayUnexpectedReturnData(bytes errorBytes, string tracingInfo);
  error SacraRelayCallToNotContract(address notContract, string tracingInfo);
  //endregion SacraRelay

  //region Misc
  error UnknownHeroClass(uint heroClass);
  error AbsDiff(int32 a, int32 b);
  //region Misc

  //region ------------------------ UserController
  error NoAvailableLootBox(address msgSender, uint lootBoxKind);
  error FameHallHeroAlreadyRegistered(uint8 openedNgLevel);

  //endregion ------------------------ UserController

  //region ------------------------ Guilds
  error AlreadyGuildMember();
  error NotGuildMember();
  error WrongGuild();
  error GuildActionForbidden(uint right);
  error GuildHasMaxSize(uint guildSize);
  error GuildHasMaxLevel(uint level);
  error TooLongUrl();
  error TooLongDescription();
  error CannotRemoveGuildOwnerFromNotEmptyGuild();
  error GuildControllerOnly();
  error GuildAlreadyHasShelter();
  error ShelterIsBusy();
  error ShelterIsNotRegistered();
  error ShelterIsNotOwnedByTheGuild();
  error ShelterIsInUse();
  error GuildHasNoShelter();
  error ShelterBidIsNotAllowedToBeUsed();
  error ShelterHasHeroesInside();
  error SecondGuildAdminIsNotAllowed();
  error NotEnoughGuildBankBalance(uint guildId);

  error GuildReinforcementCooldownPeriod();
  error NoStakedGuildHeroes();
  error NotStakedInGuild();
  error ShelterHasNotEnoughLevelForReinforcement();
  error NotBusyGuildHelper();

  error GuildRequestNotActive();
  error GuildRequestNotAvailable();
  error NotAdminCannotAddMemberWithNotZeroRights();
  //endregion ------------------------ Guilds

  //region ------------------------ Shelters
  error ErrorNotShelterController();
  error ErrorNotGuildController();
  error ShelterHasNotItem(uint shelterId, address item);
  error MaxNumberItemsSoldToday(uint numSoldItems, uint limit);
  error GuildHasNotEnoughPvpPoints(uint64 pointsAvailable, uint pointRequired);
  error FreeShelterItemsAreNotAllowed(uint shelterId, address item);
  error TooLowShelterLevel(uint8 shelterLevel, uint8 allowedShelterLevel);
  error NotEnoughPvpPointsCapacity(address user, uint usedPoints, uint pricePvpPoints, uint64 capactiy);
  error IncorrectShelterLevel(uint8 shelterLevel);
  //endregion ------------------------ Shelters

  //region ------------------------ Auction
  error WrongAuctionPosition();
  error AuctionPositionClosed();
  error AuctionBidOpened(uint positionId);
  error TooLowAmountToBid();
  error AuctionEnded();
  error TooLowAmountForNewBid();
  error AuctionSellerOnly();
  error AuctionBuyerOnly();
  error AuctionBidNotFound();
  error AuctionBidClosed();
  error OnlyShelterAuction();
  error CannotCloseLastBid();
  error AuctionNotEnded();
  error NotShelterAuction();
  error AuctionPositionOpened(uint positionId);
  error AuctionSellerCannotBid();
  error AuctionGuildWithShelterCannotBid();
  error AuctionBidExists();
  //endregion ------------------------ Auction

  //region ------------------------ Pawnshop
  error AuctionPositionNotSupported(uint positionId);
  error PositionNotSupported(uint positionId);
  error NotNftPositionNotSupported(uint positionId);
  error CallFailed(bytes callResultData);

  error PawnShopZeroOwner();
  error PawnShopZeroFeeRecipient();
  error PawnShopNotOwner();
  error PawnShopAlreadyAnnounced();
  error PawnShopTimeLock();
  error PawnShopWrongAddressValue();
  error PawnShopWrongUintValue();
  error PawnShopZeroAddress();
  error PawnShopTooHighValue();
  error PawnShopZeroAToken();
  error PawnShopZeroCToken();
  error PawnShopWrongAmounts();
  error PawnShopPosFeeForInstantDealForbidden();
  error PawnShopPosFeeAbsurdlyHigh();
  error PawnShopIncorrect();
  error PawnShopWrongId();
  error PawnShopNotBorrower();
  error PawnShopPositionClosed();
  error PawnShopPositionExecuted();
  error PawnShopWrongBidAmount();
  error PawnShopTooLowBid();
  error PawnShopNewBidTooLow();
  error PawnShopBidAlreadyExists();
  error PawnShopAuctionEnded();
  error PawnShopNotLender();
  error PawnShopTooEarlyToClaim();
  error PawnShopPositionNotExecuted();
  error PawnShopAlreadyClaimed();
  error PawnShopAuctionNotEnded();
  error PawnShopBidClosed();
  error PawnShopNoBids();
  error PawnShopAuctionBidNotFound();
  error PawnShopWrongBid();
  error PawnShopBidNotFound();

  //endregion ------------------------ Pawnshop
}

File 3 of 48 : IApplicationEvents.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "./IGOC.sol";
import "./IStatController.sol";
import "./IDungeonFactory.sol";
import "./IStoryController.sol";
import "./IFightCalculator.sol";

/// @notice All events of the app
interface IApplicationEvents {

  //region ------------------ StatController
  event HeroItemSlotChanged(
    address heroToken,
    uint heroTokenId,
    uint itemType,
    uint itemSlot,
    address itemToken,
    uint itemTokenId,
    bool equip,
    address caller
  );
  event CurrentStatsChanged(
    address heroToken,
    uint heroTokenId,
    IStatController.ChangeableStats change,
    bool increase,
    address caller
  );
  event BonusAttributesChanged(
    address heroToken,
    uint heroTokenId,
    bool add,
    bool temporally,
    address caller
  );
  event TemporallyAttributesCleared(address heroToken, uint heroTokenId, address caller);
  event NewHeroInited(address heroToken, uint heroTokenId, IStatController.ChangeableStats stats);
  event LevelUp(
    address heroToken,
    uint heroTokenId,
    uint heroClass,
    IStatController.CoreAttributes change
  );
  event ConsumableUsed(address heroToken, uint heroTokenId, address item);
  event RemoveConsumableUsage(address heroToken, uint heroTokenId, address item);
  event HeroCustomDataChanged(address token, uint tokenId, bytes32 index, uint value);
  event HeroCustomDataChangedNg(address token, uint tokenId, bytes32 index, uint value, uint8 ngLevel);
  event HeroCustomDataCleared(address token, uint tokenId);
  event GlobalCustomDataChanged(bytes32 index, uint value);
  //endregion ------------------ StatController

  //region ------------------ DungeonFactoryController
  event DungeonLaunched(
    uint16 dungeonLogicNum,
    uint64 dungeonId,
    address heroToken,
    uint heroTokenId,
    address treasuryToken,
    uint treasuryAmount
  );

  event BossCompleted(uint32 objectId, uint biome, address hero, uint heroId);
  event FreeDungeonAdded(uint8 biome, uint64 dungeonId);

  event ObjectOpened(uint64 dungId, address hero, uint id, uint32 objId, uint iteration, uint currentStage);
  event Clear(uint64 dungId);

  event DungeonLogicRegistered(uint16 dungLogicId, IDungeonFactory.DungeonGenerateInfo info);
  event DungeonLogicRemoved(uint16 dungLogicId);
  event DungeonSpecificLogicRegistered(uint16 dungLogicId, uint biome, uint heroCls);
  event DungeonSpecificLogicRemoved(uint16 dungLogicId, uint heroLvl, uint heroCls);
  event DungeonRegistered(uint16 dungLogicId, uint64 dungeonId);
  event DungeonRemoved(uint16 dungLogicId, uint64 dungeonId);
  event MinLevelForTreasuryChanged(address token, uint level);

  event ObjectAction(
    uint64 dungId,
    IGOC.ActionResult result,
    uint currentStage,
    address heroToken,
    uint heroTokenId,
    uint newStage
  );
  /// @notice On add the item to the dungeon
  event AddTreasuryItem(uint64 dungId, address itemAdr, uint itemId);
  event AddTreasuryToken(uint64 dungId, address token, uint amount);
  event ClaimToken(uint64 dungId, address token, uint amount);
  event ClaimItem(uint64 dungId, address token, uint id);

  event Entered(uint64 dungId, address hero, uint id);
  event DungeonCompleted(uint16 dungLogicNum, uint64 dungId, address hero, uint heroId);
  event Exit(uint64 dungId, bool claim);
  event ExitForcibly(uint64 dungId, address hero, uint heroId);
  event FreeDungeonRemoved(uint8 biome, uint64 dungeonId);
  event HeroCurrentDungeonChanged(address hero, uint heroId, uint64 dungeonId);
  //endregion ------------------ DungeonFactoryController

  //region ------------------ GameObjectController
  event EventRegistered(uint32 objectId, IGOC.EventRegInfo eventRegInfo);
  event StoryRegistered(uint32 objectId, uint16 storyId);
  event MonsterRegistered(uint32 objectId, IGOC.MonsterGenInfo monsterGenInfo);
  event ObjectRemoved(uint32 objectId);
  event ObjectResultEvent(
    uint64 dungeonId,
    uint32 objectId,
    IGOC.ObjectType objectType,
    address hero,
    uint heroId,
    uint8 stageId,
    uint iteration,
    bytes data,
    IGOC.ActionResult result,
    uint salt
  );
  //endregion ------------------ GameObjectController

  //region ------------------ StoryController
  event SetBurnItemsMeta(uint storyId, IStoryController.AnswerBurnRandomItemMeta meta);
  event SetNextObjRewriteMeta(uint storyId, IStoryController.NextObjRewriteMeta meta);
  event SetAnswersMeta(uint storyId, uint16[] answerPageIds, uint8[] answerHeroClasses, uint16[] answerIds);
  event SetAnswerNextPageMeta(uint storyId, IStoryController.AnswerNextPageMeta meta);
  event SetAnswerAttributeRequirements(uint storyId, IStoryController.AnswerAttributeRequirementsMeta meta);
  event SetAnswerItemRequirements(uint storyId, IStoryController.AnswerItemRequirementsMeta meta);
  event SetAnswerTokenRequirementsMeta(uint storyId, IStoryController.AnswerTokenRequirementsMeta meta);
  event SetAnswerAttributes(uint storyId, IStoryController.AnswerAttributesMeta meta);
  event SetAnswerHeroCustomDataRequirementMeta(uint storyId, IStoryController.AnswerCustomDataMeta meta);
  event SetAnswerGlobalCustomDataRequirementMeta(uint storyId, IStoryController.AnswerCustomDataMeta meta);
  event SetSuccessInfo(uint storyId, IStoryController.AnswerResultMeta meta);
  event SetFailInfo(uint storyId, IStoryController.AnswerResultMeta meta);
  event SetCustomDataResult(uint storyId, IStoryController.AnswerCustomDataResultMeta meta, IStoryController.CustomDataResult _type);
  event StoryCustomDataRequirements(uint storyId, bytes32 requiredCustomDataIndex, uint requiredCustomDataMinValue, uint requiredCustomDataMaxValue, bool requiredCustomDataIsHero);
  event StoryRequiredLevel(uint storyId, uint requiredLevel);
  event StoryFinalized(uint32 objectId, uint storyId);
  event StoryRemoved(uint32 objectId, uint storyId);

  event ItemBurned(
    address heroToken,
    uint heroTokenId,
    uint64 dungeonId,
    uint objectId,
    address nftToken,
    uint nftId,
    uint stageId,
    uint iteration
  );

  /// @notice Durability of the item was reduced to 0
  event ItemBroken(
    address heroToken,
    uint heroTokenId,
    uint64 dungeonId,
    uint objectId,
    address nftToken,
    uint nftId,
    uint stageId,
    uint iteration
  );


  event NotEquippedItemBurned(
    address heroToken,
    uint heroTokenId,
    uint64 dungeonId,
    uint storyId,
    address nftToken,
    uint nftId,
    uint stageId,
    uint iteration
  );

  event StoryChangeAttributes(
    uint32 objectId,
    address heroToken,
    uint heroTokenId,
    uint64 dungeonId,
    uint storyId,
    uint stageId,
    uint iteration,
    int32[] attributes
  );
  //endregion ------------------ StoryController

  //region ------------------------ HeroController
  event HeroRegistered(address hero, uint8 heroClass, address payToken, uint payAmount);
  event HeroCreatedNgp(address hero, uint heroId, string name, address owner, string refCode, uint8 tier, uint8 ngLevel);
  event BiomeChanged(address hero, uint heroId, uint8 biome);
  event LevelUp(address hero, uint heroId, address owner, IStatController.CoreAttributes change);
  event ReinforcementAsked(address hero, uint heroId, address helpHeroToken, uint helpHeroId);
  event GuildReinforcementAsked(address hero, uint heroId, address helpHeroToken, uint helpHeroId);
  event OtherItemGuildReinforcement(address item, uint itemId, address hero, uint heroId, address helpHeroToken, uint helpHeroId);
  event ReinforcementReleased(address hero, uint heroId, address helperToken, uint helperId);
  event GuildReinforcementReleased(address hero, uint heroId, address helperToken, uint helperId);
  event Killed(address hero, uint heroId, address killer, bytes32[] dropItems, uint dropTokenAmount);
  event Reborn(address hero, uint heroId, uint8 newNgLevel);
  event BossKilled(address account, address hero, uint heroId, uint8 biome, uint8 newNgLevel, bool reborn, uint rewardAmount);
  event TierSetup(uint8 tier, address hero, uint72 payAmount, uint8[] slots, address[][] items);
  //endregion ------------------------ HeroController

  //region ------------------------ FightLib
  event FightResultProcessed(
    address sender,
    IFightCalculator.FightInfoInternal result,
    IFightCalculator.FightCall callData,
    uint iteration
  );
  //endregion ------------------------ FightLib

  //region ------------------------ Oracle
  event Random(uint number, uint max);
  //endregion ------------------------ Oracle

  //region ------------------------ Controller
  event OfferGovernance(address newGov);
  event GovernanceAccepted(address gov);
  event StatControllerChanged(address value);
  event StoryControllerChanged(address value);
  event GameObjectControllerChanged(address value);
  event ReinforcementControllerChanged(address value);
  event OracleChanged(address value);
  event TreasuryChanged(address value);
  event ItemControllerChanged(address value);
  event HeroControllerChanged(address value);
  event GameTokenChanged(address value);
  event DungeonFactoryChanged(address value);
  event ProxyUpdated(address proxy, address logic);
  event Claimed(address token, uint amount);
  event TokenStatusChanged(address token, bool status);
  event UserControllerChanged(address value);
  event GuildControllerChanged(address value);
  event GameTokenPriceChanged(uint value);
  event RewardsPoolChanged(address value);
  event Process(address token, uint amount, address from, uint toBurn, uint toTreasury, uint toGov);
  //endregion ------------------------ Controller


  //region ------------------------ ReinforcementController
  event HeroStaked(address heroToken, uint heroId, uint biome, uint score);
  event HeroStakedV2(address heroToken, uint heroId, uint biome, uint rewardAmount);
  event HeroWithdraw(address heroToken, uint heroId);
  event HeroAsk(address heroToken, uint heroId);
  event HeroAskV2(address heroToken, uint heroId, uint hitsLast24h, uint fixedFee, uint helperRewardAmount);
  event TokenRewardRegistered(address heroToken, uint heroId, address token, uint amountAdded, uint totalAmount);
  event GuildTokenRewardRegistered(address heroToken, uint heroId, address token, uint amountAdded, uint guildId);
  event NftRewardRegistered(address heroToken, uint heroId, address token, uint id);
  event GuildNftRewardRegistered(address heroToken, uint heroId, address token, uint id, uint guildId);
  event ToHelperRatioChanged(uint value);
  event ClaimedToken(address heroToken, uint heroId, address token, uint amount, address recipient);
  event ClaimedItem(address heroToken, uint heroId, address item, uint itemId, address recipient);
  event MinLevelChanged(uint8 value);
  event MinLifeChancesChanged(uint value);
  //endregion ------------------------ ReinforcementController

  //region ------------------------ Treasury, reward pool
  event AssetsSentToDungeon(address dungeon, address token, uint amount);
  event RewardSentToUser(address receiver, address token, uint rewardAmount);
  event NotEnoughReward(address receiver, address token, uint rewardAmountToPay);
  event BaseAmountChanged(uint oldValue, uint newValue);
  //endregion ------------------------ Treasury, reward pool

  //region ------------------------ EventLib
  event EventResult(uint64 dungeonId, address heroToken, uint heroTokenId, uint8 stageId, IStatController.ActionInternalInfo gen, uint iteration);
  //endregion ------------------------ EventLib

  //region ------------------------ Item controller and helper contracts
  event ItemRegistered(address item, IItemController.RegisterItemParams info);
  event OtherItemRegistered(address item, IItemController.ItemMeta meta, bytes packedItemMetaData);
  event ItemRemoved(address item);
  event OtherItemRemoved(address item);
  event NewItemMinted(address item, uint itemId, IItemController.MintInfo info);
  event Equipped(address item, uint itemId, address heroToken, uint heroTokenId, uint8 itemSlot);
  event TakenOff(address item, uint itemId, address heroToken, uint heroTokenId, uint8 itemSlot, address destination);
  event ItemRepaired(address item, uint itemId, uint consumedItemId, uint16 baseDurability);
  event FailedToRepairItem(address item, uint itemId, uint consumedItemId, uint16 itemDurability);
  event Augmented(address item, uint itemId, uint consumedItemId, uint8 augLevel, IItemController.AugmentInfo info);
  event NotAugmented(address item, uint itemId, uint consumedItemId, uint8 augLevel);
  event ReduceDurability(address item, uint itemId, uint newDurability);
  event Used(address item, uint tokenId, address heroToken, uint heroTokenId);
  event Destroyed(address item, uint itemId);
  event FragilityReduced(address item, uint itemId, address consumedItem, uint consumedItemId, uint fragility);
  event ItemControllerHelper(address helper);
  event SetUnionConfig(uint configId, address[] items, uint[] count, address itemToMint);
  event RemoveUnionConfig(uint configId);
  event SetUnionKeyPass(address keyPassItem);
  event CombineItems(address msgSender, uint configId, address[] items, uint[][] itemIds, address mintedItem, uint mintedItemId);
  //endregion ------------------------ Item controller and helper contracts

  //region ------------------------ NFT and GameToken (only custom events, not ERC20/721 standards)
  event ChangePauseStatus(bool value);
  event MinterChanged(address value);

  event UniqueUriChanged(uint id, string uri);
  event BaseUriChanged(string uri);

  event HeroMinted(uint heroId);
  event HeroBurned(uint heroId);
  event HeroUriByStatusChanged(string uri, uint statusLvl);

  event ItemMinted(uint tokenId);
  event ItemBurned(uint tokenId);
  event UriByRarityChanged(string uri, uint rarity);
  event SponsoredHeroCreated(address msgSender, address heroAddress, uint heroId, string heroName);
  //endregion ------------------------ NFT and GameToken (only custom events, not ERC20/721 standards)

  //region ------------------------ User controller
  event SetUserName(address user, string name);
  event SetUserAvatar(address user, string avatar);
  event LootBoxOpened(address user, uint lootBoxKind, address[] itemTokens, uint[] itemTokenIds);
  event LootBoxConfigChanged(uint lootBoxKind, address[] mintItems, uint32[] mintItemsChances, uint maxDropItems);
  event SetFeeRenaming(uint feeRenaming);
  event ActivityCompleted(address user, bool daily, bool weekly);
  event FameHallHeroRegistered(address hero, uint heroId, address heroOwner, uint8 openedNgLevel);
  //endregion ------------------------ User controller

  //region ------------------------ Guild

  event GuildCreated(address owner, uint guildId, string name, string urlLogo);
  event AddToGuild(uint guildId, address newUser);
  event ChangeGuildRights(uint guildId, address user, uint rights);
  event RemoveFromGuild(uint guildId, address user);
  event GuildDeleted(uint guildId);
  event GuildLevelUp(uint guildId, uint8 newLevel);
  event GuildRename(uint guildId, string newName);
  event GuildLogoChanged(uint guildId, string newLogoUrl);
  event GuildDescriptionChanged(uint guildId, string newDescription);
  event SetGuildRelation(uint guildId1, uint guildId2, bool peace);
  event TransferFromGuildBank(address user, address token, uint amount, address recipient);
  event TransferNftFromGuildBank(address user, address[] nfts, uint[] tokenIds, address recipient);
  event GuildBankDeployed(uint guildId, address guildBank);

  event SetToHelperRatio(uint guildId, uint8 value, address user);
  event TopUpGuildBank(address msgSender, uint guildId, address guildBank, uint amount);

  event GuildRequestRegistered(address msgSender, uint guildId, string userMessage, uint depositAmount);
  event GuildRequestStatusChanged(address msgSender, uint guildRequestId, uint8 newStatus, address user);
  event SetToHelperRatio(uint guildId, address msgSender, uint8 toHelperRatio);
  event SetGuildRequestDepositAmount(uint guildId, address msgSender, uint amount);
  event SetGuildBaseFee(uint fee);
  event SetPvpPointsCapacity(address msgSender, uint64 capacityPvpPoints, address[] users);
  event SetShelterController(address shelterController);
  event SetShelterAuction(address shelterAuction);
  event PayForBidFromGuildBank(uint guildId, uint amount, uint bid);
  //endregion ------------------------ Guild

  //region ------------------------ Guild shelter
  event RegisterShelter(uint sheleterId, uint price);
  event SetShelterItems(
    uint shelterId,
    address[] items,
    uint64[] pricesInPvpPoints,
    uint128[] pricesInGameTokens,
    uint16[] maxItemsPerDayThresholds
  );
  event RemoveShelterItems(uint shelterId, address[] items);
  event BuyShelter(uint guidlId, uint shelterId);
  event LeaveShelter(uint guildId, uint shelterId);
  event NewShelterBid(uint shelterId, uint buyerGuildId, uint amount);
  event RevokeShelterBid(uint shelterId);
  event UseShelterBid(uint shelterId, uint sellerGuildId, uint buyerGuidId, uint amount);
  event PurchaseShelterItem(address msgSender, address item, uint numSoldItems, uint priceInPvpPoints, uint priceInGameToken);
  event ChangeShelterOwner(uint shelterId, uint fromGuildId, uint toGuildId);
  event RestInShelter(address msgSender, address heroToken, uint heroTokenId);
  //endregion ------------------------ Guild shelter

  //region ------------------------ Guild reinforcement
  event GuildHeroStaked(address heroToken, uint heroId, uint guildId);
  event GuildHeroWithdrawn(address heroToken, uint heroId, uint guildId);
  event GuildHeroAsked(address heroToken, uint heroId, uint guildId, address user);

  /// @param user Address can be 0 if heroId was already burnt at the moment of reinforcement releasing
  event GuildHeroReleased(address heroToken, uint heroId, uint guildId, address user);
  //endregion ------------------------ Guild reinforcement

  //region ------------------------ Guild auction
  event AuctionPositionOpened(uint positionId, uint shelterId, uint sellerGuildId, address msgSender, uint minAuctionPrice);
  event AuctionPositionClosed(uint positionId, address msgSender);
  event AuctionBidOpened(uint bidId, uint positionId, uint amount, address msgSender);
  //endregion ------------------------ Guild auction

  //region ------------------------ Guild bank
  event GuildBankTransfer(address token, address recipient, uint amount);
  event GuildBankTransferNft(address to, address nft, uint tokenId);
  event GuildBankTransferNftMulti(address to, address[] nfts, uint[] tokenIds);
  //endregion ------------------------ Guild bank

  //region ------------------------ Pawnshop
  event PawnShopRouterDeployed(address pawnShop, address gameToken, address routerOwner, address deployed);
  event PawnShopRouterTransfer(address token, uint amount, address receiver);
  event PawnShopRouterBulkSell(address[] nfts, uint[] nftIds, uint[] prices, address nftOwner, uint[] positionIds);
  event PawnShopRouterClosePositions(uint[] positionIds, address receiver);
  event PawnShopRouterBulkBuy(uint[] positionIds, address receiver);

  //endregion ------------------------ Pawnshop
}

File 4 of 48 : IControllable.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

interface IControllable {

  function VERSION() external pure returns (string memory);

  function revision() external view returns (uint);

  function previousImplementation() external view returns (address);

  function isController(address contract_) external view returns (bool);

  function isGovernance(address contract_) external view returns (bool);

  function created() external view returns (uint256);

  function createdBlock() external view returns (uint256);

  function controller() external view returns (address);

  function increaseRevision(address oldLogic) external;

}

File 5 of 48 : IController.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

interface IController {

  function governance() external view returns (address);

  function statController() external view returns (address);

  function storyController() external view returns (address);

  function gameObjectController() external view returns (address);

  function reinforcementController() external view returns (address);

  function oracle() external view returns (address);

  function treasury() external view returns (address);

  function itemController() external view returns (address);

  function heroController() external view returns (address);

  function dungeonFactory() external view returns (address);

  function gameToken() external view returns (address);

  function validTreasuryTokens(address token) external view returns (bool);

  function isDeployer(address adr) external view returns (bool);

  function onPause() external view returns (bool);

  function userController() external view returns (address);

  function guildController() external view returns (address);

  function rewardsPool() external view returns (address);

  function gameTokenPrice() external view returns (uint);

  function process(address token, uint amount, address from) external;
}

File 6 of 48 : IDungeonFactory.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "../openzeppelin/EnumerableSet.sol";
import "../openzeppelin/EnumerableMap.sol";

interface IDungeonFactory {

  /// @custom:storage-location erc7201:dungeon.factory.main
  struct MainState {
    /// @dev biome => dungeonLaunchedId
    mapping(uint => EnumerableSet.UintSet) freeDungeons;
    /// @dev hero + heroId + biome (packMapObject) -> completed
    mapping(bytes32 => bool) bossCompleted;
    /// @dev hero + heroId + dungNum (packDungeonKey) -> completed
    mapping(bytes32 => bool) specificDungeonCompleted;
    /// @notice Max biome completed by the hero
    /// @dev hero + heroId (nftPacked) -> max biome completed
    mapping(bytes32 => uint8) maxBiomeCompleted;
    /// @notice which dungeon the hero is currently in
    /// @dev hero+id => current DungeonId
    mapping(bytes32 => uint64) heroCurrentDungeon;

    // ---

    /// @notice Specific dungeon for the given pair of hero level + hero class
    ///         ALl specific dungeons are listed also in allSpecificDungeons
    /// @dev packUint8Array(specReqBiome, specReqHeroClass) => dungNum
    mapping(bytes32 => uint16) dungeonSpecific;
    /// @dev contains all specific dungNum for easy management
    EnumerableSet.UintSet allSpecificDungeons;
    /// @dev biome => dungNum
    mapping(uint8 => EnumerableSet.UintSet) dungeonsLogicByBiome;

    // ---

    /// @dev max available biome. auto-increment with new dung deploy
    uint8 maxBiome;

    /// @notice Address of treasure token => min hero level required
    /// @dev manual threshold for treasury
    mapping(address => uint) minLevelForTreasury;

    /// @notice Contains arrays for SKILL_1, SKILL_2, SKILL_3 with 0 or 1
    /// i.e. [0, 1, 0] means that durability of SKILL_2 should be reduced
    /// @dev hero + heroId => uint8[] array where idx = slotNum
    mapping(bytes32 => bytes32) skillSlotsForDurabilityReduction;

    /// @notice Counter of dungeons, it's incremented on launch of a new dungeon
    uint64 dungeonCounter;

    /// @dev dungNum = init attributes
    mapping(uint16 => DungeonAttributes) dungeonAttributes;
    /// @dev dungeonId => status
    mapping(uint64 => DungeonStatus) dungeonStatuses;

    /// @notice NG_LEVEL of the hero that has created the given dungeon
    mapping(uint64 dungeonId => uint ngLevel) dungeonNgLevel;
  }

  struct ObjectGenerateInfo {
    /// @notice List of chamber types for each unique object
    /// @dev uint8 types, packed using PackingLib.packUint8Array
    bytes32[] objTypesByStages;
    /// @notice List of chances for each chamber type
    /// @dev uint64 chances
    uint32[][] objChancesByStages;
  }

  struct DungeonGenerateInfo {
    /// @notice List of chamber types for each unique object
    uint8[][] objTypesByStages;
    /// @notice List of chances for each chamber type
    uint32[][] objChancesByStages;

    uint32[] uniqObjects;

    uint8 minLevel;
    uint8 maxLevel;

    bytes32[] requiredCustomDataIndex;
    uint64[] requiredCustomDataMinValue;
    uint64[] requiredCustomDataMaxValue;
    bool[] requiredCustomDataIsHero;
  }

  /// @notice Attributes of the given dungeon logic
  struct DungeonAttributes {
    /// @notice Total number of stages that should be passed to complete the dungeon
    uint8 stages;
    uint8 biome;

    /// @notice Default list of objects that should be passed in the dungeon
    uint32[] uniqObjects;

    /// @dev min+max (packUint8Array)
    bytes32 minMaxLevel;

    bytes32[] requiredCustomDataIndex;
    /// @notice Packed DungeonGenerateInfo.requiredCustomData: MinValue, MaxValue, IsHero
    /// @dev min+max+isHero(packStoryCustomDataRequirements)
    bytes32[] requiredCustomDataValue;

    ObjectGenerateInfo info;
  }

  /// @notice Current status of the given dungeon
  struct DungeonStatus {
    uint64 dungeonId;
    /// @notice Dungeon logic id
    uint16 dungNum;

    /// @notice True if the dungeon is completed by the hero
    bool isCompleted;

    /// @notice Hero in the dungeon or 0
    address heroToken;
    uint heroTokenId;
    /// @notice Current object that should be passed by the hero. 0 - new object is not opened
    uint32 currentObject;
    /// @notice Current stage in the dungeon that should be passed by the hero.
    uint8 currentStage;

    EnumerableMap.AddressToUintMap treasuryTokens;
    /// @notice All items that were minted on result of made actions
    bytes32[] treasuryItems;

    /// @notice Total number of stages that should be passed to complete the dungeon
    /// This value can be bigger than length of uniqObjects
    uint8 stages;
    /// @notice List of objects to be passed in the stage. The list can be dynamically changed during passing the stages
    uint32[] uniqObjects;
  }

  ////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////

  function launchForNewHero(address heroToken, uint heroTokenId, address owner) external returns (uint64 dungeonId);

  function maxBiomeCompleted(address heroToken, uint heroTokenId) external view returns (uint8);

  function currentDungeon(address heroToken, uint heroTokenId) external view returns (uint64);

  function skillSlotsForDurabilityReduction(address heroToken, uint heroTokenId) external view returns (uint8[] memory result);

  function setBossCompleted(uint32 objectId, address heroToken, uint heroTokenId, uint8 heroBiome) external;

  /// @notice Hero exists current dungeon forcibly same as when dying but without loosing life chance
  function exitForcibly(address heroToken, uint heroTokenId, address msgSender) external;

  function maxAvailableBiome() external view returns (uint8);

  function reborn(address hero, uint heroId) external;
}

File 7 of 48 : IERC165.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
  /**
   * @dev Returns true if this contract implements the interface defined by
   * `interfaceId`. See the corresponding
   * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
   * to learn more about how these ids are created.
   *
   * This function call must use less than 30 000 gas.
   */
  function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

File 8 of 48 : IERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
  /**
   * @dev Returns the amount of tokens in existence.
   */
  function totalSupply() external view returns (uint256);

  /**
   * @dev Returns the amount of tokens owned by `account`.
   */
  function balanceOf(address account) external view returns (uint256);

  /**
   * @dev Moves `amount` tokens from the caller's account to `recipient`.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * Emits a {Transfer} event.
   */
  function transfer(address recipient, uint256 amount) external returns (bool);

  /**
   * @dev Returns the remaining number of tokens that `spender` will be
   * allowed to spend on behalf of `owner` through {transferFrom}. This is
   * zero by default.
   *
   * This value changes when {approve} or {transferFrom} are called.
   */
  function allowance(address owner, address spender) external view returns (uint256);

  /**
   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * IMPORTANT: Beware that changing an allowance with this method brings the risk
   * that someone may use both the old and the new allowance by unfortunate
   * transaction ordering. One possible solution to mitigate this race
   * condition is to first reduce the spender's allowance to 0 and set the
   * desired value afterwards:
   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
   *
   * Emits an {Approval} event.
   */
  function approve(address spender, uint256 amount) external returns (bool);

  /**
   * @dev Moves `amount` tokens from `sender` to `recipient` using the
   * allowance mechanism. `amount` is then deducted from the caller's
   * allowance.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * Emits a {Transfer} event.
   */
  function transferFrom(
    address sender,
    address recipient,
    uint256 amount
  ) external returns (bool);

  /**
   * @dev Emitted when `value` tokens are moved from one account (`from`) to
   * another (`to`).
   *
   * Note that `value` may be zero.
   */
  event Transfer(address indexed from, address indexed to, uint256 value);

  /**
   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
   * a call to {approve}. `value` is the new allowance.
   */
  event Approval(address indexed owner, address indexed spender, uint256 value);
}

File 9 of 48 : IERC721.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
  /**
   * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
   */
  event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

  /**
   * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
   */
  event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

  /**
   * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
   */
  event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

  /**
   * @dev Returns the number of tokens in ``owner``'s account.
   */
  function balanceOf(address owner) external view returns (uint256 balance);

  /**
   * @dev Returns the owner of the `tokenId` token.
   *
   * Requirements:
   *
   * - `tokenId` must exist.
   */
  function ownerOf(uint256 tokenId) external view returns (address owner);

  /**
   * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
   * are aware of the ERC721 protocol to prevent tokens from being forever locked.
   *
   * Requirements:
   *
   * - `from` cannot be the zero address.
   * - `to` cannot be the zero address.
   * - `tokenId` token must exist and be owned by `from`.
   * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.
   * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
   *
   * Emits a {Transfer} event.
   */
  function safeTransferFrom(
    address from,
    address to,
    uint256 tokenId
  ) external;

  /**
   * @dev Transfers `tokenId` token from `from` to `to`.
   *
   * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
   *
   * Requirements:
   *
   * - `from` cannot be the zero address.
   * - `to` cannot be the zero address.
   * - `tokenId` token must be owned by `from`.
   * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
   *
   * Emits a {Transfer} event.
   */
  function transferFrom(
    address from,
    address to,
    uint256 tokenId
  ) external;

  /**
   * @dev Gives permission to `to` to transfer `tokenId` token to another account.
   * The approval is cleared when the token is transferred.
   *
   * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
   *
   * Requirements:
   *
   * - The caller must own the token or be an approved operator.
   * - `tokenId` must exist.
   *
   * Emits an {Approval} event.
   */
  function approve(address to, uint256 tokenId) external;

  /**
   * @dev Returns the account approved for `tokenId` token.
   *
   * Requirements:
   *
   * - `tokenId` must exist.
   */
  function getApproved(uint256 tokenId) external view returns (address operator);

  /**
   * @dev Approve or remove `operator` as an operator for the caller.
   * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
   *
   * Requirements:
   *
   * - The `operator` cannot be the caller.
   *
   * Emits an {ApprovalForAll} event.
   */
  function setApprovalForAll(address operator, bool _approved) external;

  /**
   * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
   *
   * See {setApprovalForAll}
   */
  function isApprovedForAll(address owner, address operator) external view returns (bool);

  /**
   * @dev Safely transfers `tokenId` token from `from` to `to`.
   *
   * Requirements:
   *
   * - `from` cannot be the zero address.
   * - `to` cannot be the zero address.
   * - `tokenId` token must exist and be owned by `from`.
   * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
   * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
   *
   * Emits a {Transfer} event.
   */
  function safeTransferFrom(
    address from,
    address to,
    uint256 tokenId,
    bytes calldata data
  ) external;
}

File 10 of 48 : IFightCalculator.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "./IStatController.sol";
import "./IItemController.sol";

interface IFightCalculator {

  enum AttackType {
    UNKNOWN, // 0
    MELEE, // 1
    MAGIC, // 2
    SLOT_3,
    SLOT_4,
    SLOT_5,
    SLOT_6,
    SLOT_7,
    SLOT_8,
    SLOT_9,
    SLOT_10
  }

  /// @notice Attacker info: suitable both for hero and monsters
  struct AttackInfo {
    /// @notice Type of the attack
    /// by default, if attack token presents, it's magic attack and not-magic otherwise
    /// but this logic can become more complicated after introducing new attack types
    AttackType attackType;
    /// @notice NFT selected by hero for attack, it should be equip on.
    /// If attacker is a monster, this is a special case (stub NFT with zero ID is used)
    address attackToken;
    uint attackTokenId;
    address[] skillTokens;
    uint[] skillTokenIds;
  }

  struct FighterInfo {
    int32[] fighterAttributes;
    IStatController.ChangeableStats fighterStats;
    AttackType attackType;
    address attackToken;
    uint attackTokenId;
    uint race;
  }

  struct Statuses {
    bool stun;
    bool burn;
    bool freeze;
    bool confuse;
    bool curse;
    bool poison;
    bool gotCriticalHit;
    bool missed;
    bool hitBlocked;
  }

  struct FightResult {
    int32 healthA;
    int32 healthB;
    int32 manaConsumedA;
    int32 manaConsumedB;
  }

  struct FightCall {
    FighterInfo fighterA;
    FighterInfo fighterB;
    uint64 dungeonId;
    uint32 objectId;
    address heroAdr;
    uint heroId;
    uint8 stageId;
    uint iteration;
    uint8 turn;
  }

  struct SkillSlots {
    bool slot1;
    bool slot2;
    bool slot3;
  }

  //region ------------------------ FightLib-internal (FightInfoInternal is required by IApplicationEvents..)
  struct FightInfoInternal {
    Fighter fighterA;
    Fighter fighterB;
  }

  struct Fighter {
    IFightCalculator.FighterInfo info;
    IItemController.AttackInfo magicAttack;
    int32 health;
    int32 manaConsumed;
    int32 damage;
    int32 damagePoison;
    int32 damageReflect;
    IFightCalculator.Statuses statuses;
  }
  //endregion ------------------------ FightLib-internal

  function fight(FightCall memory callData) external returns (FightResult memory);
}

File 11 of 48 : IGOC.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "../openzeppelin/EnumerableSet.sol";
import "./IController.sol";

interface IGOC {

  enum ObjectType {
    UNKNOWN, // 0
    EVENT, // 1
    MONSTER, // 2
    STORY, // 3
    END_SLOT
  }

  enum ObjectSubType {
    UNKNOWN_0, // 0
    ENEMY_NPC_1, // 1
    ENEMY_NPC_SUPER_RARE_2, // 2
    BOSS_3, // 3
    SHRINE_4, // 4
    CHEST_5, // 5
    STORY_6, // 6
    STORY_UNIQUE_7, // 7
    SHRINE_UNIQUE_8, // 8
    CHEST_UNIQUE_9, // 9
    ENEMY_NPC_UNIQUE_10, // 10
    STORY_ON_ROAD_11, // 11
    STORY_UNDERGROUND_12, // 12
    STORY_NIGHT_CAMP_13, // 13
    STORY_MOUNTAIN_14, // 14
    STORY_WATER_15, // 15
    STORY_CASTLE_16, // 16
    STORY_HELL_17, // 17
    STORY_SPACE_18, // 18
    STORY_WOOD_19, // 19
    STORY_CATACOMBS_20, // 20
    STORY_BAD_HOUSE_21, // 21
    STORY_GOOD_TOWN_22, // 22
    STORY_BAD_TOWN_23, // 23
    STORY_BANDIT_CAMP_24, // 24
    STORY_BEAST_LAIR_25, // 25
    STORY_PRISON_26, // 26
    STORY_SWAMP_27, // 27
    STORY_INSIDE_28, // 28
    STORY_OUTSIDE_29, // 29
    STORY_INSIDE_RARE_30,
    STORY_OUTSIDE_RARE_31,
    ENEMY_NPC_INSIDE_32,
    ENEMY_NPC_INSIDE_RARE_33,
    ENEMY_NPC_OUTSIDE_34,
    ENEMY_NPC_OUTSIDE_RARE_35,
    END_SLOT
  }

  /// @custom:storage-location erc7201:game.object.controller.main
  struct MainState {

    /// @dev objId = biome(00) type(00) id(0000) => biome(uint8) + objType(uint8)
    /// Id is id of the event, story or monster.
    mapping(uint32 => bytes32) objectMeta;

    /// @dev biome(uint8) + objType(uint8) => set of object id
    mapping(bytes32 => EnumerableSet.UintSet) objectIds;

    /// @dev heroAdr180 + heroId64 + cType8 + biome8 => set of already played objects. Should be cleared periodically
    mapping(bytes32 => EnumerableSet.UintSet) playedObjects;

    /// @dev HeroAdr(160) + heroId(uint64) + objId(uint32) => iteration count. It needs for properly emit events for every new entrance.
    mapping(bytes32 => uint) iterations;

    /// @dev objId(uint32) => EventInfo
    mapping(uint32 => EventInfo) eventInfos;

    /// @dev objId(uint32) => storyId
    mapping(uint32 => uint16) storyIds;

    /// @dev objId(uint32) => MonsterInfo
    mapping(uint32 => MonsterInfo) monsterInfos;

    /// @dev hero+id => last fight action timestamp
    mapping(bytes32 => uint) lastHeroFightTs;

    /// @dev delay for user actions in fight (suppose to prevent bot actions)
    uint fightDelay;
  }

  struct ActionResult {
    bool kill;
    bool completed;
    address heroToken;
    address[] mintItems;
    int32 heal;
    int32 manaRegen;
    int32 lifeChancesRecovered;
    int32 damage;
    int32 manaConsumed;
    uint32 objectId;
    uint32 experience;
    uint heroTokenId;
    uint iteration;
    uint32[] rewriteNextObject;
  }

  struct EventInfo {
    /// @dev chance to use good or bad attributes/stats
    uint32 goodChance;

    /// @dev toBytes32ArrayWithIds
    bytes32[] goodAttributes;
    bytes32[] badAttributes;

    /// @dev experience(uint32) + heal(int32) + manaRegen(int32) + lifeChancesRecovered(int32) + damage(int32) + manaConsume(int32) packStatsChange
    bytes32 statsChange;

    /// @dev item+chance packItemMintInfo
    bytes32[] mintItems;
  }

  struct MonsterInfo {
    /// @dev toBytes32ArrayWithIds
    bytes32[] attributes;
    /// @dev level(uint8) + race(uint8) + experience(uint32) + maxDropItems(uint8) packMonsterStats
    bytes32 stats;
    /// @dev attackToken(160) + attackTokenId(uint64) + attackType(uint8) packAttackInfo
    bytes32 attackInfo;

    /// @dev item+chance packItemMintInfo
    bytes32[] mintItems;

    /// @dev heroAdr(160) + heroId(uint64) => iteration => GeneratedMonster packed
    mapping(bytes32 => mapping(uint => bytes32)) _generatedMonsters;
  }

  struct MultiplierInfo {
    uint8 biome;
    /// @notice NG_LEVEL of the hero who is going to fight with the given monster
    /// Use type(uint8).max for !NG+
    uint8 heroNgLevel;
  }

  struct GeneratedMonster {
    bool generated;
    uint8 turnCounter;
    int32 hp;
    uint32 amplifier;
  }

  struct MonsterGenInfo {
    uint16 monsterId;
    uint8 biome;
    ObjectSubType subType;

    uint8[] attributeIds;
    int32[] attributeValues;

    uint8 level;
    uint8 race;
    uint32 experience;
    uint8 maxDropItems;

    address attackToken;
    uint64 attackTokenId;
    uint8 attackType;

    address[] mintItems;
    uint32[] mintItemsChances;
  }

  struct ActionContext {
    address sender;
    address heroToken;
    IController controller;
    uint8 biome;
    uint8 objectSubType;
    uint8 stageId;
    uint8 heroNgLevel;
    uint32 objectId;
    uint64 dungeonId;
    uint heroTokenId;
    uint salt;
    uint iteration;
    bytes data;
  }

  struct EventRegInfo {
    uint8 biome;
    uint16 eventId;
    ObjectSubType subType;

    uint32 goodChance;

    AttributeGenerateInfo goodAttributes;
    AttributeGenerateInfo badAttributes;

    uint32 experience;
    int32 heal;
    int32 manaRegen;
    int32 lifeChancesRecovered;
    int32 damage;
    int32 manaConsumed;

    address[] mintItems;
    uint32[] mintItemsChances;
  }

  struct AttributeGenerateInfo {
    uint8[] ids;
    int32[] values;
  }

  //////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////////////////////

  /// @dev represent object registration if non zero values
  function getObjectMeta(uint32 objectId) external view returns (uint8 biome, uint8 objectSubType);

  function isBattleObject(uint32 objectId) external view returns (bool);

  function getRandomObject(
    uint8[] memory cTypes,
    uint32[] memory chances,
    uint8 biomeLevel,
    address heroToken,
    uint heroTokenId
  ) external returns (uint32 objectId);

  function open(address heroToken, uint heroTokenId, uint32 objectId) external returns (uint iteration);

  function action(
    address sender,
    uint64 dungeonId,
    uint32 objectId,
    address heroToken,
    uint heroTokenId,
    uint8 stageId,
    bytes memory data
  ) external returns (ActionResult memory);

}

File 12 of 48 : IGameToken.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "./IERC20.sol";

interface IGameToken is IERC20 {

  function minter() external view returns (address);

  function mint(address account, uint amount) external returns (bool);

  function burn(uint amount) external returns (bool);

  function setMinter(address minter_) external;

  function pause(bool value) external;

}

File 13 of 48 : IGuildBank.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../interfaces/IAppErrors.sol";
import "../interfaces/IGuildController.sol";
import "../interfaces/IERC20.sol";
import "../interfaces/IERC721.sol";

interface IGuildBank {
  function transfer(address token, address recipient, uint amount) external;

  function approve(address token, address spender, uint256 amount) external returns (bool);

  function transferNft(address to, address nft, uint256 tokenId) external;

  function transferNftMulti(address to, address[] memory nfts, uint256[] memory tokenIds) external;

  function approveNft(address to, address nft, uint256 tokenId) external;

  function approveNftMulti(address to, address[] memory nfts, uint256[] memory tokenIds) external;
}

File 14 of 48 : IGuildController.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../openzeppelin/EnumerableSet.sol";

interface IGuildController {
  enum GuildRightBits {
    ADMIN_0,
    RENAME_1,
    CHANGE_LOGO_2,
    CHANGE_SHELTER_3,
    ADD_MEMBER_4,
    REMOVE_MEMBER_5,
    BANK_TOKENS_OPERATION_6,
    CHANGE_ROLES_7,
    LEVEL_UP_8,
    SET_RELATION_KIND_9,
    BANK_ITEMS_OPERATION_10,
    SET_GUILD_PARAMS_11,
    CHANGE_PURCHASING_SHELTER_ITEMS_CAPACITY_12
  }

  enum GuildsParams {
    NONE_0,
    COUNTER_GUILD_IDS_1,
    BASE_FEE_2,
    COUNTER_GUILD_REQUESTS_3,
    REENTRANT_STATUS_4,
    SHELTER_CONTROLLER_5,
    SHELTER_AUCTION_6
  }

  enum GuildRequestStatus {
    NONE_0,
    ACCEPTED_1,
    REJECTED_2,
    CANCELED_3
  }

  /// @custom:storage-location erc7201:guild.controller.main
  struct MainState {
    /// @notice Mapping to store various guilds params (with global values for all guilds)
    mapping(GuildsParams param => uint value) guildsParam;

    /// @notice guildId => address of instance of GuildBank contract
    mapping(uint guildId => address) guildBanks;

    /// @notice guild id => guild data (owner, name, logo, etc)
    mapping(uint guildId => GuildData) guildData;

    /// @notice name => guild id
    mapping(string guildName => uint guildId) nameToGuild;

    /// @notice EOA => guild id, EOA can be a member of a single guild only
    mapping(address member => uint guildId) memberToGuild;

    /// @notice List of participants of guilds
    /// @dev Allowed number of members is 20 + 5 * guildLevel
    mapping(uint guildId => EnumerableSet.AddressSet listEoa) members;

    /// @notice Rights of the member in the guild, mask of GuildRightBits
    mapping(address member => uint maskRights) rights;

    /// @notice _getGuildsPairKey(guild1, guild2) => status (false - war, true - peace)
    mapping(bytes32 guildsPairKey => bool) relationsPeaceful;

    // ---------------------------- Request to join to the guild
    /// @notice Full list of requests registered for the guild
    mapping(uint guildId => mapping(GuildRequestStatus status => EnumerableSet.UintSet guildRequestIds)) guildRequests;

    /// @notice List of active requests created by the given user.
    /// "Active" => deposit should be returned to the user.
    /// All not-active requests are removed from here automatically.
    mapping(address user => EnumerableSet.UintSet guildRequestIds) userActiveGuildRequests;

    /// @notice Data of all guild requests ever created
    mapping(uint guildRequestId => GuildRequestData) guildRequestData;

    /// @notice Deposit amount required to create a guild request
    mapping(uint guildId => GuildRequestDeposit) guildRequestDepositAmounts;

    /// @notice Counter of spent pvp points + number of guild pvp-points allowed to be used by the guild member
    mapping(uint guildId => mapping(address member => UserPvpPoints)) userPvpPoints;

    /// @notice guild id => guildDescription
    mapping(uint guildId => string) guildDescription;
  }

  struct GuildData {
    /// @notice Not empty unique guild name
    string guildName;

    /// @notice URL of guild logo (empty is allowed)
    string urlLogo;

    /// @notice Creator (owner) of the guild
    address owner;

    /// @notice Guild level [1...10]
    uint8 guildLevel;

    /// @notice Percent of guild reinforcement fee Value in range [_FEE_MIN ... _TO_HELPER_RATIO_MAX], i.e. [10..50]
    uint8 toHelperRatio;

    /// @notice Global guild points counter, it's incremented on each victory in php-fight.
    /// @dev Assume here, that uint64 is enough to store any sums of scores
    uint64 pvpCounter;
  }

  struct GuildRequestData {
    GuildRequestStatus status;
    /// @notice Creator of the guild request that asks to include him to the guild
    address user;
    /// @notice Message to the guild owner from the user
    string userMessage;
    uint guildId;
  }

  struct GuildRequestDeposit {
    bool initialized;
    uint192 amount;
  }

  struct UserPvpPoints {
    /// @notice How many guild pvp-points the user is allowed to use
    uint64 capacityPvpPoints;

    /// @notice How many guild pvp-points the user has used
    uint64 spentPvpPoints;
  }

  /// ----------------------------------------------------------------------------------------------

  function memberOf(address user) external view returns (uint guildId);
  function guildToShelter(uint guildId) external view returns (uint shelterId);

  function getGuildData(uint guildId) external view returns (
    string memory guildName,
    string memory urlLogo,
    address owner,
    uint8 guildLevel,
    uint64 pvpCounter,
    uint toHelperRatio
  );

  function getRights(address user) external view returns (uint);
  function getGuildBank(uint guildId) external view returns (address);
  function shelterController() external view returns (address);

  function usePvpPoints(uint guildId, address user, uint64 priceInPvpPoints) external;
  function payFromGuildBank(uint guildId, uint shelterPrice) external;
  function payFromBalance(uint amount, address user) external;

  /// @notice Ensure that the {user} has given {right}, revert otherwise
  function checkPermissions(address user, uint right) external view returns (uint guildId, uint rights);
  function shelterAuctionController() external view returns (address);
  function payForAuctionBid(uint guildId, uint amount, uint bid) external;
}

File 15 of 48 : IHeroController.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;
import "../openzeppelin/EnumerableSet.sol";
import "../openzeppelin/EnumerableMap.sol";

interface IHeroController {

  /// @custom:storage-location erc7201:hero.controller.main
  struct MainState {

    /// @dev A central place for all hero tokens
    /// @dev Deprecated. Controller is used instead.
    address heroTokensVault;

    /// @dev heroAdr => packed tokenAdr160+ amount96
    mapping(address => bytes32) payToken;

    /// @dev heroAdr => heroCls8
    mapping(address => uint8) heroClass;

    // ---

    /// @dev hero+id => individual hero name
    mapping(bytes32 => string) heroName;

    /// @dev name => hero+id, needs for checking uniq names
    mapping(string => bytes32) nameToHero;

    // ---

    /// @dev hero+id => biome
    mapping(bytes32 => uint8) heroBiome;

    /// @notice Exist reinforcement of any kind for the given hero
    /// @dev hero+id => packed reinforcement helper+id
    mapping(bytes32 => bytes32) reinforcementHero;

    /// @dev hero+id => reinforcement packed attributes
    mapping(bytes32 => bytes32[]) reinforcementHeroAttributes;

    /// @notice packedHero (hero + id) => count of calls of beforeTokenTransfer
    mapping(bytes32 => uint) countHeroTransfers;


    // ------------------------------------ NG plus

    /// @notice (tier, hero address) => TierInfo, where tier = [2, 3]
    /// @dev For tier=1 no data is required. Amount for tier 1 is stored in {payToken}, no items are minted
    /// Token from {payToken} is equal for all tiers
    mapping(bytes32 packedTierHero => TierInfo) tiers;

    mapping(bytes32 packedHero => HeroInfo) heroInfo;

    /// @notice Max NG_LVL reached by the heroes of a given account
    mapping(address user => uint8 maxNgLevel) maxUserNgLevel;

    /// @notice When the hero has killed boss on the given biome first time
    /// packedBiomeNgLevel = packed (biome, NG_LEVEL)
    mapping(bytes32 packedHero => mapping (bytes32 packedBiomeNgLevel => uint timestamp)) killedBosses;

    /// @notice Max NG_LEVEL reached by any user
    uint maxOpenedNgLevel;
  }

  /// @notice Tier = hero creation cost option
  /// There are 3 tiers:
  /// 1: most chip option, just pay fixed amount {payTokens} - new hero is created
  /// 2: pay bigger amount - random skill is equipped on the newly created hero
  /// 3: pay even more amount - random sill + some random items are equipped on the newly created hero
  struct TierInfo {
    /// @notice Cost of the hero creation using the given tier in terms of the token stored in {payToken}
    /// This amount is used for tiers 2, 3. For tier 1 the amount is taken from {payToken}
    uint amount;

    /// @notice All slots for which items-to-mint are registered in {itemsToMint}
    EnumerableSet.UintSet slots;

    /// @notice slot => items that can be minted and equipped on the hero to the given {slot} after hero creation
    mapping(uint8 slot => address[] items) itemsToMint;
  }

  /// @notice Current NG+-related values
  struct HeroInfo {
    /// @notice Hero tier = [0..3]. 0 - the hero is post-paid, it can be changed by upgrading the hero to pre-paid
    uint8 tier;

    /// @notice NG_LVL of the hero
    uint8 ngLevel;

    /// @notice True if hero has passed last biome on current NG+ and so NG_LEVEL can be incremented (reborn is allowed)
    bool rebornAllowed;

    /// @notice Amount paid for the hero on creation OR on upgrade to NG+
    /// Amount paid for creation of the hero in terms of game token (!NG+) is NOT stored here.
    /// @dev uint72 is used here to pack the whole struct to single slot
    uint72 paidAmount;

    /// @notice Pay token used to pay {paidAmount}
    address paidToken;
  }

  /// @notice Input data to create new hero
  struct HeroCreationData {
    /// @notice Desired NG_LVL of the hero
    uint8 ngLevel;

    /// @notice Desired tire of the newly created hero. Allowed values: [1..3]
    uint8 tier;

    /// @notice Enter to the dungeon after creation
    bool enter;

    /// @notice Desired hero name
    string heroName;

    /// @notice Optional: user account for which the hero is created
    address targetUserAccount;

    /// @notice Optional: ref-code to be passed to the hero-creation-related event
    string refCode;
  }


  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  function heroClass(address hero) external view returns (uint8);

  function heroBiome(address hero, uint heroId) external view returns (uint8);

  function payTokenInfo(address hero) external view returns (address token, uint amount);

  function heroReinforcementHelp(address hero, uint heroId) external view returns (address helperHeroToken, uint helperHeroId);

  function score(address hero, uint heroId) external view returns (uint);

  function isAllowedToTransfer(address hero, uint heroId) external view returns (bool);

  function beforeTokenTransfer(address hero, uint heroId) external returns (bool);

  // ---

  function create(address hero, string memory heroName_, bool enter) external returns (uint);

  function kill(address hero, uint heroId) external returns (bytes32[] memory dropItems);

  /// @notice Take off all items from the hero, reduce life to 1. The hero is NOT burnt.
  /// Optionally reduce mana to zero and/or decrease life chance.
  function softKill(address hero, uint heroId, bool decLifeChances, bool resetMana) external returns (bytes32[] memory dropItems);

  function releaseReinforcement(address hero, uint heroId) external returns (address helperToken, uint helperId);

  function resetLifeAndMana(address hero, uint heroId) external;

  function countHeroTransfers(address hero, uint heroId) external view returns (uint);

  function askGuildReinforcement(address hero, uint heroId, address helper, uint helperId) external;

  function getHeroInfo(address hero, uint heroId) external view returns (IHeroController.HeroInfo memory data);

  function registerKilledBoss(address hero, uint heroId, uint32 objectId) external;

  function maxOpenedNgLevel() external view returns (uint);
}

File 16 of 48 : IItem.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

interface IItem {

  function isItem() external pure returns (bool);

  function mintFor(address recipient) external returns (uint tokenId);

  function burn(uint tokenId) external;

  function controlledTransfer(address from, address to, uint tokenId) external;
}

File 17 of 48 : IItemController.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "./IStatController.sol";
import "./IGOC.sol";
import "../openzeppelin/EnumerableSet.sol";

interface IItemController {

  enum GlobalParam {
    UNKNOWN_0,

    /// @notice Address of ItemControllerHelper
    ITEM_CONTROLLER_HELPER_ADDRESS_1
  }

  /// @custom:storage-location erc7201:item.controller.main
  struct MainState {

    ////////////////// GENERATE //////////////////

    EnumerableSet.AddressSet items;

    /// @dev itemAdr => itemMetaType8 + itemLvl8 + itemType8 + baseDurability16 + defaultRarity8 + minAttr8 + maxAttr8 + manaCost32 + req(packed core 128)
    mapping(address => bytes32) itemMeta;

    /// @dev itemAdr => packed tokenAdr160+ amount96
    mapping(address => bytes32) augmentInfo;

    // --- common attr ---

    /// @dev itemAdr => id8 + min(int32) + max(int32) + chance32
    mapping(address => bytes32[]) generateInfoAttributes;

    // --- consumable ---

    /// @dev itemAdr => ids+values (toBytes32ArrayWithIds)
    mapping(address => bytes32[]) _itemConsumableAttributes;

    /// @dev itemAdr => IStatController.ChangeableStats packed int32[]
    mapping(address => bytes32) itemConsumableStats;

    // --- buff ---

    /// @dev itemAdr => id8 + min(int32) + max(int32) + chance32
    mapping(address => bytes32[]) generateInfoCasterAttributes;

    /// @dev itemAdr => id8 + minDmg(int32) + maxDmg(int32) + chance32
    mapping(address => bytes32[]) generateInfoTargetAttributes;

    // --- attack ---

    /// @dev itemAdr => packed AttackInfo: attackType8 + min32 + max32 + factors(packed core 128)
    mapping(address => bytes32) generateInfoAttack;

    ////////////////// ITEMS INFO //////////////////

    /// @dev itemAdr+id => itemRarity8 + augmentationLevel8 + itemDurability16
    mapping(bytes32 => bytes32) itemInfo;

    /// @dev itemAdr+id => heroAdr+id
    mapping(bytes32 => bytes32) equippedOn;

    // --- common attr ---

    /// @dev itemAdr+Id => ids+values (toBytes32ArrayWithIds)
    mapping(bytes32 => bytes32[]) _itemAttributes;

    // --- consumable ---

    // consumable stats unchangeable, get them by address

    // --- buff ---

    /// @dev itemAdr+Id => ids+values (toBytes32ArrayWithIds)
    mapping(bytes32 => bytes32[]) _itemCasterAttributes;

    /// @dev itemAdr+Id => ids+values (toBytes32ArrayWithIds)
    mapping(bytes32 => bytes32[]) _itemTargetAttributes;

    // --- attack ---

    /// @dev itemAdr+Id => packed AttackInfo: attackType8 + min32 + max32 + factors(packed core 128)
    mapping(bytes32 => bytes32) _itemAttackInfo;

    ////////////////// Additional generate info //////////////////

    /// @notice (itemAdr) => Bitmask of ConsumableActionBits
    mapping(address => uint) _consumableActionMask;


    /// --------------------------------- SIP-003: Item fragility
    /// @notice itemAdr + id => item fragility counter that displays the chance of an unsuccessful repair
    /// @dev [0...100_000], decimals 3
    mapping(bytes32 packedItem => uint fragility) itemFragility;

    /// @notice Universal mapping to store various addresses and numbers (params of the contract)
    mapping (GlobalParam param => uint value) globalParam;

    /// @notice Item address => packedMetadata
    /// {packedMetaData} is encoded using abi.encode/abi.decode
    /// Read first byte, detect meta data type by the byte value, apply proper decoder from PackingLib
    mapping(address item => bytes packedMetaData) packedItemMetaData;
  }

  struct RegisterItemParams {
    ItemMeta itemMeta;
    address augmentToken;
    uint augmentAmount;
    ItemGenerateInfo commonAttributes;

    IGOC.AttributeGenerateInfo consumableAttributes;
    IStatController.ChangeableStats consumableStats;

    ItemGenerateInfo casterAttributes;
    ItemGenerateInfo targetAttributes;

    AttackInfo genAttackInfo;
    /// @notice Bit mask of ConsumableActionBits
    uint consumableActionMask;
  }

  /// @notice Possible actions that can be triggered by using the consumable item
  enum ConsumableActionBits {
    CLEAR_TEMPORARY_ATTRIBUTES_0
    // other items are used instead this mask
  }

  struct ItemGenerateInfo {
    /// @notice Attribute ids
    uint8[] ids;
    /// @notice Min value of the attribute, != 0
    int32[] mins;
    /// @notice Max value of the attribute, != 0
    int32[] maxs;
    /// @notice Chance of the selection [0..MAX_CHANCES]
    uint32[] chances;
  }

  struct ItemMeta {
    uint8 itemMetaType;
    // Level in range 1-99. Reducing durability in low level dungeons. lvl/5+1 = biome
    uint8 itemLevel;
    IItemController.ItemType itemType;
    uint16 baseDurability;
    uint8 defaultRarity;
    uint32 manaCost;

    // it doesn't include positions with 100% chance
    uint8 minRandomAttributes;
    uint8 maxRandomAttributes;

    IStatController.CoreAttributes requirements;
  }

  // Deprecated. Todo - remove
  enum FeeType {
    UNKNOWN,
    REPAIR,
    AUGMENT,
    STORY,

    END_SLOT
  }

  enum ItemRarity {
    UNKNOWN, // 0
    NORMAL, // 1
    MAGIC, // 2
    RARE, // 3
    SET, // 4
    UNIQUE, // 5

    END_SLOT
  }

  enum ItemType {
    NO_SLOT, // 0
    HEAD, // 1
    BODY, // 2
    GLOVES, // 3
    BELT, // 4
    AMULET, // 5
    RING, // 6
    OFF_HAND, // 7
    BOOTS, // 8
    ONE_HAND, // 9
    TWO_HAND, // 10
    SKILL, // 11
    OTHER, // 12

    END_SLOT
  }

  enum ItemMetaType {
    UNKNOWN, // 0
    COMMON, // 1
    ATTACK, // 2
    BUFF, // 3
    CONSUMABLE, // 4

    END_SLOT
  }

  enum AttackType {
    UNKNOWN, // 0
    FIRE, // 1
    COLD, // 2
    LIGHTNING, // 3
    CHAOS, // 4

    END_SLOT
  }

  struct AttackInfo {
    AttackType aType;
    int32 min;
    int32 max;
    // if not zero - activate attribute factor for the attribute
    IStatController.CoreAttributes attributeFactors;
  }

  struct ItemInfo {
    ItemRarity rarity;
    uint8 augmentationLevel;
    uint16 durability;
  }

  /// @dev The struct is used in events, so it's moved here from the lib
  struct MintInfo {
    IItemController.ItemMeta meta;
    uint8[] attributesIds;
    int32[] attributesValues;
    IItemController.ItemRarity itemRarity;

    IItemController.AttackInfo attackInfo;

    uint8[] casterIds;
    int32[] casterValues;
    uint8[] targetIds;
    int32[] targetValues;
  }

  /// @dev The struct is used in events, so it's moved here from the lib
  struct AugmentInfo {
    uint8[] attributesIds;
    int32[] attributesValues;
    IItemController.AttackInfo attackInfo;
    uint8[] casterIds;
    int32[] casterValues;
    uint8[] targetIds;
    int32[] targetValues;
  }

  ///region ------------------------ Item type "Other"
  /// @notice Possible kinds of "Other" items
  /// Each "Other" item has each own structure for metadata, see OtherItemXXX
  enum OtherSubtypeKind {
    UNKNOWN_0,
    /// @notice Item to reduce fragility, see SCB-1014. Metadata is {OtherItemReduceFragility}
    REDUCE_FRAGILITY_1,

    /// @notice This item allows asking guild reinforcement to the guild member
    USE_GUILD_REINFORCEMENT_2,

    /// @notice Exit from dungeon (shelter of level 3 is required)
    EXIT_FROM_DUNGEON_3,

    /// @notice Rest in the shelter: restore of hp & mp, clear temporally attributes, clear used consumables (shelter of level 3 is required)
    REST_IN_SHELTER_4,

    /// @notice Stub item (i.e. OTHER_4) that has no logic in contracts, but it has correct (not empty) packedMetaData
    EMPTY_NO_LOGIC_5,

    END_SLOT
  }
  struct OtherItemReduceFragility {
    /// @notice "Other" item kind. It MUST BE first field in the struct.
    uint8 kind;

    /// @notice Value on which the fragility will be reduced.
    /// @dev [0...100%], decimals 3, so the value is in the range [0...10_000]
    uint248 value;
  }
  ///endregion ------------------------ Item type "Other"

  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  function itemMeta(address item) external view returns (ItemMeta memory meta);

  function augmentInfo(address item) external view returns (address token, uint amount);

  function genAttributeInfo(address item) external view returns (ItemGenerateInfo memory info);

  function genCasterAttributeInfo(address item) external view returns (ItemGenerateInfo memory info);

  function genTargetAttributeInfo(address item) external view returns (ItemGenerateInfo memory info);

  function genAttackInfo(address item) external view returns (AttackInfo memory info);

  function itemInfo(address item, uint itemId) external view returns (ItemInfo memory info);

  function equippedOn(address item, uint itemId) external view returns (address hero, uint heroId);

  function itemAttributes(address item, uint itemId) external view returns (int32[] memory values, uint8[] memory ids);

  function consumableAttributes(address item) external view returns (int32[] memory values, uint8[] memory ids);

  function consumableStats(address item) external view returns (IStatController.ChangeableStats memory stats);

  function casterAttributes(address item, uint itemId) external view returns (int32[] memory values, uint8[] memory ids);

  function targetAttributes(address item, uint itemId) external view returns (int32[] memory values, uint8[] memory ids);

  function itemAttackInfo(address item, uint itemId) external view returns (AttackInfo memory info);

  function score(address item, uint tokenId) external view returns (uint);

  function isAllowedToTransfer(address item, uint tokenId) external view returns (bool);

  // ---

  function mint(address item, address recipient) external returns (uint itemId);

  function reduceDurability(address hero, uint heroId, uint8 biome, bool reduceDurabilityAllSkills) external;

  function destroy(address item, uint tokenId) external;

  function takeOffDirectly(
    address item,
    uint itemId,
    address hero,
    uint heroId,
    uint8 itemSlot,
    address destination,
    bool broken
  ) external;

  /// @notice SIP-003: item fragility counter that displays the chance of an unsuccessful repair.
  /// @dev [0...100%], decimals 3, so the value is in the range [0...10_000]
  function itemFragility(address item, uint itemId) external view returns (uint);

  /// @notice SIP-003: The quest mechanic that previously burned the item will increase its fragility by 1%
  function incBrokenItemFragility(address item, uint itemId) external;

  function equip(
    address hero,
    uint heroId,
    address[] calldata items,
    uint[] calldata itemIds,
    uint8[] calldata itemSlots
  ) external;
}

File 18 of 48 : IItemControllerHelper.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "../openzeppelin/EnumerableSet.sol";

/// @notice Helper contract owned by ItemController only, it reduces data and code size in ItemController
interface IItemControllerHelper {

  enum GlobalParam {
    UNKNOWN_0,

    /// @notice Address of the nft. User should own such nft to be able to combine items. 0 is allowed (no keyPass)
    UNION_KEY_PASS_ITEM_1,

    /// @notice Generator of ID for union combinations
    UNION_ID_COUNTER_2
  }

  /// @custom:storage-location erc7201:item.controller.helper.main
  struct MainState {
    /// @notice Universal mapping to store various addresses and numbers (params of the contract)
    mapping (GlobalParam param => uint value) globalParam;

    //region ------------------------ SCB-1028: combine multiple items to new more powerful item

    /// @notice Union ID generated using {UNION_ID_COUNTER_2} => configuration
    mapping (uint configId => UnionConfig) unionConfig;

    /// @notice List of configId of all registered union configs
    EnumerableSet.UintSet listUnionConfigs;

    //endregion ------------------------ SCB-1028: combine multiple items to new more powerful item
  }

  struct UnionConfig {
    /// @notice Each union has unique ID
    /// @dev Assume that 96 bits are enough to store any id => configId + itemToMint = 1 slot
    uint96 configId;

    /// @notice Result item that will be minted in exchange of destroying of the combined {items} of the given {count}
    address itemToMint;

    /// @notice List of items that can be combined. We need to store the list to be able to display it on UI only
    address[] items;

    /// @notice Count of items that can be combined. We need to store the list to be able to display it on UI only
    uint[] count;
  }


  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  function prepareToCombine(address user, uint configId, address[] memory items, uint[][] memory tokens) external view returns (address);
}

File 19 of 48 : IOracle.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

interface IOracle {

  function getRandomNumber(uint max, uint seed) external returns (uint);

  function getRandomNumberInRange(uint min, uint max, uint seed) external returns (uint);

}

File 20 of 48 : IReinforcementController.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "./IStatController.sol";
import "../openzeppelin/EnumerableMap.sol";

/// @notice Terms
/// Reinforcement v1: helper is selected randomly in askHero, fixed part of rewards (tokens and NFT) is sent to the helper.
/// Guild reinforcement: helper is selected from guild heroes. Rewards are sent to guild bank.
/// Reinforcement v2: helper is selected manually in askHeroV2, helper receives fixed amount.
interface IReinforcementController {

  enum ConfigParams {
    /// @notice Packed MinMaxBoardV2
    V2_MIN_MAX_BOARD_0
  }

  /// @custom:storage-location erc7201:reinforcement.controller.main
  struct MainState {

    // ------------------------ Reinforcement v1

    /// @dev minLvl8 + minLifeChances8
    bytes32 config;
    /// @dev hero token + hero id => heroInfo(biome8 + score128 + fee8 + stakeTs64)
    mapping(bytes32 => bytes32) _stakedHeroes;
    /// @dev biome => helperAdr+id
    mapping(uint => EnumerableSet.Bytes32Set) _internalIdsByBiomes;
    /// @dev biome => score  // The field is deprecated and not updated any more
    mapping(uint => uint) maxScore;
    /// @dev heroAdr+id => itemAdr+id
    mapping(bytes32 => bytes32[]) _heroNftRewards;
    /// @dev heroAdr+id => tokenAdr and amount map
    mapping(bytes32 => EnumerableMap.AddressToUintMap) _heroTokenRewards;


    // ------------------------ Guild reinforcement

    /// @notice All staked guild heroes for the given guild
    /// @dev helper (hero token + hero id) => guild
    mapping(bytes32 packedHero => uint guildId) stakedGuildHeroes;

    /// @notice All guild heroes that are currently in use by guild reinforcement
    /// It's allowed to withdraw a hero before reinforcement releasing,
    /// so it's possible to have !0 in {guildBusyHelpers} and 0 in {stakedGuildHeroes} simultaneously.
    /// @dev helper (hero token + hero id) => guildId (guild at the moment of askGuildReinforcement)
    mapping(bytes32 packedHero => uint guildId) busyGuildHelpers;

    /// @notice All (free and busy) staked guild heroes per guild.
    /// guild => (packed helper => guild where the helper is busy currently)
    /// @dev There is a chance that guilds are different here
    /// i.e. hero can be:
    /// 1) added to G1 2) staked in G1 3) asked for help 4) withdrawn 5) G1=>G2 6) staked in G2
    /// In such case guildHelpers[G2][hero] = G1, guildHelpers[G1][hero] = 0
    /// After releasing guildHelpers[G2][hero] = 0
    mapping(uint guildId => EnumerableMap.Bytes32ToUintMap) guildHelpers;

    /// @notice Moment of withdrawing the hero from staking. Next staking is possible in 1 day since withdrawing
    mapping(bytes32 packedHero => uint lastWithdrawTimestamp) lastGuildHeroWithdrawTs;


    // ------------------------ Reinforcement v2
    /// @notice Map to store various config params
    mapping(ConfigParams paramId => uint) configParams;

    mapping(bytes32 packedHero => HeroInfoV2) stakedHeroesV2;

    /// @notice biome => set of packedHero. All staked heroes (they can be busy of free currently)
    mapping(uint biome => EnumerableSet.Bytes32Set) heroesByBiomeV2;

    mapping(uint biome => LastWindowsV2) stat24hV2;
  }


  /// @notice Deprecated. Reinforcement v1
  struct HeroInfo {
    uint8 biome;
    uint score; // stored in 128 but easy to use 256
    /// @notice To helper ratio
    uint8 fee;
    uint64 stakeTs;
  }

  struct HeroInfoV2 {
    uint8 biome;
    uint64 stakeTs;
    /// @notice Amount of game token that is paid to the helper at the moment of the call {askHeroV2}
    uint128 rewardAmount;
  }

  /// @notice Statistic of askHeroV2 calls per last 24 hours at the moment of the last call
  struct LastWindowsV2 {
    /// @notice 24 hours are divided on 8 intervals, each interval is 3 hour
    /// Current basket has index {basketIndex}
    /// {baskets[current basket]} contains "old" value.
    /// New value for the current basket is collected in {basketValue}.
    /// The value for the current basket is calculated as weighted average of old and new values.
    /// New value replaces the old value at the moment of changing current basket index.
    uint24[8] baskets;
    /// @notice New value (hits counter) for current basket
    uint24 basketValue;
    /// @notice Abs. index of the current basket (abs. hour / 3)
    uint48 basketIndex;
  }

  /// @dev 1 slot
  struct ConfigReinforcementV2 {
    /// @notice if Number-of-askHeroV2-calls is below given value then burn fee has min value
    uint32 minNumberHits;
    /// @notice if Number-of-askHeroV2-calls is above given value then burn fee has max value
    uint32 maxNumberHits;
    /// @notice Lowest fee = amountForDungeon / given value, i.e. 100 => amountForDungeon/100 as lower fee
    uint32 lowDivider;
    /// @notice Highest fee = amountForDungeon / given value, i.e. 2 => amountForDungeon/2 as highest fee
    uint32 highDivider;
    /// @notice Limit for min level of the staked hero
    /// In practice we need following limitation: (stats.level < 5 || (stats.level - 5) / 5 < biome)
    /// so, levelLimit should be equal 5
    /// In tests we need to be able to disable such limitation, so levelLimit = 0 allow to disable that constraint
    uint8 levelLimit;
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  function toHelperRatio(address heroToken, uint heroId) external view returns (uint);

  function isStaked(address heroToken, uint heroId) external view returns (bool);

  function registerTokenReward(address heroToken, uint heroId, address token, uint amount) external;

  function registerNftReward(address heroToken, uint heroId, address token, uint tokenId) external;

  function askHeroV2(address hero, uint heroId, address helper, uint helperId) external returns (int32[] memory attributes);

  function askGuildHero(address hero, uint heroId, address helper, uint helperId) external returns (int32[] memory attributes);

  /// @notice Return the guild in which the hero is currently asked for guild reinforcement
  function busyGuildHelperOf(address heroToken, uint heroId) external view returns (uint guildId);

  function releaseGuildHero(address helperHeroToken, uint helperHeroTokenId) external;
}

File 21 of 48 : IRewardsPool.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

interface IRewardsPool {

  /// @custom:storage-location erc7201:rewards.pool.main
  struct MainState {
    mapping(address token => uint baseAmountValue) baseAmounts;
  }

  function balanceOfToken(address token) external view returns (uint);

  function rewardAmount(address token, uint maxBiome, uint maxNgLevel, uint biome, uint heroNgLevel) external view returns (uint);

  function sendReward(address token, uint rewardAmount_, address receiver) external;

  function lostProfitPercent(uint maxBiome, uint maxNgLevel, uint heroNgLevel) external view returns (uint percent);
}

File 22 of 48 : IShelterAuction.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;
import "../openzeppelin/EnumerableSet.sol";
import "../openzeppelin/EnumerableMap.sol";

interface IShelterAuction {
  enum ShelterAuctionParams {
    NONE_0,
    POSITION_COUNTER_1,
    BID_COUNTER_2,
    FEE_3
  }

  //region ------------------------ Data types
  /// @custom:storage-location erc7201:shelter.auction.main
  struct MainState {

    /// @notice Mapping to store auction params (i.e. counters)
    mapping(ShelterAuctionParams param => uint value) params;

    /// @notice Hold all positions. Any record should not be removed
    mapping(uint positionId => Position) positions;

    /// @dev BidId => Bid. Hold all bids. Any record should not be removed
    mapping(uint bidId => AuctionBid) auctionBids;

    /// @notice List of currently opened positions
    EnumerableSet.UintSet openPositions;

    /// @notice Seller to position map
    /// At any moment each guild can have only one opened position to sell
    mapping(uint sellerGuildId => uint openedPositionId) sellerPosition;

    /// @notice Position that the buyer is going to purchase.
    /// At any moment each guild can have only one opened position to purchase
    mapping(uint buyerGuildId => BuyerPositionData) buyerPosition;

    /// @notice All open and close bids for the given position
    mapping(uint positionId => uint[] bidIds) positionToBidIds;

    /// @notice Timestamp of the last bid for the auction
    mapping(uint positionId => uint timestamp) lastAuctionBidTs;
}

  struct Position {
    bool open;
    /// @notice User that opens the position. The user belongs to the guild with id = {sellerGuildId}
    address seller;

    /// @notice Assume that shelter can be stored as uint64
    uint64 shelterId;

    uint128 positionId;

    /// @notice Min allowed (initial) auction price. Only first bid is able to use it.
    uint128 minAuctionPrice;

    uint128 sellerGuildId;
  }

  struct AuctionBid {
    /// @notice Only last bid is opened, all previous bids are closed automatically
    bool open;
    /// @notice User that opens the bid. The user belongs to the guild with id = {buyerGuildId}
    address buyer;

    uint128 bidId;
    uint128 positionId;
    /// @notice Bid amount in terms of game token. This amount is transferred from guild Bank to ShelterAuction balance
    uint128 amount;
    uint128 buyerGuildId;
  }

  struct BuyerPositionData {
    /// @notice ID of the position that the buyer is going to purchase
    uint128 positionId;

    /// @notice 0-based index of the opened bid in {positionToBidIds}
    uint128 bidIndex;
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  function positionBySeller(uint sellerGuildId_) external view returns (uint positionId);
  function positionByBuyer(uint buyerGuildId) external view returns (uint positionId, uint bidIndex);
  function posByShelter(uint shelterId_) external view returns (uint positionId);
}

File 23 of 48 : IShelterController.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../openzeppelin/EnumerableSet.sol";

interface IShelterController {
  /// @custom:storage-location erc7201:shelter.controller.main
  struct MainState {
    /// @notice List of items allowed to be purchased in the shelter
    mapping(uint shelterId => EnumerableSet.AddressSet) shelterItems;

    /// @notice Data of items available for purchasing in the given shelter
    mapping(uint shelterId => mapping(address item => ShelterItemData)) shelterItemData;

    // @notice Statistics how much items were purchased per day
    mapping(uint shelterId => mapping(uint32 epochDay => mapping(address item => uint))) countPurchasedItems;

    /// @notice List of registered shelters in {biome}
    mapping(uint biome => EnumerableSet.UintSet shelterUids) shelters;

    /// @notice Initial price of the shelters in game tokens
    mapping(uint shelterId => uint) shelterPrices;

    /// @notice Shelters belong to a specific guild (not the player)
    /// Shelters can be free (don't belong to any guild)
    mapping(uint shelterId => uint guildId) shelterToGuild;

    /// @notice Each guild can own 0 or 1 shelter
    mapping(uint guildId => uint shelterId) guildToShelter;
  }

  struct ShelterItemData {
    /// @notice Price of the item in pvp-points
    uint64 priceInPvpPoints;
    /// @notice Price of the item game token
    uint128 priceInGameToken;
    /// @notice Max number of items that can be purchases per day in the shelter. 0 - no limitations
    uint16 maxItemsPerDayLimit;
  }

  /// ----------------------------------------------------------------------------------------------

  function clearShelter(uint guildId) external;
  function guildToShelter(uint guildId) external view returns (uint shelterId);
  function changeShelterOwner(uint shelterId, uint newOwnerGuildId) external;
  function shelterToGuild(uint shelterId) external view returns (uint guildId);
}

File 24 of 48 : IStatController.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;
import "../openzeppelin/EnumerableSet.sol";
import "../openzeppelin/EnumerableMap.sol";

interface IStatController {

  /// @custom:storage-location erc7201:stat.controller.main
  struct MainState {
    mapping(bytes32 => bytes32[]) heroTotalAttributes;
    /// @dev heroAdr+heroId => int32 packed strength, dexterity, vitality, energy
    mapping(bytes32 => bytes32) _heroCore;
    mapping(bytes32 => bytes32[]) heroBonusAttributes;
    mapping(bytes32 => bytes32[]) heroTemporallyAttributes;
    /// @dev heroAdr+heroId => uint32 packed level, experience, life, mana, lifeChances
    mapping(bytes32 => bytes32) heroStats;
    /// @dev heroAdr+heroId+itemSlot => itemAdr + itemId
    mapping(bytes32 => bytes32) heroSlots;
    /// @dev heroAdr+heroId => busy slots uint8[] packed
    mapping(bytes32 => bytes32) heroBusySlots;
    mapping(bytes32 => EnumerableSet.AddressSet) usedConsumables;
    /// @dev heroCustomDataV2 is used instead
    mapping(bytes32 => mapping(bytes32 => uint)) _deprecated_heroCustomData;
    mapping(bytes32 => uint) globalCustomData;

    /// @notice packNftIdWithValue(hero, heroId, ngLevel) => hero custom data map
    /// @dev initially it was packedHero => hero custom data map
    mapping(bytes32 => EnumerableMap.Bytes32ToUintMap) heroCustomDataV2;
  }


  enum ATTRIBUTES {
    // core
    STRENGTH, // 0
    DEXTERITY, // 1
    VITALITY, // 2
    ENERGY, // 3
    // attributes
    DAMAGE_MIN, // 4
    DAMAGE_MAX, // 5
    ATTACK_RATING, // 6
    DEFENSE, // 7
    BLOCK_RATING, // 8
    LIFE, // 9
    MANA, // 10
    // resistance
    FIRE_RESISTANCE, // 11
    COLD_RESISTANCE, // 12
    LIGHTNING_RESISTANCE, // 13
    // dmg against
    DMG_AGAINST_HUMAN, // 14
    DMG_AGAINST_UNDEAD, // 15
    DMG_AGAINST_DAEMON, // 16
    DMG_AGAINST_BEAST, // 17

    // defence against
    DEF_AGAINST_HUMAN, // 18
    DEF_AGAINST_UNDEAD, // 19
    DEF_AGAINST_DAEMON, // 20
    DEF_AGAINST_BEAST, // 21

    // --- unique, not augmentable
    // hero will not die until have positive chances
    LIFE_CHANCES, // 22
    // increase chance to get an item
    MAGIC_FIND, // 23
    // decrease chance to get an item
    DESTROY_ITEMS, // 24
    // percent of chance x2 dmg
    CRITICAL_HIT, // 25
    // dmg factors
    MELEE_DMG_FACTOR, // 26
    FIRE_DMG_FACTOR, // 27
    COLD_DMG_FACTOR, // 28
    LIGHTNING_DMG_FACTOR, // 29
    // increase attack rating on given percent
    AR_FACTOR, // 30
    // percent of damage will be converted to HP
    LIFE_STOLEN_PER_HIT, // 31
    // amount of mana restored after each battle
    MANA_AFTER_KILL, // 32
    // reduce all damage on percent after all other reductions
    DAMAGE_REDUCTION, // 33

    // -- statuses
    // chance to stun an enemy, stunned enemy skip next hit
    STUN, // 34
    // chance burn an enemy, burned enemy will loss 50% of defence
    BURN, // 35
    // chance freeze an enemy, frozen enemy will loss 50% of MELEE damage
    FREEZE, // 36
    // chance to reduce enemy's attack rating on 50%
    CONFUSE, // 37
    // chance curse an enemy, cursed enemy will loss 50% of resistance
    CURSE, // 38
    // percent of dmg return to attacker
    REFLECT_DAMAGE_MELEE, // 39
    REFLECT_DAMAGE_MAGIC, // 40
    // chance to poison enemy, poisoned enemy will loss 10% of the current health
    POISON, // 41
    // reduce chance get any of uniq statuses
    RESIST_TO_STATUSES, // 42

    END_SLOT // 43
  }

  // possible
  // HEAL_FACTOR

  struct CoreAttributes {
    int32 strength;
    int32 dexterity;
    int32 vitality;
    int32 energy;
  }

  struct ChangeableStats {
    uint32 level;
    uint32 experience;
    uint32 life;
    uint32 mana;
    uint32 lifeChances;
  }

  enum ItemSlots {
    UNKNOWN, // 0
    HEAD, // 1
    BODY, // 2
    GLOVES, // 3
    BELT, // 4
    AMULET, // 5
    BOOTS, // 6
    RIGHT_RING, // 7
    LEFT_RING, // 8
    RIGHT_HAND, // 9
    LEFT_HAND, // 10
    TWO_HAND, // 11
    SKILL_1, // 12
    SKILL_2, // 13
    SKILL_3, // 14
    END_SLOT // 15
  }

  struct NftItem {
    address token;
    uint tokenId;
  }

  enum Race {
    UNKNOWN, // 0
    HUMAN, // 1
    UNDEAD, // 2
    DAEMON, // 3
    BEAST, // 4
    END_SLOT // 5
  }

  struct ChangeAttributesInfo {
    address heroToken;
    uint heroTokenId;
    int32[] changeAttributes;
    bool add;
    bool temporally;
  }

  struct BuffInfo {
    address heroToken;
    uint heroTokenId;
    uint32 heroLevel;
    address[] buffTokens;
    uint[] buffTokenIds;
  }

  /// @dev This struct is used inside event, so it's moved here from lib
  struct ActionInternalInfo {
    int32[] posAttributes;
    int32[] negAttributes;

    uint32 experience;
    int32 heal;
    int32 manaRegen;
    int32 lifeChancesRecovered;
    int32 damage;
    int32 manaConsumed;

    address[] mintedItems;
  }

  function initNewHero(address token, uint tokenId, uint heroClass) external;

  function heroAttributes(address token, uint tokenId) external view returns (int32[] memory);

  function heroAttribute(address token, uint tokenId, uint index) external view returns (int32);

  function heroAttributesLength(address token, uint tokenId) external view returns (uint);

  function heroBaseAttributes(address token, uint tokenId) external view returns (CoreAttributes memory);

  function heroCustomData(address token, uint tokenId, bytes32 index) external view returns (uint);

  function globalCustomData(bytes32 index) external view returns (uint);

  function heroStats(address token, uint tokenId) external view returns (ChangeableStats memory);

  function heroItemSlot(address token, uint64 tokenId, uint8 itemSlot) external view returns (bytes32 nftPacked);

  function heroItemSlots(address heroToken, uint heroTokenId) external view returns (uint8[] memory);

  function isHeroAlive(address heroToken, uint heroTokenId) external view returns (bool);

  function levelUp(address token, uint tokenId, uint heroClass, CoreAttributes memory change) external returns (uint newLvl);

  function changeHeroItemSlot(
    address heroToken,
    uint64 heroTokenId,
    uint itemType,
    uint8 itemSlot,
    address itemToken,
    uint itemTokenId,
    bool equip
  ) external;

  function changeCurrentStats(
    address token,
    uint tokenId,
    ChangeableStats memory change,
    bool increase
  ) external;

  function changeBonusAttributes(ChangeAttributesInfo memory info) external;

  function registerConsumableUsage(address heroToken, uint heroTokenId, address item) external;

  function clearUsedConsumables(address heroToken, uint heroTokenId) external;

  function clearTemporallyAttributes(address heroToken, uint heroTokenId) external;

  function buffHero(BuffInfo memory info) external view returns (int32[] memory attributes, int32 manaConsumed);

  function setHeroCustomData(address token, uint tokenId, bytes32 index, uint value) external;

  function setGlobalCustomData(bytes32 index, uint value) external;

  /// @notice Restore life and mana during reinforcement
  /// @dev Life and mana will be increased on ((current life/mana attr value) - (prev life/mana attr value))
  /// @param prevAttributes Hero attributes before reinforcement
  function restoreLifeAndMana(address heroToken, uint heroTokenId, int32[] memory prevAttributes) external;

  function reborn(address heroToken, uint heroTokenId, uint heroClass) external;
}

File 25 of 48 : IStoryController.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "../interfaces/IGOC.sol";
import "../interfaces/IStatController.sol";
import "../interfaces/IItemController.sol";
import "./IController.sol";
import "./IOracle.sol";
import "./IHeroController.sol";
import "../openzeppelin/EnumerableSet.sol";

interface IStoryController {

  enum AnswerResultId {
    UNKNOWN, // 0
    SUCCESS, // 1
    ATTRIBUTE_FAIL, // 2
    RANDOM_FAIL, // 3
    DELAY_FAIL, // 4
    HERO_CUSTOM_DATA_FAIL, // 5
    GLOBAL_CUSTOM_DATA_FAIL, // 6

    END_SLOT
  }

  enum CustomDataResult {
    UNKNOWN, // 0
    HERO_SUCCESS, // 1
    HERO_FAIL, // 2
    GLOBAL_SUCCESS, // 3
    GLOBAL_FAIL, // 4

    END_SLOT
  }

  /// @custom:storage-location erc7201:story.controller.main
  struct MainState {

    // --- STORY REG INFO ---

    /// @dev Uniq story identification.
    mapping(uint32 => uint16) storyIds;
    /// @dev Revers mapping for stories for using in the next object rewrite logic.
    mapping(uint16 => uint32) idToStory;
    /// @dev Store used ids for stories.
    mapping(uint16 => bool) _usedStoryIds;
    /// @dev Prevent register the story twice
    mapping(uint32 => bool) registeredStories;

    // --- ANSWER MAPPING ---

    /// @dev storyId => all story pages. We need to have this mapping for properly remove meta info
    mapping(uint16 => EnumerableSet.UintSet) allStoryPages;

    /// @dev storyId => all possible answers. We need to have this mapping for properly remove meta info
    mapping(uint16 => EnumerableSet.Bytes32Set) allStoryAnswers;

    /// @dev storyId + pageId + heroClass (zero is default answers) => storyId + pageId + heroClass (zero is default answers) + answerId
    mapping(bytes32 => bytes32[]) answers;
    /// @dev answerUnPackedId + answerResultId => nextPageIds (will be chosen randomly from this array)
    ///      where answerResultId is:
    ///      0 - unknown,
    ///      1 - success,
    ///      2 - attr fail
    ///      3 - random fail
    ///      4 - delay fail
    ///      5 - hero custom data fail
    ///      6 - global custom data fail
    ///      see COUNT_ANSWER_RESULT_IDS
    mapping(bytes32 => uint16[]) nextPageIds;
    /// @dev story + pageId + heroClass (zero is default answers) => random nextObjs (adr + id, like packed nft id)
    mapping(bytes32 => uint32[]) nextObjectsRewrite;

    /// @dev answerPackedId => packed array of uint32[]
    ///      0 - random requirement(uint32, 1 - 99% success of this action, zero means no check)
    ///      1 - delay requirement(uint32, if time since the last call more than this value the check is fail, zero means no check)
    ///      2 - isFinalAnswer(uint8)
    mapping(bytes32 => bytes32) answerAttributes;

    // --- ANSWER REQUIREMENTS ---

    /// @dev answerPackedId => array of AttributeRequirementsPacked
    mapping(bytes32 => bytes32[]) attributeRequirements;
    /// @dev answerPackedId=> array of ItemRequirementsPacked
    mapping(bytes32 => bytes32[]) itemRequirements;
    /// @dev answerPackedId => array of TokenRequirementsPacked
    mapping(bytes32 => bytes32[]) tokenRequirements;
    /// @dev answerPackedId => custom data for hero
    mapping(bytes32 => CustomDataRequirementPacked[]) heroCustomDataRequirement;
    /// @dev answerPackedId => global custom data
    mapping(bytes32 => CustomDataRequirementPacked[]) globalCustomDataRequirement;

    // --- ANSWER RESULTS ---

    /// @dev answerPackedId => change attributes
    mapping(bytes32 => bytes32[]) successInfoAttributes;
    /// @dev answerPackedId => change stats
    mapping(bytes32 => bytes32) successInfoStats;
    /// @dev answerPackedId => mint items
    mapping(bytes32 => bytes32[]) successInfoMintItems;

    /// @dev answerPackedId => change attributes
    mapping(bytes32 => bytes32[]) failInfoAttributes;
    /// @dev answerPackedId => change stats
    mapping(bytes32 => bytes32) failInfoStats;
    /// @dev answerPackedId => mint items
    mapping(bytes32 => bytes32[]) failInfoMintItems;

    /// @dev answerUnPackedId + CustomDataResult => custom data array change
    ///      where CustomDataResult is
    ///      1 - hero success
    ///      2 - hero fail
    ///      3 - global success
    ///      4 - global fail
    ///      see COUNT_CUSTOM_DATA_RESULT_IDS
    mapping(bytes32 => bytes32[]) customDataResult;

    /// @notice answerPackedId => slot+chance+stopIfBurnt
    /// @dev Since SIP-003 the items are not burn but broke
    mapping(bytes32 => bytes32[]) burnItem;

    // --- GENERAL STORY REQUIREMENTS ---

    /// @dev story => Custom hero data requirements for a story. If exist and hero is not eligible should be not chose in a dungeon.
    mapping(uint => CustomDataRequirementRangePacked[]) storyRequiredHeroData;
    /// @dev story => Minimal level for the history. 0 means no requirements.
    mapping(uint => uint) storyRequiredLevel;

    // --- HERO STATES ---

    /// @dev hero + heroId + storyId => pageId + heroLastActionTS
    mapping(bytes32 => bytes32) heroState;

    // --- OTHER ---

    /// @dev storyId => build hash for the last update
    mapping(uint16 => uint) storyBuildHash;

    /// @notice Number of already minted items by the user within the given iteration of the story.
    /// Only minting of the given number of items is allowed per iteration (see MAX_MINTED_ITEMS_PER_ITERATION).
    /// @dev hero, heroId, story => mintedInIteration
    /// This map is not cleared: storyId:objectId is 1:1, each object has own sequence of iterations without duplicates
    mapping(bytes32 => mapping(uint iteration => uint countMintedItems)) mintedInIteration;
  }

  /// @dev We need to have flat structure coz Solidity can not handle arrays of structs properly
  struct StoryMetaInfo {
    uint16 storyId;

    // --- story reqs

    bytes32[] requiredCustomDataIndex;
    uint64[] requiredCustomDataMinValue;
    uint64[] requiredCustomDataMaxValue;
    bool[] requiredCustomDataIsHero;
    uint minLevel;

    // --- answer reqs

    AnswersMeta answersMeta;
    AnswerNextPageMeta answerNextPage;
    AnswerAttributeRequirementsMeta answerAttributeRequirements;
    AnswerItemRequirementsMeta answerItemRequirements;
    AnswerTokenRequirementsMeta answerTokenRequirements;
    AnswerAttributesMeta answerAttributes;
    AnswerCustomDataMeta answerHeroCustomDataRequirement;
    AnswerCustomDataMeta answerGlobalCustomDataRequirement;

    // --- answer results

    AnswerBurnRandomItemMeta answerBurnRandomItemMeta;
    NextObjRewriteMeta nextObjRewriteMeta;

    // --- story results

    AnswerResultMeta successInfo;
    AnswerResultMeta failInfo;

    AnswerCustomDataResultMeta successHeroCustomData;
    AnswerCustomDataResultMeta failHeroCustomData;
    AnswerCustomDataResultMeta successGlobalCustomData;
    AnswerCustomDataResultMeta failGlobalCustomData;
  }

  struct NextObjRewriteMeta {
    uint16[] nextObjPageIds;
    uint8[] nextObjHeroClasses;
    uint32[][] nextObjIds;
  }

  struct AnswersMeta {
    uint16[] answerPageIds;
    uint8[] answerHeroClasses;
    uint16[] answerIds;
  }

  struct AnswerNextPageMeta {
    uint16[] pageId;
    uint8[] heroClass;
    uint16[] answerId;
    uint8[] answerResultIds;
    uint16[][] answerNextPageIds;
  }

  struct AnswerAttributeRequirementsMeta {
    uint16[] pageId;
    uint8[] heroClass;
    uint16[] answerId;
    bool[][] cores;
    uint8[][] ids;
    int32[][] values;
  }

  struct AnswerItemRequirementsMeta {
    uint16[] pageId;
    uint8[] heroClass;
    uint16[] answerId;
    address[][] requireItems;
    bool[][] requireItemBurn;
    bool[][] requireItemEquipped;
  }

  struct AnswerTokenRequirementsMeta {
    uint16[] pageId;
    uint8[] heroClass;
    uint16[] answerId;
    address[][] requireToken;
    uint88[][] requireAmount;
    bool[][] requireTransfer;
  }

  struct AnswerAttributesMeta {
    uint16[] pageId;
    uint8[] heroClass;
    uint16[] answerId;
    uint32[] randomRequirements;
    uint32[] delayRequirements;
    bool[] isFinalAnswer;
  }

  struct AnswerCustomDataMeta {
    uint16[] pageId;
    uint8[] heroClass;
    uint16[] answerId;

    bytes32[][] dataIndexes;
    bool[][] mandatory;
    uint64[][] dataValuesMin;
    uint64[][] dataValuesMax;
  }

  struct AnswerResultMeta {
    uint16[] pageId;
    uint8[] heroClass;
    uint16[] answerId;

    uint8[][] attributeIds;
    /// @dev Max value is limitied by int24, see toBytes32ArrayWithIds impl
    int32[][] attributeValues;

    uint32[] experience;
    int32[] heal;
    int32[] manaRegen;
    int32[] lifeChancesRecovered;
    int32[] damage;
    int32[] manaConsumed;

    address[][] mintItems;
    uint32[][] mintItemsChances;
  }

  struct AnswerCustomDataResultMeta {
    uint16[] pageId;
    uint8[] heroClass;
    uint16[] answerId;

    bytes32[][] dataIndexes;
    int16[][] dataValues;
  }

  struct AnswerBurnRandomItemMeta {
    uint16[] pageId;
    uint8[] heroClass;
    uint16[] answerId;

    /// @notice 0 - random slot
    uint8[][] slots;
    /// @notice typical chances are [0..100] (no decimals here)
    uint64[][] chances;
    /// @notice Since SIP-003 the burning is replaced by breaking bu the name is kept as is
    bool[][] isStopIfBurnt;
  }

  struct CustomDataRequirementPacked {
    bytes32 index;
    /// @dev min(uint64) + max(uint64) + mandatory(uint8)
    bytes32 data;
  }

  struct CustomDataRequirementRangePacked {
    bytes32 index;
    /// @dev min(uint64) + max(uint64) + isHeroData(uint8)
    bytes32 data;
  }

  struct StatsChange {
    uint32 experience;
    int32 heal;
    int32 manaRegen;
    int32 lifeChancesRecovered;
    int32 damage;
    int32 manaConsumed;
  }

  struct StoryActionContext {
    uint stageId;
    uint iteration;
    bytes32 answerIdHash;
    bytes32 answerAttributes;
    address sender;
    address heroToken;
    IController controller;
    IStatController statController;
    IHeroController heroController;
    IOracle oracle;
    IItemController itemController;
    uint8 heroClass;
    uint8 heroClassFromAnswerHash;
    uint8 biome;
    uint16 storyId;
    uint16 storyIdFromAnswerHash;
    uint16 pageIdFromAnswerHash;
    uint16 answerNumber;
    uint16 pageId;
    uint32 objectId;
    uint64 dungeonId;
    uint40 heroLastActionTS;
    uint80 heroTokenId;
    IStatController.ChangeableStats heroStats;
  }

  // --- WRITE ---

  function storyAction(
    address sender,
    uint64 dungeonId,
    uint32 objectId,
    uint stageId,
    address heroToken,
    uint heroTokenId,
    uint8 biome,
    uint iteration,
    bytes memory data
  ) external returns (IGOC.ActionResult memory);

  // --- READ ---

  function isStoryAvailableForHero(uint32 objectId, address heroToken, uint heroTokenId) external view returns (bool);

  function idToStory(uint16 id) external view returns (uint32 objectId);

  function heroPage(address hero, uint80 heroId, uint16 storyId) external view returns (uint16 pageId);

  function storyIds(uint32 objectId) external view returns (uint16);

  function registeredStories(uint32 objectId) external view returns (bool);

}

File 26 of 48 : ITreasury.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "./IItemController.sol";

interface ITreasury {

  function balanceOfToken(address token) external view returns (uint);

  function sendToDungeon(address dungeon, address token, uint amount) external;
}

File 27 of 48 : IUserController.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

interface IUserController {

  //region ------------------------ Data types

  enum LootBoxKind {
    /// @notice small loot box - reward for the daily activity
    DAILY_0,
    /// @notice large loot box - reward for the weekly activity (daily activity is passed each ot of the 7 days)
    WEEKLY_1,

    END_SLOT
  }

  /// @dev registerPassedDungeon assumes that the whole struct takes single slot only, not more
  struct UserActivity {
    /// @notice A day for which the daily activity is calculated (see counterXXX below)
    /// The number of days since 1970-01-01
    uint32 epochDay;

    /// @notice A week for which total count of daily activities were calculated
    /// The number of weeks since (1970-01-01 Thursday) - 3 days = (1969-12-29 Monday)
    uint32 epochWeek;

    /// @notice Count of dungeons passed during the day
    uint32 counterPassedDungeons;
    /// @notice Count of PvP during the day
    uint32 counterPvp;

    /// @notice Count of daily activities completed per the week
    uint16 dailyActivities;

    /// @notice Daily activity is completed and small loot box is added to the earned loot boxes
    bool dailyLootBoxReceived;
    /// @notice Weekly activity is completed and large loot box is added to the earned loot boxes
    bool weeklyLootBoxReceived;
  }

  struct EarnedLootBoxes {
    /// @notice Count of loot boxes earned by daily activity
    uint32 dailyCounter;
    /// @notice Count of loot boxes earned by weekly activity
    uint32 weeklyCounter;
  }

  struct LootBoxConfig {
    address[] mintItems;
    uint32[] mintItemsChances;
    uint maxDropItems;
  }

  /// @custom:storage-location erc7201:user.controller.main
  struct MainState {
    /// @notice Amount of sacra required to rename user account
    uint feeRenaming;

    /// @dev user EOA => account name
    mapping(address => string) userAccountName;

    /// @dev name => user EOA, needs for checking uniq names
    mapping(string => address) nameToUserAccount;

    /// @notice user => daily activity info
    mapping(address => UserActivity) userActivity;

    /// @notice user => earned loot boxes
    mapping(address => EarnedLootBoxes) counterLootBoxes;

    /// @notice Configs of loot boxes of various kinds
    mapping(LootBoxKind => LootBoxConfig) lootBoxConfig;

    /// @dev Deprecated, controller is used instead.
    address userTokensVault;

    /// @dev user EOA => account avatar
    mapping(address => string) userAvatar;

    // @notice Hall of Fame: ngLevel [1...99] => who opened the NG_LEVEL first
    mapping(uint8 ngLevel => FameHallData) fameHall;
  }

  struct FameHallData {
    // ------------ slot 1
    /// @notice The hero who opened given the NG_LEVEL first
    address hero;
    uint64 heroId;
    // ------------ slot 2
    /// @notice The owner of the hero
    address heroOwner;
    /// @notice Timestamp of the moment of the opening given NG_LEVEL
    uint64 tsOpen;
  }

  //endregion ------------------------ Data types

  /// @notice Register daily activity - a dungeon was passed
  /// @param user Owner of the hero who has passed the dungeon
  function registerPassedDungeon(address user) external;

  /// @notice Register daily activity - PvP was made
  /// @param user Owner of the hero who has taken participation in the PvP
  function registerPvP(address user, bool isWinner) external;

  function registerFameHallHero(address hero, uint heroId, uint8 openedNgLevel) external;
}

File 28 of 48 : AppLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../interfaces/IERC20.sol";

/// @notice Common internal utils
library AppLib {

  /// @notice Make infinite approve of {token} to {spender} if the approved amount is less than {amount}
  /// @dev Should NOT be used for third-party pools
  function approveIfNeeded(address token, uint amount, address spender) internal {
    if (IERC20(token).allowance(address(this), spender) < amount) {
      IERC20(token).approve(spender, type(uint).max);
    }
  }

  /// @dev Remove from array the item with given id and move the last item on it place
  ///      Use with mapping for keeping indexes in correct ordering
  function removeIndexed(
    uint256[] storage array,
    mapping(uint256 => uint256) storage indexes,
    uint256 id
  ) internal {
    uint256 lastId = array[array.length - 1];
    uint256 index = indexes[id];
    indexes[lastId] = index;
    indexes[id] = type(uint256).max;
    array[index] = lastId;
    array.pop();
  }

  /// @notice Return a-b OR zero if a < b
  function sub0(uint32 a, uint32 b) internal pure returns (uint32) {
    return a > b ? a - b : 0;
  }
}

File 29 of 48 : CalcLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../interfaces/IAppErrors.sol";
import "../solady/LibPRNG.sol";

library CalcLib {

  uint32 public constant MAX_CHANCE = 1e9;

  function minI32(int32 a, int32 b) internal pure returns (int32) {
    return a < b ? a : b;
  }

  function max32(int32 a, int32 b) internal pure returns (int32) {
    return a >= b ? a : b;
  }

  function absDiff(int32 a, int32 b) internal pure returns (uint32) {
    if (!((a >= 0 && b >= 0) || (a <= 0 && b <= 0))) revert IAppErrors.AbsDiff(a, b);
    if (a < 0) {
      a = - a;
    }
    if (b < 0) {
      b = - b;
    }
    return uint32(uint(int(a >= b ? a - b : b - a)));
  }

  function toUint(int32 n) internal pure returns (uint) {
    if (n <= 0) {
      return 0;
    }
    return uint(int(n));
  }

  function toInt32(uint a) internal pure returns (int32){
    if (a >= uint(int(type(int32).max))) {
      return type(int32).max;
    }
    return int32(int(a));
  }

  /// @dev Simplified pseudo-random for minor functionality
  function pseudoRandom(uint maxValue) internal view returns (uint) {
    if (maxValue == 0) {
      return 0;
    }

    uint salt = genSalt();
    // pseudo random number
    return (uint(keccak256(abi.encodePacked(blockhash(block.number), block.coinbase, block.difficulty, block.number, block.timestamp, tx.gasprice, gasleft(), salt))) % (maxValue + 1));
  }

  function genSalt() internal view returns (uint salt) {
    // skale has a RNG Endpoint
    if (
      block.chainid == uint(1351057110)
      || block.chainid == uint(37084624)
    ) {
      assembly {
        let freemem := mload(0x40)
        let start_addr := add(freemem, 0)
        if iszero(staticcall(gas(), 0x18, 0, 0, start_addr, 32)) {
          invalid()
        }
        salt := mload(freemem)
      }
    }
  }

  function pseudoRandomUint32(uint32 maxValue) internal view returns (uint32) {
    return uint32(pseudoRandom(uint(maxValue)));
  }

  /// @notice Generate pseudo-random uint in the range [0..maxValue) using Solady pseudo-random function
  function nextPrng(LibPRNG.PRNG memory prng, uint maxValue) internal pure returns (uint) {
    return LibPRNG.next(prng) % maxValue;
  }

  /// @notice pseudoRandomUint32 with customizable pseudoRandom()
  function pseudoRandomUint32Flex(
    uint32 maxValue,
    function (uint) internal view returns (uint) random_
  ) internal view returns (uint32) {
    return uint32(random_(uint(maxValue)));
  }

  function pseudoRandomInt32(int32 maxValue) internal view returns (int32) {
    bool neg;
    if (maxValue < 0) {
      neg = true;
      maxValue = - maxValue;
    }
    uint32 v = uint32(pseudoRandom(uint(int(maxValue))));
    return neg
      ? - int32(int(uint(v)))
      : int32(int(uint(v)));
  }

  /// @dev Simplified pseudo-random for minor functionality
  function pseudoRandomWithSeed(uint maxValue, uint seed) internal view returns (uint) {
    if (maxValue == 0) {
      return 0;
    }
    uint salt = genSalt();
    // pseudo random number
    return (uint(keccak256(abi.encodePacked(blockhash(block.number), block.coinbase, block.difficulty, block.number, block.timestamp, tx.gasprice, gasleft(), seed, salt))) % (maxValue + 1));
  }

  /// @dev Simplified pseudo-random for minor functionality, in range
  function pseudoRandomInRange(uint min, uint max) internal view returns (uint) {
    if (min >= max) {
      return max;
    }
    uint r = pseudoRandom(max - min);
    return min + r;
  }

  /// @dev Simplified pseudo-random for minor functionality, in range
  ///      Equal to pseudoRandomInRange(min, max, pseudoRandom)
  function pseudoRandomInRangeFlex(
    uint min,
    uint max,
    function (uint) internal view returns (uint) random_
  ) internal view returns (uint) {
    return min >= max ? max : min + random_(max - min);
  }

  function minusWithZeroFloor(uint a, uint b) internal pure returns (uint){
    if (a <= b) {
      return 0;
    }
    return a - b;
  }

  function minusWithMinFloorI32(int32 a, int32 b) internal pure returns (int32){
    if (int(a) - int(b) < type(int32).min) {
      return type(int32).min;
    }
    return a - b;
  }

  function plusWithMaxFloor32(int32 a, int32 b) internal pure returns (int32){
    if (int(a) + int(b) >= type(int32).max) {
      return type(int32).max;
    }
    return a + b;
  }

  function sqrt(uint x) internal pure returns (uint z) {
    assembly {
    // Start off with z at 1.
      z := 1

    // Used below to help find a nearby power of 2.
      let y := x

    // Find the lowest power of 2 that is at least sqrt(x).
      if iszero(lt(y, 0x100000000000000000000000000000000)) {
        y := shr(128, y) // Like dividing by 2 ** 128.
        z := shl(64, z) // Like multiplying by 2 ** 64.
      }
      if iszero(lt(y, 0x10000000000000000)) {
        y := shr(64, y) // Like dividing by 2 ** 64.
        z := shl(32, z) // Like multiplying by 2 ** 32.
      }
      if iszero(lt(y, 0x100000000)) {
        y := shr(32, y) // Like dividing by 2 ** 32.
        z := shl(16, z) // Like multiplying by 2 ** 16.
      }
      if iszero(lt(y, 0x10000)) {
        y := shr(16, y) // Like dividing by 2 ** 16.
        z := shl(8, z) // Like multiplying by 2 ** 8.
      }
      if iszero(lt(y, 0x100)) {
        y := shr(8, y) // Like dividing by 2 ** 8.
        z := shl(4, z) // Like multiplying by 2 ** 4.
      }
      if iszero(lt(y, 0x10)) {
        y := shr(4, y) // Like dividing by 2 ** 4.
        z := shl(2, z) // Like multiplying by 2 ** 2.
      }
      if iszero(lt(y, 0x8)) {
      // Equivalent to 2 ** z.
        z := shl(1, z)
      }

    // Shifting right by 1 is like dividing by 2.
      z := shr(1, add(z, div(x, z)))
      z := shr(1, add(z, div(x, z)))
      z := shr(1, add(z, div(x, z)))
      z := shr(1, add(z, div(x, z)))
      z := shr(1, add(z, div(x, z)))
      z := shr(1, add(z, div(x, z)))
      z := shr(1, add(z, div(x, z)))

    // Compute a rounded down version of z.
      let zRoundDown := div(x, z)

    // If zRoundDown is smaller, use it.
      if lt(zRoundDown, z) {
        z := zRoundDown
      }
    }
  }

  /*********************************************
 *              PRB-MATH                      *
 *   https://github.com/hifi-finance/prb-math *
 **********************************************/
  /// @notice Calculates the binary logarithm of x.
  ///
  /// @dev Based on the iterative approximation algorithm.
  /// https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation
  ///
  /// Requirements:
  /// - x must be greater than or equal to SCALE, otherwise the result would be negative.
  ///
  /// Caveats:
  /// - The results are nor perfectly accurate to the last decimal,
  ///   due to the lossy precision of the iterative approximation.
  ///
  /// @param x The unsigned 60.18-decimal fixed-point number for which
  ///           to calculate the binary logarithm.
  /// @return result The binary logarithm as an unsigned 60.18-decimal fixed-point number.
  function log2(uint256 x) internal pure returns (uint256 result) {
    if (x < 1e18) revert IAppErrors.TooLowX(x);

    // Calculate the integer part of the logarithm
    // and add it to the result and finally calculate y = x * 2^(-n).
    uint256 n = mostSignificantBit(x / 1e18);

    // The integer part of the logarithm as an unsigned 60.18-decimal fixed-point number.
    // The operation can't overflow because n is maximum 255 and SCALE is 1e18.
    uint256 rValue = n * 1e18;

    // This is y = x * 2^(-n).
    uint256 y = x >> n;

    // If y = 1, the fractional part is zero.
    if (y == 1e18) {
      return rValue;
    }

    // Calculate the fractional part via the iterative approximation.
    // The "delta >>= 1" part is equivalent to "delta /= 2", but shifting bits is faster.
    for (uint256 delta = 5e17; delta > 0; delta >>= 1) {
      y = (y * y) / 1e18;

      // Is y^2 > 2 and so in the range [2,4)?
      if (y >= 2 * 1e18) {
        // Add the 2^(-m) factor to the logarithm.
        rValue += delta;

        // Corresponds to z/2 on Wikipedia.
        y >>= 1;
      }
    }
    return rValue;
  }

  /// @notice Finds the zero-based index of the first one in the binary representation of x.
  /// @dev See the note on msb in the "Find First Set"
  ///      Wikipedia article https://en.wikipedia.org/wiki/Find_first_set
  /// @param x The uint256 number for which to find the index of the most significant bit.
  /// @return msb The index of the most significant bit as an uint256.
  //noinspection NoReturn
  function mostSignificantBit(uint256 x) internal pure returns (uint256 msb) {
    if (x >= 2 ** 128) {
      x >>= 128;
      msb += 128;
    }
    if (x >= 2 ** 64) {
      x >>= 64;
      msb += 64;
    }
    if (x >= 2 ** 32) {
      x >>= 32;
      msb += 32;
    }
    if (x >= 2 ** 16) {
      x >>= 16;
      msb += 16;
    }
    if (x >= 2 ** 8) {
      x >>= 8;
      msb += 8;
    }
    if (x >= 2 ** 4) {
      x >>= 4;
      msb += 4;
    }
    if (x >= 2 ** 2) {
      x >>= 2;
      msb += 2;
    }
    if (x >= 2 ** 1) {
      // No need to shift x any more.
      msb += 1;
    }
  }

}

File 30 of 48 : ControllerContextLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../interfaces/IController.sol";
import "../interfaces/IOracle.sol";
import "../interfaces/IStatController.sol";
import "../interfaces/IStoryController.sol";
import "../interfaces/ITreasury.sol";
import "../interfaces/IDungeonFactory.sol";
import "../interfaces/IReinforcementController.sol";
import "../interfaces/IGameToken.sol";
import "../interfaces/IGOC.sol";
import "../interfaces/IItemController.sol";
import "../interfaces/IHeroController.sol";
import "../interfaces/IUserController.sol";
import "../interfaces/IGuildController.sol";
import "../interfaces/IRewardsPool.sol";

/// @notice Provide context-struct with all controller addresses and routines for lazy init
/// Usage:
///       Create an instance of the structure
///               cc = ControllerContextLib.init(controller);
///       access controller directly
///               cc.controller.xxx();
///       access other contracts indirectly
///               sc = ControllerContextLib.getStatController(cc);
library ControllerContextLib {
  struct ControllerContext {
    IController controller;
    IStatController statController;
    IStoryController storyController;
    IOracle oracle;
    ITreasury treasury;
    IDungeonFactory dungeonFactory;
    IGOC gameObjectController;
    IReinforcementController reinforcementController;
    IItemController itemController;
    IHeroController heroController;
    IGameToken gameToken;
    IUserController userController;
    IGuildController guildController;
    IRewardsPool rewardsPool;
  }

  function init(IController controller) internal pure returns (ControllerContext memory cc) {
    cc.controller = controller;
    return cc;
  }

  function getStatController(ControllerContext memory cc) internal view returns (IStatController statController) {
    if (address(cc.statController) == address(0)) {
      cc.statController = IStatController(cc.controller.statController());
    }
    return cc.statController;
  }

  function getStoryController(ControllerContext memory cc) internal view returns (IStoryController storyController) {
    if (address(cc.storyController) == address(0)) {
      cc.storyController = IStoryController(cc.controller.storyController());
    }
    return cc.storyController;
  }

  function getOracle(ControllerContext memory cc) internal view returns (IOracle oracle) {
    if (address(cc.oracle) == address(0)) {
      cc.oracle = IOracle(cc.controller.oracle());
    }
    return cc.oracle;
  }

  function getTreasury(ControllerContext memory cc) internal view returns (ITreasury treasury) {
    if (address(cc.treasury) == address(0)) {
      cc.treasury = ITreasury(cc.controller.treasury());
    }
    return cc.treasury;
  }

  function getDungeonFactory(ControllerContext memory cc) internal view returns (IDungeonFactory dungeonFactory) {
    if (address(cc.dungeonFactory) == address(0)) {
      cc.dungeonFactory = IDungeonFactory(cc.controller.dungeonFactory());
    }
    return cc.dungeonFactory;
  }

  function getGameObjectController(ControllerContext memory cc) internal view returns (IGOC gameObjectController) {
    if (address(cc.gameObjectController) == address(0)) {
      cc.gameObjectController = IGOC(cc.controller.gameObjectController());
    }
    return cc.gameObjectController;
  }

  function getReinforcementController(ControllerContext memory cc) internal view returns (IReinforcementController reinforcementController) {
    if (address(cc.reinforcementController) == address(0)) {
      cc.reinforcementController = IReinforcementController(cc.controller.reinforcementController());
    }
    return cc.reinforcementController;
  }

  function getItemController(ControllerContext memory cc) internal view returns (IItemController itemController) {
    if (address(cc.itemController) == address(0)) {
      cc.itemController = IItemController(cc.controller.itemController());
    }
    return cc.itemController;
  }

  function getHeroController(ControllerContext memory cc) internal view returns (IHeroController heroController) {
    if (address(cc.heroController) == address(0)) {
      cc.heroController = IHeroController(cc.controller.heroController());
    }
    return cc.heroController;
  }

  function getGameToken(ControllerContext memory cc) internal view returns (IGameToken gameToken) {
    if (address(cc.gameToken) == address(0)) {
      cc.gameToken = IGameToken(cc.controller.gameToken());
    }
    return cc.gameToken;
  }

  function getUserController(ControllerContext memory cc) internal view returns (IUserController userController) {
    if (address(cc.userController) == address(0)) {
      cc.userController = IUserController(cc.controller.userController());
    }
    return cc.userController;
  }

  function getGuildController(ControllerContext memory cc) internal view returns (IGuildController guildController) {
    if (address(cc.guildController) == address(0)) {
      cc.guildController = IGuildController(cc.controller.guildController());
    }
    return cc.guildController;
  }

  function getRewardsPool(ControllerContext memory cc) internal view returns (IRewardsPool rewardsPool) {
    if (address(cc.rewardsPool) == address(0)) {
      cc.rewardsPool = IRewardsPool(cc.controller.rewardsPool());
    }
    return cc.rewardsPool;
  }
}

File 31 of 48 : ItemControllerHelperLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../interfaces/IERC721.sol";
import "../interfaces/IAppErrors.sol";
import "../interfaces/IItemControllerHelper.sol";
import "./ItemStatsLib.sol";

library ItemControllerHelperLib {
  using EnumerableSet for EnumerableSet.UintSet;

  //region ------------------------ Constants
  /// @dev keccak256(abi.encode(uint256(keccak256("item.controller.helper.main")) - 1)) & ~bytes32(uint256(0xff))
  bytes32 internal constant ITEM_CONTROLLER_HELPER_MAIN_STORAGE_LOCATION = 0x369019597a17a51d9b72838e37238167004097b8e88fd096bb2d3ab0fa92ac00; // item.controller.helper.main
  //endregion ------------------------ Constants

  //region ------------------------ Storage
  function _S() internal pure returns (IItemControllerHelper.MainState storage s) {
    assembly {
      s.slot := ITEM_CONTROLLER_HELPER_MAIN_STORAGE_LOCATION
    }
    return s;
  }
  //endregion ------------------------ Storage

  //region ------------------------ Views
  function listUnionConfigsLength() internal view returns (uint) {
    return _S().listUnionConfigs.length();
  }

  function unionConfigIdByIndex(uint index) internal view returns (uint) {
    return _S().listUnionConfigs.at(index);
  }
  
  function unionConfig(uint configId) internal view returns (IItemControllerHelper.UnionConfig memory) {
    return _S().unionConfig[configId];
  }

  function keyPassItem() internal view returns (address) {
    return address(uint160(_S().globalParam[IItemControllerHelper.GlobalParam.UNION_KEY_PASS_ITEM_1]));
  }

  function getGlobalParamValue(uint8 globalParam) internal view returns (uint) {
    return _S().globalParam[IItemControllerHelper.GlobalParam(globalParam)];
  }

  //endregion ------------------------ Views

  //region ------------------------ Deployer actions
  function addUnionConfig(IController controller, address[] memory items, uint[] memory count, address itemToMint) internal returns (uint) {
    ItemStatsLib.onlyDeployer(controller, msg.sender);

    _checkConfigItems(items, count);

    uint configId = _S().globalParam[IItemControllerHelper.GlobalParam.UNION_ID_COUNTER_2] + 1;
    _S().globalParam[IItemControllerHelper.GlobalParam.UNION_ID_COUNTER_2] = configId;

    _S().listUnionConfigs.add(configId);
    _S().unionConfig[configId] = IItemControllerHelper.UnionConfig({
      configId: uint96(configId),
      itemToMint: itemToMint,
      items: items,
      count: count
    });

    emit IApplicationEvents.SetUnionConfig(configId, items, count, itemToMint);

    return configId;
  }

  function updateUnionConfig(IController controller, uint configId, address[] memory items, uint[] memory count, address itemToMint) internal {
    ItemStatsLib.onlyDeployer(controller, msg.sender);

    _checkConfigItems(items, count);

    if (configId == 0 || _S().unionConfig[configId].configId != configId) revert IAppErrors.UnknownUnionConfig(configId);
    _S().unionConfig[configId] = IItemControllerHelper.UnionConfig({
      configId: uint96(configId),
      itemToMint: itemToMint,
      items: items,
      count: count
    });

    emit IApplicationEvents.SetUnionConfig(configId, items, count, itemToMint);
  }

  function removeUnionConfig(IController controller, uint configId) internal {
    ItemStatsLib.onlyDeployer(controller, msg.sender);

    if (configId == 0 || _S().unionConfig[configId].configId != configId) revert IAppErrors.UnknownUnionConfig(configId);
    delete _S().unionConfig[configId];
    _S().listUnionConfigs.remove(configId);

    emit IApplicationEvents.RemoveUnionConfig(configId);
  }

  /// @notice Set key pass item. User should own this item to be able to combine items.
  /// @param item 0 is allowed, it means there is no keyPass item
  function setUnionKeyPassItem(IController controller, address item) internal {
    ItemStatsLib.onlyDeployer(controller, msg.sender);

    _S().globalParam[IItemControllerHelper.GlobalParam.UNION_KEY_PASS_ITEM_1] = uint(uint160(item));
    emit IApplicationEvents.SetUnionKeyPass(item);
  }
  //endregion ------------------------ Deployer actions

  //region ------------------------ Item controller actions
  /// @notice Check if list of {items} passed by {user} fit to config, ensure that user is able to combine items
  /// @param items List of items to destroy. Should contain exactly same items as the config. Items order doesn't matter
  /// @param tokens List of tokens of each item to destroy. Length of the tokens should fit to count specified in config
  /// @dev itemTokenIds[count tokens of i-th item][countItems]
  /// @return Item to mint in exchange of {items}
  function prepareToCombine(address user, uint configId, address[] memory items, uint[][] memory tokens) internal view returns (address) {
    // user must own keyPass item
    address _keyPassItem = keyPassItem();
    if (_keyPassItem != address(0)) {
      if (0 == IERC721(_keyPassItem).balanceOf(user)) revert IAppErrors.UserHasNoKeyPass(user, _keyPassItem);
    }

    // config exists
    IItemControllerHelper.UnionConfig storage config = _S().unionConfig[configId];
    if (configId == 0 || config.configId != configId) revert IAppErrors.UnknownUnionConfig(configId);

    // ensure that {items} and {tokens} are fit to the required config
    checkItemsByConfig(user, config.items, config.count, items, tokens);

    return config.itemToMint;
  }
  //endregion ------------------------ Item controller actions  
  
  //region ------------------------ Internal logic
  /// @notice Ensure that list of passed items fit to the list of the items specified in union config.
  /// Check that the user is owner of each passed token.
  /// @param configItems List of items required by the config.
  /// @param configCount Count of items required by the config.
  /// @param items List of items to destroy. Order of the items doesn't matter.
  /// @param itemTokenIds List of tokens to destroy. itemTokenIds[count tokens of i-th item][countItems]
  function checkItemsByConfig(
    address user,
    address[] memory configItems,
    uint[] memory configCount,
    address[] memory items,
    uint[][] memory itemTokenIds
  ) internal view {
    uint len = configItems.length;
    if (items.length != len || itemTokenIds.length != len) revert IAppErrors.LengthsMismatch();

    // we don't need to check duplicates in {items} - assume that there are no duplicates in config
    for (uint i; i < len; ++i) {
      // find index of the {configItems[i]} in the array {items} passed by the user
      uint index = type(uint).max;
      for (uint j; j < len; ++j) {
        if (items[j] == configItems[i]) {
          index = j;
          break;
        }
      }
      if (index == type(uint).max) revert IAppErrors.UnionItemNotFound(configItems[i]);

      // check ownership of {count} tokens of the given item
      uint[] memory tokens = itemTokenIds[index];
      uint lenTokens = tokens.length;

      if (configCount[i] != lenTokens) revert IAppErrors.WrongListUnionItemTokens(configItems[i], lenTokens, configCount[i]);

      for (uint k; k < lenTokens; ++k) {
        if (IERC721(configItems[i]).ownerOf(tokens[k]) != user) revert IAppErrors.ErrorNotOwner(configItems[i], tokens[k]);
      }

      // we don't check uniqueness of the tokens in {itemTokenIds}
      // if user will try to use same tokenId multiple times, we will have revert on destroying stage
    }
  }

  function _checkConfigItems(address[] memory items, uint[] memory count) internal pure {
    // assume, that the items don't repeat - it's to expensive to check it on-chain
    uint len = items.length;
    if (len == 0) revert IAppErrors.ZeroValueNotAllowed();
    if (len != count.length) revert IAppErrors.LengthsMismatch();
  }
  //endregion ------------------------ Internal logic

}

File 32 of 48 : ItemLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../interfaces/IAppErrors.sol";
import "../interfaces/IApplicationEvents.sol";
import "../interfaces/IGuildController.sol";
import "../interfaces/IItem.sol";
import "../interfaces/IItemController.sol";
import "../interfaces/IOracle.sol";
import "../openzeppelin/Math.sol";
import "../solady/LibPRNG.sol";
import "./CalcLib.sol";
import "./ControllerContextLib.sol";
import "./ShelterLib.sol";
import "./StatLib.sol";

library ItemLib {
  using CalcLib for int32;
  using PackingLib for address;
  using PackingLib for bytes32;
  using PackingLib for bytes32[];
  using PackingLib for uint32[];
  using PackingLib for int32[];

  //region ------------------------ Data types
  struct GenerateAttributesContext {
    /// @notice True if max allowed amount of random attributes were reached inside {_prepareAttributes}
    bool stopGenerateRandom;
    /// @notice Flag - attribute was generated. The array matches to info.ids
    bool[] usedIndexes;
    /// @notice Ids of the generated attributes
    uint8[] ids;
    /// @notice Randomly selected values of the generated attributes
    int32[] values;
    /// @notice Counter of the stored values into {ids} and {values}
    uint counter;
    /// @notice Total number of random attributes that were generated inside {_prepareAttributes}
    uint randomAttrCounter;
    /// @notice Total sum of all {random} values for random attributes generated in {_prepareAttributes}
    uint randomSum;
    /// @notice Total sum of all chances of the random attributes generated in {_prepareAttributes}
    uint chancesSum;
    /// @notice Total number of random attributes that can be generated
    uint totalRandomAttrsPossible;
  }

  struct MintItemInfo {
    uint8 maxItems;
    int32 magicFind;
    int32 destroyItems;
    uint32[] mintItemsChances;
    IOracle oracle;
    address[] mintItems;
    uint amplifier;
    uint seed;
    /// @notice Penalty to reduce chance as chance/delta if the hero not in his biome
    /// @dev Use StatLib.mintDropChanceDelta
    uint mintDropChanceDelta;
    /// @notice SCR-1064: drop chance depends on NG_LEVEL, decimals 18, value is in the range [0...1e18]
    /// it's always 100% for NG0 (no reduce, value is 1e18)
    /// Use {dropChancePercent} to calculate actual value
    uint mintDropChanceNgLevelMultiplier;
  }
  //endregion ------------------------ Data types

  //region ------------------------ Restrictions
  /// @notice ensure that the user belongs to a guild, the guild has a shelter, the shelter has highest level 3
  function _onlyMemberOfGuildWithShelterMaxLevel(IController controller, address msgSender) internal view {
    // ensure that signer belongs to a guild and the guild has a shelter of ANY level
    IGuildController gc = IGuildController(controller.guildController());

    uint guildId = gc.memberOf(msgSender);
    if (guildId == 0) revert IAppErrors.NotGuildMember();

    uint shelterId = gc.guildToShelter(guildId);
    if (shelterId == 0) revert IAppErrors.GuildHasNoShelter();

    // only highest level of shelters gives possibility to exit from dungeon
    (, uint8 shelterLevel,) = PackingLib.unpackShelterId(shelterId);
    if (shelterLevel != ShelterLib.MAX_SHELTER_LEVEL) revert IAppErrors.TooLowShelterLevel(shelterLevel, ShelterLib.MAX_SHELTER_LEVEL);
  }

  //endregion ------------------------ Restrictions

  //region ------------------------ Main logic

  /// @notice Mint new item, setup attributes, make extra setup if necessary (setup attack item, buff item)
  /// @param sender Dungeon Factory / User Controller / Guild Controller are allowed
  /// @param item Item to be minted
  /// @param recipient The item is minted for the given recipient
  /// @return itemId Id of the newly minted item
  function mintNewItem(
    IItemController.MainState storage s,
    IController controller,
    address sender,
    address item,
    address recipient
  ) external returns (uint itemId) {
    ControllerContextLib.ControllerContext memory ctx = ControllerContextLib.init(controller);

    address guildController = address(ControllerContextLib.getGuildController(ctx));
    address shelterController = guildController == address(0)
      ? address(0)
      : IGuildController(guildController).shelterController();

    if (
      address(ControllerContextLib.getDungeonFactory(ctx)) != sender
      && address(ControllerContextLib.getUserController(ctx)) != sender
      && guildController != sender
      && shelterController != sender
      && address(ControllerContextLib.getItemController(ctx)) != sender
      && address(ControllerContextLib.getHeroController(ctx)) != sender
    ) revert IAppErrors.MintNotAllowed();

    itemId = IItem(item).mintFor(recipient);

    IItemController.MintInfo memory info;

    (
      info.meta,
      info.attributesIds,
      info.attributesValues,
      info.itemRarity
    ) = _setupNewAttributes(s, item, itemId, CalcLib.pseudoRandom);

    // setup extra info

    if (info.meta.itemMetaType == uint8(IItemController.ItemMetaType.ATTACK)) {
      info.attackInfo = unpackItemAttackInfo(_setupNewAttackItem(s, item, itemId));
    } else if (info.meta.itemMetaType == uint8(IItemController.ItemMetaType.BUFF)) {
      (
        info.casterIds,
        info.casterValues,
        info.targetIds,
        info.targetValues
      ) = _setupNewBuffItem(s, item, itemId, CalcLib.pseudoRandom);
    }
    // consumable stats unchangeable, get them by address

    emit IApplicationEvents.NewItemMinted(item, itemId, info);
  }

  /// @notice Mint random items, not more than {info.maxItems}
  function mintRandomItems(MintItemInfo memory info) internal returns (address[] memory) {
    return _mintRandomItems(info, CalcLib.nextPrng);
  }

  function applyActionMasks(
    uint actionMask,
    IStatController statController,
    address heroToken,
    uint heroTokenId
  ) external {
    if ((actionMask & (2 ** uint(IItemController.ConsumableActionBits.CLEAR_TEMPORARY_ATTRIBUTES_0))) != 0) {
      statController.clearTemporallyAttributes(heroToken, heroTokenId);
    }
  }

  //endregion ------------------------ Main logic

  //region ------------------------ Internal logic
  /// @param nextPrng_ CalcLib.nextPrng, param is required by unit tests
  function _mintRandomItems(
    MintItemInfo memory info,
    function (LibPRNG.PRNG memory, uint) internal view returns (uint) nextPrng_
  ) internal returns (address[] memory) {

    // if hero is not in his biome do not mint at all
    if (info.mintDropChanceDelta != 0) {
      return new address[](0);
    }

    uint len = info.mintItems.length;

    // Fisher–Yates shuffle
    LibPRNG.PRNG memory prng = LibPRNG.PRNG(info.oracle.getRandomNumber(CalcLib.MAX_CHANCE, info.seed));
    uint[] memory indices = new uint[](len);
    for (uint i = 1; i < len; ++i) {
      indices[i] = i;
    }
    LibPRNG.shuffle(prng, indices);

    address[] memory minted = new address[](len);
    uint mintedLength;
    uint di = Math.min(CalcLib.toUint(info.destroyItems), 100);

    for (uint i; i < len; ++i) {
      if (info.mintItemsChances[indices[i]] > CalcLib.MAX_CHANCE) {
        revert IAppErrors.TooHighChance(info.mintItemsChances[indices[i]]);
      }

      uint chance = _adjustChance(info.mintItemsChances[indices[i]], info, di);

      // need to call random in each loop coz each minted item should have dedicated chance
      uint rnd = nextPrng_(prng, CalcLib.MAX_CHANCE); // randomWithSeed_(CalcLib.MAX_CHANCE, rndSeed);

      if (chance != 0 && (chance >= CalcLib.MAX_CHANCE || rnd < chance)) {
        // There is no break here: the cycle is continued even if the number of the minted items reaches the max.
        // The reason: gas consumption of success operation must be great of equal of the gas consumption of fail op.
        if (mintedLength < info.maxItems) {
          minted[i] = info.mintItems[indices[i]];
          ++mintedLength;
        }
      }
    }

    address[] memory mintedAdjusted = new address[](mintedLength);
    uint j;
    for (uint i; i < len; ++i) {
      if (minted[i] != address(0)) {
        mintedAdjusted[j] = minted[i];
        ++j;
      }
    }

    return mintedAdjusted;
  }

  /// @notice Apply all corrections to the chance of item drop
  /// There are two params to increase chances: amplifier and magicFind
  /// There are two params to decrease chances: destroyItems and mintDropChanceNgLevelMultiplier
  /// @param info Assume here, that info.mintDropChanceNgLevelMultiplier is in the range [0..1e18]
  /// @param di Assume that di <= 100
  function _adjustChance(uint32 itemChance, MintItemInfo memory info, uint di) internal pure returns (uint) {
    uint chance = uint(itemChance) * Math.min(1e18, info.mintDropChanceNgLevelMultiplier) / 1e18;
    chance += chance * info.amplifier / StatLib._MAX_AMPLIFIER;
    chance += chance * CalcLib.toUint(info.magicFind) / 100;
    chance -= chance * di / 100;
    return chance;
  }

  function _setupNewAttributes(
    IItemController.MainState storage s,
    address item,
    uint itemId,
    function (uint) internal view returns (uint) random_
  ) internal returns (
    IItemController.ItemMeta memory meta,
    uint8[] memory ids,
    int32[] memory values,
    IItemController.ItemRarity itemRarity
  ){
    meta = unpackedItemMeta(s.itemMeta[item]);
    (ids, values, itemRarity) = _generateAttributes(unpackItemGenerateInfo(s.generateInfoAttributes[item]), meta, random_);

    bytes32 packedItemId = item.packNftId(itemId);
    if (ids.length != 0) {
      s._itemAttributes[packedItemId] = values.toBytes32ArrayWithIds(ids);
    }

    s.itemInfo[packedItemId] = PackingLib.packItemInfo(uint8(itemRarity), 0, meta.baseDurability);
  }

  function _setupNewAttackItem(IItemController.MainState storage s, address item, uint itemId) internal returns (bytes32 attackInfo){
    // we just write data for attack item, no need to generate, it will be augmented later so need individual data for itemId
    attackInfo = s.generateInfoAttack[item];
    s._itemAttackInfo[item.packNftId(itemId)] = attackInfo;
  }

  function _setupNewBuffItem(
    IItemController.MainState storage s,
    address item,
    uint itemId,
    function (uint) internal view returns (uint) random_
  ) internal returns (
    uint8[] memory casterIds,
    int32[] memory casterValues,
    uint8[] memory targetIds,
    int32[] memory targetValues
  ){

    // CASTER
    (casterIds, casterValues) = _generateSimpleAttributes(
      unpackItemGenerateInfo(s.generateInfoCasterAttributes[item]),
      true,
      random_
    );

    if (casterIds.length != 0) {
      s._itemCasterAttributes[item.packNftId(itemId)] = casterValues.toBytes32ArrayWithIds(casterIds);
    }

    // TARGET
    (targetIds, targetValues) = _generateSimpleAttributes(
      unpackItemGenerateInfo(s.generateInfoTargetAttributes[item]),
      true,
      random_
    );

    if (targetIds.length != 0) {
      s._itemTargetAttributes[item.packNftId(itemId)] = targetValues.toBytes32ArrayWithIds(targetIds);
    }
  }

  /// @notice Generate all mandatory attributes and try to generate required number of random attributes.
  /// Generate at least {info.minRandomAttributes} of random attributes if it's possible
  /// but not more than {info.maxRandomAttributes}. Value of each attribute is generated randomly according its chances.
  /// @param meta Assume, that meta.min != 0, meta.max != 0 and both meta.min and meta.min should have same sign
  /// because results value cannot be 0
  /// @return ids Ids of the attributes, zero id is allowed
  /// @return values Randomly generated attributes values, min <= value <= max
  /// @return itemRarity Rarity of the item (Either meta.defaultRarity or calculated if there is no default rarity)
  function _generateAttributes(
    IItemController.ItemGenerateInfo memory info,
    IItemController.ItemMeta memory meta,
    function (uint) internal view returns (uint) random_
  ) internal view returns (
    uint8[] memory ids,
    int32[] memory values,
    IItemController.ItemRarity itemRarity
  ) {
    GenerateAttributesContext memory ctx;

    uint len = info.ids.length;
    if (len != 0) {
      ctx.ids = new uint8[](len);
      ctx.values = new int32[](len);
      ctx.usedIndexes = new bool[](len);

      // Fisher–Yates shuffle
      _shuffleInfo(info, random_);

      // initialize ctx by initial values
      // generate all mandatory attributes, try to generate not more than {meta.maxRandomAttributes} random attributes
      _prepareAttributes(info, meta.maxRandomAttributes, ctx, random_);

      // generate missing random attributes if it's necessary, ctx.counter is incremented
      _generateMissingRandomAttributes(info, meta.minRandomAttributes, ctx, random_);

      itemRarity = meta.defaultRarity == 0
        ? _calculateRarity(ctx.randomSum, ctx.chancesSum, ctx.randomAttrCounter, meta.maxRandomAttributes)
        : IItemController.ItemRarity(meta.defaultRarity);
    } else {
      itemRarity = IItemController.ItemRarity.UNKNOWN;
    }

    (ids, values) = _fixLengthsIdsValues(ctx.ids, ctx.values, ctx.counter);
  }

  /// @notice Generate missing random attributes if necessary
  function _generateMissingRandomAttributes(
    IItemController.ItemGenerateInfo memory info,
    uint8 minRandomAttributes,
    GenerateAttributesContext memory ctx,
    function (uint) internal view returns (uint) random_
  ) internal view {
    uint attrToGen = Math.min(ctx.totalRandomAttrsPossible, minRandomAttributes);
    if (ctx.randomAttrCounter < attrToGen && ctx.totalRandomAttrsPossible > ctx.randomAttrCounter) {
      // it's necessary AND possible to generate more random attributes
      uint possibleRemainingAttrs = ctx.totalRandomAttrsPossible - ctx.randomAttrCounter;
      uint remainingAttrsToGen = attrToGen - ctx.randomAttrCounter;

      uint[] memory indicesToGen = new uint[](possibleRemainingAttrs);
      uint indicesToGenCounter;

      // enumerate all attributes, add all indices of not-generated attributes to {indexesToGen}
      for (uint i; i < info.ids.length; ++i) {
        // mandatory attrs should be already generated and no need to check
        if (!ctx.usedIndexes[i]) {
          indicesToGen[indicesToGenCounter] = i;
          indicesToGenCounter++;
        }
      }

      // Shuffle indices of not-generated attributes using Fisher–Yates shuffle
      if (possibleRemainingAttrs > 1) {
        for (uint i; i < possibleRemainingAttrs - 1; ++i) {
          uint randomIndex = CalcLib.pseudoRandomInRangeFlex(i, possibleRemainingAttrs - 1, random_);
          (indicesToGen[randomIndex], indicesToGen[i]) = (indicesToGen[i], indicesToGen[randomIndex]);
        }
      }
      // Generate necessary amount of attributes. Fist (shuffled) attributes are selected (MAX_CHANCE is used for each)
      for (uint i; i < remainingAttrsToGen; ++i) {
        uint idx = indicesToGen[i];
        (int32 attr,) = _generateAttribute(info.mins[idx], info.maxs[idx], CalcLib.MAX_CHANCE, random_);
        ctx.ids[ctx.counter] = info.ids[idx];
        ctx.values[ctx.counter] = attr;
        ctx.counter++;
      }
    }
  }

  /// @notice Generate all mandatory attributes, generate not more than {meta.maxRandomAttributes} random attributes.
  /// Updates context:
  ///   {ctx.totalRandomAttrsPossible} - total number of possible random attributes
  ///   {ctx.randomAttrCounter} - total number of generated random attributes  <= {maxRandomAttributes}
  ///   {ctx.randomSum} = sum of random of all random attributes.
  ///   {ctx.chancesSum} = sum of chances of all random attributes.
  ///   {ctx.counter} = total number of generated attributes. Values of ctx.ids, ctx.values, ctx.usedIndexes are
  ///   initialized in the range [0...ctx.counter)
  /// @param ctx Empty struct but arrays ids, values and usedIndexes should be allocated for info.ids.length items
  function _prepareAttributes(
    IItemController.ItemGenerateInfo memory info,
    uint8 maxRandomAttributes,
    GenerateAttributesContext memory ctx,
    function (uint) internal view returns (uint) random_
  ) internal view {
    uint len = info.ids.length;
    for (uint i; i < len; ++i) {
      if (info.chances[i] != CalcLib.MAX_CHANCE) {
        ctx.totalRandomAttrsPossible++;
      }

      if (info.chances[i] >= CalcLib.MAX_CHANCE || !ctx.stopGenerateRandom) {
        (int32 attr, uint random) = _generateAttribute(info.mins[i], info.maxs[i], info.chances[i], random_);

        // count only random attributes for calc rarity
        if (attr != 0) {

          if (
            info.chances[i] < CalcLib.MAX_CHANCE
            // && random != 0 // commented: random = 0 can produce crash in _generateMissingRandomAttributes
          ) {
            ctx.randomAttrCounter++;
            ctx.randomSum += random;
            ctx.chancesSum += info.chances[i];
          }
          ctx.ids[ctx.counter] = info.ids[i];
          ctx.values[ctx.counter] = attr;
          ctx.counter++;
          ctx.usedIndexes[i] = true;
        }

        // it is a bit less fair random for attrs in the end of the list, however we assume it should be pretty rare case
        if (ctx.randomAttrCounter == maxRandomAttributes) {
          ctx.stopGenerateRandom = true;
        }
      }
    }
  }

  /// @notice Shuffle info arrays using Fisher–Yates shuffle algo
  function _shuffleInfo(
    IItemController.ItemGenerateInfo memory info,
    function (uint) internal view returns (uint) random_
  ) internal view {
    uint len = info.ids.length;
    if (len > 1) {
      for (uint i; i < len - 1; i++) {
        uint randomIndex = CalcLib.pseudoRandomInRangeFlex(i, len - 1, random_);

        (info.ids[randomIndex], info.ids[i]) = (info.ids[i], info.ids[randomIndex]);
        (info.mins[randomIndex], info.mins[i]) = (info.mins[i], info.mins[randomIndex]);
        (info.maxs[randomIndex], info.maxs[i]) = (info.maxs[i], info.maxs[randomIndex]);
        (info.chances[randomIndex], info.chances[i]) = (info.chances[i], info.chances[randomIndex]);
      }
    }
  }

  /// @notice Generate array [0,1,2.. N-1] and shuffle it using Fisher–Yates shuffle algo
  function _shuffleIndices(
    uint countItems,
    function (uint) internal view returns (uint) random_
  ) internal view returns (uint[] memory indices){
    indices = new uint[](countItems);
    for (uint i = 1; i < countItems; ++i) {
      indices[i] = i;
    }
    if (countItems > 1) {
      for (uint i; i < countItems - 1; i++) {
        uint randomIndex = CalcLib.pseudoRandomInRangeFlex(i, countItems - 1, random_);
        (indices[randomIndex], indices[i]) = (indices[i], indices[randomIndex]);
      }
    }
  }

  /// @notice Reduce lengths of {ids} and {values} to {count}
  function _fixLengthsIdsValues(uint8[] memory ids, int32[] memory values, uint count) internal pure returns (
    uint8[] memory idsOut,
    int32[] memory valuesOut
  ) {
    if (count == ids.length) {
      return (ids, values);
    }

    idsOut = new uint8[](count);
    valuesOut = new int32[](count);
    for (uint i; i < count; ++i) {
      idsOut[i] = ids[i];
      valuesOut[i] = values[i];
    }
    return (idsOut, valuesOut);
  }

  /// @param random_ Pass CalcLib.pseudoRandom here, param is required for unit tests. Max value is MAX_CHANCE
  function _generateSimpleAttributes(
    IItemController.ItemGenerateInfo memory info,
    bool maxChance,
    function (uint) internal view returns (uint) random_
  ) internal view returns (
    uint8[] memory ids,
    int32[] memory values
  ) {
    uint len = info.ids.length;
    ids = new uint8[](len);
    values = new int32[](len);

    uint n = 0;
    for (uint i; i < len; ++i) {
      (int32 attr,) = _generateAttribute(
        info.mins[i],
        info.maxs[i],
        maxChance ? CalcLib.MAX_CHANCE : info.chances[i],
        random_
      );
      if (attr != 0) {
        ids[n] = info.ids[i];
        values[n] = attr;
        ++n;
      }
    }

    return _fixLengthsIdsValues(ids, values, n);
  }

  //endregion ------------------------ Internal logic

  //region ------------------------ Internal utils
  /// @param chance Chance in the range [0...MAX_CHANCE], MAX_CHANCE=1e9 means "mandatory" element.
  /// @param random_ Pass CalcLib.pseudoRandom here, param is required for unit tests
  /// @return attr Either 0 or min <= attr <= max
  /// @return rnd Random value in the range [0...MAX_CHANCE]; It's always 0 for mandatory elements
  function _generateAttribute(
    int32 min,
    int32 max,
    uint32 chance,
    function (uint) internal view returns (uint) random_
  ) internal view returns (
    int32 attr,
    uint rnd
  ) {
    if (chance > CalcLib.MAX_CHANCE) revert IAppErrors.TooHighChance(chance);

    uint diff = uint(CalcLib.absDiff(min, max));

    if (chance < CalcLib.MAX_CHANCE) {
      uint32 random = CalcLib.pseudoRandomUint32Flex(CalcLib.MAX_CHANCE, random_);
      if (random < chance) {
        uint r = uint(CalcLib.MAX_CHANCE - random * (CalcLib.MAX_CHANCE / chance));
        int32 k = int32(int(r * diff / uint(CalcLib.MAX_CHANCE)));
        return (min + k, random);
      }
    } else { // chance == CalcLib.MAX_CHANCE => mandatory element
      if (diff == 0) {
        return (min, 0);
      } else {
        uint r = uint(CalcLib.pseudoRandomUint32Flex(CalcLib.MAX_CHANCE, random_));
        int32 k = int32(int(r % (diff + 1)));

        // return zero random - no need to calc rarity for mandatory elements
        return (min + k, 0);
      }
    }

    return (0, 0);
  }

  /// @notice Calculate item rarity
  /// @param randomSum Total sum random values of all random attributes in ItemGenerateInfo, [0...MAX_CHANCE/attrCounter]
  /// @param chancesSum Total sum of all random chances in ItemGenerateInfo
  /// @param attrCounter Count of random attributes in ItemGenerateInfo
  /// @param maxAttr Index of max allowed random attribute (all attributes with higher indices are not random)
  /// @return item rarity
  function _calculateRarity(uint randomSum, uint chancesSum, uint attrCounter, uint maxAttr) internal pure returns (
    IItemController.ItemRarity
  ) {
    if (attrCounter == 0) {
      return IItemController.ItemRarity.NORMAL;
    }

    uint random = randomSum / attrCounter;
    uint averageChance = chancesSum / attrCounter;

    if (random > CalcLib.MAX_CHANCE) revert IAppErrors.TooHighRandom(random);

    if (random < averageChance / 4 && attrCounter == maxAttr) {
      return IItemController.ItemRarity.RARE;
    } else if (random < averageChance * 3 / 4) {
      return attrCounter > 2
        ? IItemController.ItemRarity.RARE
        : IItemController.ItemRarity.MAGIC;
    } else {
      return attrCounter > 1
        ? IItemController.ItemRarity.MAGIC
        : IItemController.ItemRarity.NORMAL;
    }
  }
  //endregion ------------------------ Internal utils

  //region ------------------------ PACKING

  function packItemGenerateInfo(IItemController.ItemGenerateInfo memory info) internal pure returns (bytes32[] memory result) {
    uint len = info.ids.length;
    if (len != info.mins.length || len != info.maxs.length || len != info.chances.length) {
      revert IAppErrors.LengthsMismatch();
    }

    result = new bytes32[](len);

    for (uint i; i < len; ++i) {
      result[i] = PackingLib.packItemGenerateInfo(info.ids[i], info.mins[i], info.maxs[i], info.chances[i]);
    }
  }

  function unpackItemGenerateInfo(bytes32[] memory gen) internal pure returns (
    IItemController.ItemGenerateInfo memory
  ) {
    uint length = gen.length;

    uint8[] memory ids = new uint8[](length);
    int32[] memory mins = new int32[](length);
    int32[] memory maxs = new int32[](length);
    uint32[] memory chances = new uint32[](length);

    for (uint i; i < length; ++i) {
      (ids[i], mins[i], maxs[i], chances[i]) = gen[i].unpackItemGenerateInfo();
    }

    return IItemController.ItemGenerateInfo(ids, mins, maxs, chances);
  }

  function packItemMeta(IItemController.ItemMeta memory meta) internal pure returns (bytes32) {
    return PackingLib.packItemMeta(
      meta.itemMetaType,
      meta.itemLevel,
      uint8(meta.itemType),
      meta.baseDurability,
      meta.defaultRarity,
      meta.minRandomAttributes,
      meta.maxRandomAttributes,
      meta.manaCost,
      meta.requirements
    );
  }

  function unpackedItemMeta(bytes32 meta) internal pure returns (IItemController.ItemMeta memory result) {
    return meta.unpackItemMeta();
  }

  function packItemInfo(IItemController.ItemInfo memory info) internal pure returns (bytes32) {
    return PackingLib.packItemInfo(uint8(info.rarity), info.augmentationLevel, info.durability);
  }

  function unpackedItemInfo(bytes32 info) internal pure returns (IItemController.ItemInfo memory result) {
    uint8 rarity;
    (rarity, result.augmentationLevel, result.durability) = info.unpackItemInfo();

    result.rarity = IItemController.ItemRarity(rarity);
    return result;
  }

  function packItemAttackInfo(IItemController.AttackInfo memory info) internal pure returns (bytes32) {
    return PackingLib.packItemAttackInfo(
      uint8(info.aType),
      info.min,
      info.max,
      info.attributeFactors.strength,
      info.attributeFactors.dexterity,
      info.attributeFactors.vitality,
      info.attributeFactors.energy
    );
  }

  function unpackItemAttackInfo(bytes32 info) internal pure returns (IItemController.AttackInfo memory result) {
    IStatController.CoreAttributes memory fs;
    uint8 aType;
    (aType, result.min, result.max, fs.strength, fs.dexterity, fs.vitality, fs.energy) = info.unpackItemAttackInfo();

    result.aType = IItemController.AttackType(aType);
    result.attributeFactors = fs;

    return result;
  }
  //endregion ------------------------ PACKING
}

File 33 of 48 : ItemStatsLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../interfaces/IAppErrors.sol";
import "../interfaces/IApplicationEvents.sol";
import "../interfaces/IDungeonFactory.sol";
import "../interfaces/IERC721.sol";
import "../interfaces/IGuildController.sol";
import "../interfaces/IItem.sol";
import "../interfaces/IItemController.sol";
import "../interfaces/IItemControllerHelper.sol";
import "../interfaces/IReinforcementController.sol";
import "../interfaces/ITreasury.sol";
import "./AppLib.sol";
import "./CalcLib.sol";
import "./ShelterLib.sol";
import "./ItemLib.sol";
import "./PackingLib.sol";
import "./ScoreLib.sol";
import "./OtherItemLib.sol";

library ItemStatsLib {
  using EnumerableSet for EnumerableSet.AddressSet;
  using CalcLib for int32;
  using PackingLib for address;
  using PackingLib for bytes32;
  using PackingLib for bytes32[];
  using PackingLib for uint32[];
  using PackingLib for int32[];

  //region ------------------------ CONSTANTS

  uint private constant AUGMENT_CHANCE = 0.7e18;
  /// @dev should be 20%
  uint private constant AUGMENT_FACTOR = 5;
  uint private constant DURABILITY_REDUCTION = 3;
  uint private constant MAX_AUGMENTATION_LEVEL = 20;

  /// @dev keccak256(abi.encode(uint256(keccak256("item.controller.main")) - 1)) & ~bytes32(uint256(0xff))
  bytes32 internal constant MAIN_STORAGE_LOCATION = 0xe78a2879cd91c3f7b62ea14e72546fed47c40919bca4daada532a5fa05ac6700;

  /// @notice SIP-003: Max value of item fragility that corresponds to 100%
  uint private constant MAX_FRAGILITY = 100_000;
  /// @notice SIP-003: Each successful repair has a chance of increasing the item's fragility by 1%.
  uint private constant FRAGILITY_SUCCESSFUL_REPAIR_PORTION = 1_000;
  /// @notice SIP-003: 10% chance of increasing the item's fragility on successful repair
  uint private constant FRAGILITY_SUCCESSFUL_REPAIR_CHANCE = 15;
  /// @notice SIP-003: The quest mechanic will break the item and increase its fragility by 1%.
  uint private constant FRAGILITY_BREAK_ITEM_PORTION = 1_000;

  //endregion ------------------------ CONSTANTS

  //region ------------------------ STRUCTS

  struct EquipLocalContext {
    IStatController statController;
    IDungeonFactory dungeonFactory;
    IHeroController hc;
    address payToken;
    address heroToken;
    /// @notice Lazy initialization of {equippedSlots}
    bool equippedSlotsLoaded;
    uint8[] equippedSlots;
    uint heroTokenId;
  }

  struct ReduceDurabilityContext {
    /// @notice values 0 or 1 for SKILL_1, SKILL_2, SKILL_3
    uint8[] skillSlots;
    uint8[] busySlots;
    IStatController statController;
    address dungeonFactory;
    address itemAdr;
    uint16 durability;
    uint itemId;
  }

  struct TakeOffContext {
    bool broken;
    IController controller;

    address msgSender;
    address heroToken;
    address destination;
    IHeroController heroController;
    IDungeonFactory dungeonFactory;
    IStatController statController;

    uint heroTokenId;
  }
  //endregion ------------------------ STRUCTS

  //region ------------------------ STORAGE
  function _S() internal pure returns (IItemController.MainState storage s) {
    assembly {
      s.slot := MAIN_STORAGE_LOCATION
    }
    return s;
  }
  //endregion ------------------------ STORAGE

  //region ------------------------ Restrictions

  function onlyDeployer(IController c, address sender) internal view {
    if (!c.isDeployer(sender)) revert IAppErrors.ErrorNotDeployer(sender);
  }

  function onlyOwner(address token, uint tokenId, address sender) internal view {
    if (IERC721(token).ownerOf(tokenId) != sender) revert IAppErrors.ErrorNotOwner(token, tokenId);
  }

  function onlyEOA(bool isEoa) internal view {
    if (!isEoa) {
      revert IAppErrors.NotEOA(msg.sender);
    }
  }

  function onlyStoryController(IController c, address sender) internal view {
    if (sender != c.storyController()) revert IAppErrors.ErrorNotStoryController();
  }

  function onlyNotEquippedItem(address item, uint itemId) internal view {
    if (isItemEquipped(_S(), item, itemId)) revert IAppErrors.ItemEquipped(item, itemId);
  }

  function onlyNotConsumable(IItemController.ItemMeta memory meta, address item) internal pure {
    if (
      uint(meta.itemType) == 0
      || meta.itemMetaType == uint8(IItemController.ItemMetaType.CONSUMABLE) // todo probably first check is enough?
    ) revert IAppErrors.Consumable(item);
  }

  function _checkPauseEoaOwner(
    bool isEoa,
    IController controller,
    address msgSender,
    address item,
    uint itemId
  ) internal view {
    onlyEOA(isEoa);
    if (controller.onPause()) revert IAppErrors.ErrorPaused();
    onlyOwner(item, itemId, msgSender);
  }

  function _onlyRegisteredControllers(IController controller) internal view returns (address dungeonFactory) {
    dungeonFactory = controller.dungeonFactory();
    if (
      msg.sender != dungeonFactory
      && msg.sender != controller.reinforcementController()
      // todo && msg.sender != controller.pvpController()
    ) revert IAppErrors.ErrorForbidden(msg.sender);
  }
  //endregion ------------------------ Restrictions

  //region ------------------------ Views
  function itemByIndex(uint idx) internal view returns (address) {
    return _S().items.at(idx);
  }

  function itemsLength() internal view returns (uint) {
    return _S().items.length();
  }

  function itemMeta(address item) internal view returns (IItemController.ItemMeta memory meta) {
    return ItemLib.unpackedItemMeta(_S().itemMeta[item]);
  }

  function augmentInfo(address item) internal view returns (address token, uint amount) {
    return PackingLib.unpackAddressWithAmount(_S().augmentInfo[item]);
  }

  function genAttributeInfo(address item) internal view returns (IItemController.ItemGenerateInfo memory info) {
    return ItemLib.unpackItemGenerateInfo(_S().generateInfoAttributes[item]);
  }

  function genCasterAttributeInfo(address item) internal view returns (IItemController.ItemGenerateInfo memory info) {
    return ItemLib.unpackItemGenerateInfo(_S().generateInfoCasterAttributes[item]);
  }

  function genTargetAttributeInfo(address item) internal view returns (IItemController.ItemGenerateInfo memory info) {
    return ItemLib.unpackItemGenerateInfo(_S().generateInfoTargetAttributes[item]);
  }

  function genAttackInfo(address item) internal view returns (IItemController.AttackInfo memory info) {
    return ItemLib.unpackItemAttackInfo(_S().generateInfoAttack[item]);
  }

  function itemInfo(address item, uint itemId) internal view returns (IItemController.ItemInfo memory info) {
    return ItemLib.unpackedItemInfo(_S().itemInfo[PackingLib.packNftId(item, itemId)]);
  }

  function equippedOn(address item, uint itemId) internal view returns (address hero, uint heroId) {
    return PackingLib.unpackNftId(_S().equippedOn[item.packNftId(itemId)]);
  }

  function itemAttributes(address item, uint itemId) internal view returns (int32[] memory values, uint8[] memory ids) {
    return PackingLib.toInt32ArrayWithIds(_S()._itemAttributes[PackingLib.packNftId(item, itemId)]);
  }

  function consumableAttributes(address item) internal view returns (int32[] memory values, uint8[] memory ids) {
    return PackingLib.toInt32ArrayWithIds(_S()._itemConsumableAttributes[item]);
  }

  function consumableStats(address item) internal view returns (IStatController.ChangeableStats memory stats) {
    return StatLib.unpackChangeableStats(_S().itemConsumableStats[item]);
  }

  function casterAttributes(address item, uint itemId) internal view returns (int32[] memory values, uint8[] memory ids) {
    return PackingLib.toInt32ArrayWithIds(_S()._itemCasterAttributes[PackingLib.packNftId(item, itemId)]);
  }

  function targetAttributes(address item, uint itemId) internal view returns (int32[] memory values, uint8[] memory ids) {
    return PackingLib.toInt32ArrayWithIds(_S()._itemTargetAttributes[PackingLib.packNftId(item, itemId)]);
  }

  function itemAttackInfo(address item, uint itemId) internal view returns (IItemController.AttackInfo memory info) {
    return ItemLib.unpackItemAttackInfo(_S()._itemAttackInfo[PackingLib.packNftId(item, itemId)]);
  }

  function score(address item, uint itemId) external view returns (uint) {
    return ScoreLib.itemScore(
      StatLib.bytesToFullAttributesArray(_S()._itemAttributes[PackingLib.packNftId(item, itemId)]),
      ItemLib.unpackedItemMeta(_S().itemMeta[item]).baseDurability
    );
  }

  function isAllowedToTransfer(address item, uint itemId) internal view returns (bool) {
    return _S().equippedOn[item.packNftId(itemId)] == bytes32(0);
  }

  function consumableActionMask(address item) internal view returns (uint) {
    return _S()._consumableActionMask[item];
  }

  /// @notice SIP-003: item fragility counter that displays the chance of an unsuccessful repair.
  /// @dev [0...100%], decimals 3, so the value is in the range [0...10_000]
  function itemFragility(address item, uint itemId) internal view returns (uint) {
    return _S().itemFragility[item.packNftId(itemId)];
  }

  /// @notice SCB-1014: packed metadata for the item of type "Other"
  /// Use {PackingLib.unpackOtherXXX} routines to unpack data.
  /// The proper routine depends on subtype kind, use {PackingLib.getOtherItemTypeKind} to extract it.
  function packedItemMetaData(address item) internal view returns (bytes memory) {
    return _S().packedItemMetaData[item];
  }

  function itemControllerHelper() internal view returns (address) {
    return address(uint160(_S().globalParam[IItemController.GlobalParam.ITEM_CONTROLLER_HELPER_ADDRESS_1]));
  }

  function isItemEquipped(IItemController.MainState storage s, address item, uint itemId) internal view returns (bool) {
    return s.equippedOn[item.packNftId(itemId)] != bytes32(0);
  }
  //endregion ------------------------ Views

  //region ------------------------ Deployer actions
  function setItemControllerHelper(IController controller, address helper_) internal {
    onlyDeployer(controller, msg.sender);
    if (itemControllerHelper() != address(0)) revert IAppErrors.AlreadyInitialized();

    _S().globalParam[IItemController.GlobalParam.ITEM_CONTROLLER_HELPER_ADDRESS_1] = uint(uint160(helper_));
    emit IApplicationEvents.ItemControllerHelper(helper_);
  }

  //endregion ------------------------ Deployer actions

  //region ------------------------ Controllers actions
  function mintNewItem(
    IController controller,
    address sender,
    address item,
    address recipient
  ) internal returns (uint itemId) {
    return ItemLib.mintNewItem(_S(), controller, sender, item, recipient);
  }

  /// @notice Reduce durability of all equipped items except not-used items of SKILL-type.
  /// Used skills are stored in skillSlotsForDurabilityReduction
  function reduceEquippedItemsDurability(
    IController controller,
    address hero,
    uint heroId,
    uint8 biome,
    bool reduceDurabilityAllSkills
  ) external {
    ReduceDurabilityContext memory ctx;
    ctx.dungeonFactory = _onlyRegisteredControllers(controller);

    IItemController.MainState storage s = _S();

    if (!reduceDurabilityAllSkills) {
      // reduce durability of skill-slots only if they are marked for slot-durability-reduction
      ctx.skillSlots = IDungeonFactory(ctx.dungeonFactory).skillSlotsForDurabilityReduction(hero, heroId);
    }
    ctx.statController = IStatController(controller.statController());
    ctx.busySlots = ctx.statController.heroItemSlots(hero, heroId);

    for (uint i; i < ctx.busySlots.length; ++i) {
      if (!reduceDurabilityAllSkills) {
        if (
          (ctx.busySlots[i] == uint8(IStatController.ItemSlots.SKILL_1) && ctx.skillSlots[0] == 0)
          || (ctx.busySlots[i] == uint8(IStatController.ItemSlots.SKILL_2) && ctx.skillSlots[1] == 0)
          || (ctx.busySlots[i] == uint8(IStatController.ItemSlots.SKILL_3) && ctx.skillSlots[2] == 0)
        ) {
          continue;
        }
      }

      (ctx.itemAdr, ctx.itemId) = ctx.statController.heroItemSlot(hero, uint64(heroId), ctx.busySlots[i]).unpackNftId();
      ctx.durability = _reduceDurabilityForItem(s, ctx.itemAdr, ctx.itemId, biome);

      // if broken need to take off
      if (ctx.durability == 0) {
        _takeOff(
          s,
          ctx.statController,
          ctx.itemAdr,
          ctx.itemId,
          hero,
          heroId,
          ctx.busySlots[i],
          IERC721(hero).ownerOf(heroId),
          false
        );
      }

    }
  }

  function destroy(IController controller, address msgSender, address item, uint itemId) external {
    if (
      controller.gameObjectController() != msgSender
      && controller.storyController() != msgSender
      && IERC721(item).ownerOf(itemId) != msgSender
    ) {
      revert IAppErrors.ErrorForbidden(msgSender);
    }

    onlyNotEquippedItem(item, itemId);

    _destroy(item, itemId);
  }

  /// @dev Some stories can manipulate items
  function takeOffDirectly(
    IController controller,
    address item,
    uint itemId,
    address hero,
    uint heroId,
    uint8 itemSlot,
    address destination,
    bool broken
  ) external {
    if (controller.storyController() != msg.sender && controller.heroController() != msg.sender) {
      revert IAppErrors.ErrorForbidden(msg.sender);
    }
    ItemStatsLib._takeOff(_S(), IStatController(controller.statController()), item, itemId, hero, heroId, itemSlot, destination, broken);
  }

  /// @notice SIP-003: The quest mechanic that previously burned the item will increase its fragility by 1%
  function incBrokenItemFragility(IController controller, address item, uint itemId) internal {
    onlyStoryController(controller, msg.sender);
    _addItemFragility(item, itemId, FRAGILITY_BREAK_ITEM_PORTION);
  }
  //endregion ------------------------ Controllers actions

  //region ------------------------ Eoa actions
  function equipMany(
    bool isEoa,
    IController controller,
    address msgSender,
    address heroToken,
    uint heroTokenId,
    address[] calldata items,
    uint[] calldata itemIds,
    uint8[] calldata itemSlots
  ) external {
    EquipLocalContext memory ctx;
    ctx.hc = IHeroController(controller.heroController());

    // only HeroController or EOA
    if (address(ctx.hc) != msgSender) {
      onlyEOA(isEoa);
    }

    IItemController.MainState storage s = _S();
    if (items.length != itemIds.length || items.length != itemSlots.length) revert IAppErrors.LengthsMismatch();

    ctx.statController = IStatController(controller.statController());
    ctx.dungeonFactory = IDungeonFactory(controller.dungeonFactory());
    (ctx.payToken,) = ctx.hc.payTokenInfo(heroToken);
    ctx.heroTokenId = heroTokenId;
    ctx.heroToken = heroToken;

    if (address(ctx.hc) != msgSender) {
      onlyOwner(heroToken, heroTokenId, msgSender);
    }
    _checkHeroAndController(controller, ctx.hc, heroToken, heroTokenId);
    if (ctx.dungeonFactory.currentDungeon(heroToken, heroTokenId) != 0) revert IAppErrors.EquipForbiddenInDungeon();

    if (ctx.payToken == address(0)) revert IAppErrors.ErrorEquipForbidden();

    for (uint i; i < items.length; ++i) {
      // SCB-1021: some slots require uniqueness of item tokens
      IStatController.ItemSlots slot = IStatController.ItemSlots(itemSlots[i]);
      if (slot == IStatController.ItemSlots.RIGHT_RING) {
        _checkItemIsUnique(ctx, items, i, [IStatController.ItemSlots.LEFT_RING, IStatController.ItemSlots.UNKNOWN]);
      } else if (slot == IStatController.ItemSlots.LEFT_RING) {
        _checkItemIsUnique(ctx, items, i, [IStatController.ItemSlots.RIGHT_RING, IStatController.ItemSlots.UNKNOWN]);
      } else if (slot == IStatController.ItemSlots.SKILL_1) {
        _checkItemIsUnique(ctx, items, i, [IStatController.ItemSlots.SKILL_2, IStatController.ItemSlots.SKILL_3]);
      } else if (slot == IStatController.ItemSlots.SKILL_2) {
        _checkItemIsUnique(ctx, items, i, [IStatController.ItemSlots.SKILL_1, IStatController.ItemSlots.SKILL_3]);
      } else if (slot == IStatController.ItemSlots.SKILL_3) {
        _checkItemIsUnique(ctx, items, i, [IStatController.ItemSlots.SKILL_1, IStatController.ItemSlots.SKILL_2]);
      }

      if (address(ctx.hc) != msgSender) {
        onlyOwner(items[i], itemIds[i], msgSender);
        _equip(s, ctx, msgSender, items[i], itemIds[i], itemSlots[i]);
      } else {
        _equip(s, ctx, IERC721(items[i]).ownerOf(itemIds[i]), items[i], itemIds[i], itemSlots[i]);
      }
    }
  }

  function takeOffMany(
    bool isEoa,
    IController controller,
    address msgSender,
    address heroToken,
    uint heroTokenId,
    address[] calldata items,
    uint[] calldata tokenIds,
    uint8[] calldata itemSlots
  ) external {
    onlyEOA(isEoa);

    TakeOffContext memory ctx = ItemStatsLib.TakeOffContext({
      controller: controller,
      msgSender: msgSender,
      heroToken: heroToken,
      heroTokenId: heroTokenId,
      destination: msgSender,
      broken: false,
      heroController: IHeroController(controller.heroController()),
      dungeonFactory: IDungeonFactory(controller.dungeonFactory()),
      statController: IStatController(controller.statController())
    });

    IItemController.MainState storage s = _S();
    uint len = items.length;
    if (len != tokenIds.length || len != itemSlots.length) revert IAppErrors.LengthsMismatch();

    for (uint i; i < len; ++i) {
      _takeOffWithChecks(s, ctx, items[i], tokenIds[i], itemSlots[i]);
    }
  }

  /// @notice Destroy {consumed item} to repair durability of the {item}
  /// There is a chance ~ item fragility that the item won't be repaired.
  function repairDurability(
    bool isEoa,
    IController controller,
    address msgSender,
    address item,
    uint itemId,
    uint consumedItemId
  ) external {
    _repairDurability(isEoa, controller, msgSender, item, itemId, consumedItemId, CalcLib.pseudoRandom);
  }

  /// @notice Destroy {consumed item} to augment given {item}.
  /// There is a chance of 30% that the item will be destroyed instead of augmentation.
  function augment(
    bool isEoa,
    IController controller,
    address msgSender,
    address item,
    uint itemId,
    uint consumedItemId
  ) external {
    // restrictions are checked inside {_prepareToAugment}
    IItemController.MainState storage s = _S();
    (
      IItemController.ItemMeta memory meta,
      IItemController.ItemInfo memory _itemInfo
    ) = _prepareToAugment(isEoa, controller, msgSender, item, itemId, consumedItemId);

    onlyNotConsumable(meta, item);
    if (_itemInfo.augmentationLevel >= MAX_AUGMENTATION_LEVEL) revert IAppErrors.TooHighAgLevel(_itemInfo.augmentationLevel);

    _destroy(item, consumedItemId);

    address augToken = _sendFee(s, controller, item, msgSender);
    // we check augToken for 0 AFTER sendFee to avoid second reading of augmentInfo
    if (augToken == address(0)) revert IAppErrors.ZeroAugmentation();

    if (IOracle(controller.oracle()).getRandomNumber(1e18, 0) < AUGMENT_CHANCE) {
      IItemController.AugmentInfo memory _augmentInfo;
      bytes32 packedItemId = item.packNftId(itemId);

      // augment base
      (_augmentInfo.attributesValues, _augmentInfo.attributesIds) = _augmentAttributes(s._itemAttributes[packedItemId], true);
      s._itemAttributes[packedItemId] = _augmentInfo.attributesValues.toBytes32ArrayWithIds(_augmentInfo.attributesIds);

      // additionally
      if (meta.itemMetaType == uint8(IItemController.ItemMetaType.ATTACK)) {
        _augmentInfo.attackInfo = ItemLib.unpackItemAttackInfo(s._itemAttackInfo[packedItemId]);
        _augmentInfo.attackInfo.min = _augmentAttribute(_augmentInfo.attackInfo.min);
        _augmentInfo.attackInfo.max = _augmentAttribute(_augmentInfo.attackInfo.max);
        s._itemAttackInfo[packedItemId] = ItemLib.packItemAttackInfo(_augmentInfo.attackInfo);
      } else if (meta.itemMetaType == uint8(IItemController.ItemMetaType.BUFF)) {
        // caster
        (_augmentInfo.casterValues, _augmentInfo.casterIds) = _augmentAttributes(s._itemCasterAttributes[packedItemId], true);
        s._itemCasterAttributes[packedItemId] = _augmentInfo.casterValues.toBytes32ArrayWithIds(_augmentInfo.casterIds);

        // target
        (_augmentInfo.targetValues, _augmentInfo.targetIds) = _augmentAttributes(s._itemTargetAttributes[packedItemId], false);
        s._itemTargetAttributes[packedItemId] = _augmentInfo.targetValues.toBytes32ArrayWithIds(_augmentInfo.targetIds);
      }

      // increase aug level
      _itemInfo.augmentationLevel = _itemInfo.augmentationLevel + 1;
      s.itemInfo[packedItemId] = ItemLib.packItemInfo(_itemInfo);

      emit IApplicationEvents.Augmented(item, itemId, consumedItemId, _itemInfo.augmentationLevel, _augmentInfo);
    } else {
      _destroy(item, itemId);
      emit IApplicationEvents.NotAugmented(item, itemId, consumedItemId, _itemInfo.augmentationLevel);
    }
  }

  /// @notice Use consumable
  function use(
    bool isEoa,
    IController controller,
    IStatController statController,
    address msgSender,
    address item,
    uint itemId,
    address heroToken,
    uint heroTokenId
  ) external returns (uint actionMask) {
    onlyEOA(isEoa);
    onlyOwner(item, itemId, msgSender);

    IItemController.MainState storage s = _S();

    IHeroController hc = IHeroController(controller.heroController());

    (address payToken,) = hc.payTokenInfo(heroToken);
    if (payToken == address(0)) revert IAppErrors.UseForbiddenZeroPayToken();

    onlyOwner(heroToken, heroTokenId, msgSender);
    _checkHeroAndController(controller, hc, heroToken, heroTokenId);

    {
      IItemController.ItemMeta memory meta = ItemLib.unpackedItemMeta(s.itemMeta[item]);
      if (uint8(meta.itemType) != 0) revert IAppErrors.NotConsumable(item);
      _checkRequirements(statController, heroToken, heroTokenId, meta.requirements);

      IStatController.ChangeableStats memory change = StatLib.unpackChangeableStats(s.itemConsumableStats[item]);
      // allow to use multiple times items with experience/lc
      if(change.experience == 0 && change.lifeChances == 0) {
        statController.registerConsumableUsage(heroToken, heroTokenId, item);
      }
      statController.changeCurrentStats(heroToken, heroTokenId, change, true);
    }

    {
      bytes32[] memory itemConsumableAttributes = s._itemConsumableAttributes[item];
      if (itemConsumableAttributes.length != 0) {
        int32[] memory attributes = StatLib.bytesToFullAttributesArray(itemConsumableAttributes);
        statController.changeBonusAttributes(IStatController.ChangeAttributesInfo({
          heroToken: heroToken,
          heroTokenId: heroTokenId,
          changeAttributes: attributes,
          add: true,
          temporally: true
        }));
      }
    }

    actionMask = s._consumableActionMask[item];

    _destroy(item, itemId);
    emit IApplicationEvents.Used(item, itemId, heroToken, heroTokenId);
  }

  function combineItems(
    bool isEoa,
    IController controller,
    address msgSender,
    uint configId,
    address[] memory items,
    uint[][] memory itemIds
  ) internal returns (uint itemId) {
    onlyEOA(isEoa);

    address helper = ItemStatsLib.itemControllerHelper();
    if (helper == address(0)) revert IAppErrors.NotInitialized();

    // validate that {items} and {itemIds} fit to the selected config
    address itemToMint = IItemControllerHelper(helper).prepareToCombine(msgSender, configId, items, itemIds);

    // destroy provided items
    uint lenItems = items.length;
    for (uint i; i < lenItems; ++i) {
      uint[] memory ids = itemIds[i];
      uint len = ids.length;
      for (uint j; j < len; ++j) {
        _destroy(items[i], ids[j]);
      }
    }

    // mint a new item in exchange of destroyed items
    itemId = ItemLib.mintNewItem(_S(), controller, address(this), itemToMint, msgSender);

    emit IApplicationEvents.CombineItems(msgSender, configId, items, itemIds, itemToMint, itemId);
  }

  //endregion ------------------------ Eoa actions

  //region ------------------------ Internal logic - equip and take off

  /// @notice Ensure that 1) {items} has no duplicates of items[index] 2) items[index] is not equipped at {slotsToCheck}
  function _checkItemIsUnique(
    EquipLocalContext memory ctx,
    address[] memory items,
    uint index,
    IStatController.ItemSlots[2] memory slotsToCheck
  ) internal view {
    if (!ctx.equippedSlotsLoaded) {
      ctx.equippedSlots = ctx.statController.heroItemSlots(ctx.heroToken, ctx.heroTokenId);
      ctx.equippedSlotsLoaded = true;
    }

    // there are no duplicates of the item in {items}
    // we don't check slots - assume that if item is being equipped it's equipped to the slot under consideration
    uint len = items.length;
    for (uint i; i < len; ++i) {
      if (i == index) continue;
      if (items[i] == items[index]) revert IAppErrors.DoubleItemUsageForbidden(index, items);
    }

    // ensure that the item is not yet equipped
    len = ctx.equippedSlots.length;
    for (uint i; i < len; ++i) {
      if (uint8(slotsToCheck[0]) == ctx.equippedSlots[i] || uint8(slotsToCheck[1]) == ctx.equippedSlots[i]) {
        (address item,) = PackingLib.unpackNftId(ctx.statController.heroItemSlot(ctx.heroToken, uint64(ctx.heroTokenId), ctx.equippedSlots[i]));
        if (item == items[index]) revert IAppErrors.ItemAlreadyUsedInSlot(item, uint8(ctx.equippedSlots[i]));
      }
    }
  }

  /// @notice Equip the item, add bonus attributes, transfer the item from the sender to the hero token
  function _equip(
    IItemController.MainState storage s,
    EquipLocalContext memory c,
    address msgSender,
    address item,
    uint itemId,
    uint8 itemSlot
  ) internal {
    IItemController.ItemMeta memory meta = ItemLib.unpackedItemMeta(s.itemMeta[item]);
    IItemController.ItemInfo memory _itemInfo = ItemLib.unpackedItemInfo(s.itemInfo[item.packNftId(itemId)]);

    if (meta.itemMetaType == 0) revert IAppErrors.UnknownItem(item);
    onlyNotEquippedItem(item, itemId);
    onlyNotConsumable(meta, item);

    if (meta.baseDurability != 0 && _itemInfo.durability == 0) revert IAppErrors.Broken(item);
    _checkRequirements(c.statController, c.heroToken, c.heroTokenId, meta.requirements);

    c.statController.changeHeroItemSlot(
      c.heroToken,
      uint64(c.heroTokenId),
      uint(meta.itemType),
      itemSlot,
      item,
      itemId,
      true
    );

    bytes32[] memory attributes = s._itemAttributes[item.packNftId(itemId)];
    if (attributes.length != 0) {
      c.statController.changeBonusAttributes(IStatController.ChangeAttributesInfo({
        heroToken: c.heroToken,
        heroTokenId: c.heroTokenId,
        changeAttributes: StatLib.bytesToFullAttributesArray(attributes),
        add: true,
        temporally: false
      }));

      // some items can reduce hero life to zero, prevent this
      if (c.statController.heroStats(c.heroToken, c.heroTokenId).life == 0) revert IAppErrors.ZeroLife();
    }

    // transfer item to hero
    IItem(item).controlledTransfer(msgSender, c.heroToken, itemId);
    // need to equip after transfer for properly checks
    s.equippedOn[item.packNftId(itemId)] = c.heroToken.packNftId(c.heroTokenId);

    emit IApplicationEvents.Equipped(item, itemId, c.heroToken, c.heroTokenId, itemSlot);
  }

  function _checkRequirements(
    IStatController statController,
    address heroToken,
    uint heroTokenId,
    IStatController.CoreAttributes memory requirements
  ) internal view {
    IStatController.CoreAttributes memory attributes = statController.heroBaseAttributes(heroToken, heroTokenId);
    if (
      requirements.strength > attributes.strength
      || requirements.dexterity > attributes.dexterity
      || requirements.vitality > attributes.vitality
      || requirements.energy > attributes.energy
    ) revert IAppErrors.RequirementsToItemAttributes();
  }

  /// @notice Check requirements for the hero and for the controller state before equip/take off/use items
  function _checkHeroAndController(IController controller, IHeroController heroController, address heroToken, uint heroTokenId) internal view {
    if (IReinforcementController(controller.reinforcementController()).isStaked(heroToken, heroTokenId)) revert IAppErrors.Staked(heroToken, heroTokenId);
    if (controller.onPause()) revert IAppErrors.ErrorPaused();
    if (heroController.heroClass(heroToken) == 0) revert IAppErrors.ErrorHeroIsNotRegistered(heroToken);
  }

  function _takeOffWithChecks(
    IItemController.MainState storage s,
    TakeOffContext memory ctx,
    address item,
    uint itemId,
    uint8 itemSlot
  ) internal {
    onlyOwner(ctx.heroToken, ctx.heroTokenId, ctx.msgSender);
    _checkHeroAndController(ctx.controller,
      ctx.heroController,
      ctx.heroToken,
      ctx.heroTokenId
    );
    if (ctx.dungeonFactory.currentDungeon(ctx.heroToken, ctx.heroTokenId) != 0) revert IAppErrors.TakeOffForbiddenInDungeon();

    if (s.equippedOn[item.packNftId(itemId)] != ctx.heroToken.packNftId(ctx.heroTokenId)) revert IAppErrors.NotEquipped(item);

    _takeOff(s, ctx.statController, item, itemId, ctx.heroToken, ctx.heroTokenId, itemSlot, ctx.destination, ctx.broken);
  }

  /// @notice Take off the item, remove bonus attributes, transfer the item from the hero token to {destination}
  /// @param broken True if the item is broken. The durability of the broken item will be set to 0.
  function _takeOff(
    IItemController.MainState storage s,
    IStatController statController,
    address item,
    uint itemId,
    address heroToken,
    uint heroTokenId,
    uint8 itemSlot,
    address destination,
    bool broken
  ) internal {
    bytes32 packedItemId = item.packNftId(itemId);
    IItemController.ItemMeta memory meta = ItemLib.unpackedItemMeta(s.itemMeta[item]);
    IItemController.ItemInfo memory _itemInfo = ItemLib.unpackedItemInfo(s.itemInfo[packedItemId]);

    onlyNotConsumable(meta, item);

    statController.changeHeroItemSlot(
      heroToken,
      uint64(heroTokenId),
      uint(meta.itemType),
      itemSlot,
      item,
      itemId,
      false
    );

    if (broken) {
      _itemInfo.durability = 0;
      s.itemInfo[packedItemId] = ItemLib.packItemInfo(_itemInfo);
    }

    bytes32[] memory attributes = s._itemAttributes[packedItemId];
    if (attributes.length != 0) {
      statController.changeBonusAttributes(IStatController.ChangeAttributesInfo({
        heroToken: heroToken,
        heroTokenId: heroTokenId,
        changeAttributes: StatLib.bytesToFullAttributesArray(attributes),
        add: false,
        temporally: false
      }));
    }

    // need to take off before transfer for properly checks
    s.equippedOn[packedItemId] = bytes32(0);
    IItem(item).controlledTransfer(heroToken, destination, itemId);

    emit IApplicationEvents.TakenOff(item, itemId, heroToken, heroTokenId, itemSlot, destination);
  }
  //endregion ------------------------ Internal logic - equip and take off

  //region ------------------------ Internal logic - augment, repair

  /// @notice Initialization for augment() and repairDurability()
  /// Get {meta} and {info}, check some restrictions
  function _prepareToAugment(
    bool isEoa,
    IController controller,
    address msgSender,
    address item,
    uint itemId,
    uint consumedItemId
  ) internal view returns(
    IItemController.ItemMeta memory meta,
    IItemController.ItemInfo memory info
  ) {
    _checkPauseEoaOwner(isEoa, controller, msgSender, item, itemId);
    onlyOwner(item, consumedItemId, msgSender);

    if (itemId == consumedItemId) revert IAppErrors.SameIdsNotAllowed();
    meta = ItemLib.unpackedItemMeta(_S().itemMeta[item]);
    info = ItemLib.unpackedItemInfo(_S().itemInfo[item.packNftId(itemId)]);

    onlyNotEquippedItem(item, itemId);
    onlyNotEquippedItem(item, consumedItemId);
  }

  /// @notice Destroy {consumed item} to repair durability of the {item}
  /// There is a chance ~ item fragility that the item won't be repaired.
  /// @param random_ Pass _pseudoRandom here, param is required to simplify unit testing
  function _repairDurability(
    bool isEoa,
    IController controller,
    address msgSender,
    address item,
    uint itemId,
    uint consumedItemId,
    function (uint) internal view returns (uint) random_
  ) internal {
    // restrictions are checked inside {_prepareToAugment}
    IItemController.MainState storage s = _S();
    (
      IItemController.ItemMeta memory meta,
      IItemController.ItemInfo memory _itemInfo
    ) = _prepareToAugment(isEoa, controller, msgSender, item, itemId, consumedItemId);

    if (meta.baseDurability == 0) revert IAppErrors.ZeroDurability();

    _destroy(item, consumedItemId);
    _sendFee(s, controller, item, msgSender);

    // SIP-003: There is a chance of unsuccessful repair ~ to the item fragility
    uint fragility = itemFragility(item, itemId);
    bool success = fragility == 0 || random_(MAX_FRAGILITY - 1) > fragility;
    // Each successful repair has a 10% chance of increasing the item's fragility by 1%.
    bool incFragility = random_(100) < FRAGILITY_SUCCESSFUL_REPAIR_CHANCE;

    _itemInfo.durability = success
      ? meta.baseDurability // the item is repaired successfully
      : _itemInfo.durability;

    // try to hide gas difference between successful and failed cases
    _addItemFragility(item, itemId, success && incFragility ? FRAGILITY_SUCCESSFUL_REPAIR_PORTION : 0); // item fragility is increased

    s.itemInfo[item.packNftId(itemId)] = ItemLib.packItemInfo(_itemInfo);

    if (success) {
      emit IApplicationEvents.ItemRepaired(item, itemId, consumedItemId, meta.baseDurability);
    } else {
      emit IApplicationEvents.FailedToRepairItem(item, itemId, consumedItemId, _itemInfo.durability);
    }
  }

  /// @notice Modify either positive or negative values
  /// @param ignoreNegative True - leave unchanged all negative values, False - don't change all positive values
  function _augmentAttributes(bytes32[] memory packedAttr, bool ignoreNegative) internal pure returns (
    int32[] memory values,
    uint8[] memory ids
  ) {
    (values, ids) = packedAttr.toInt32ArrayWithIds();
    for (uint i; i < values.length; ++i) {
      // do not increase destroy item attribute
      if(uint(ids[i]) == uint(IStatController.ATTRIBUTES.DESTROY_ITEMS)) {
        continue;
      }
      if ((ignoreNegative && values[i] > 0) || (!ignoreNegative && values[i] < 0)) {
        values[i] = _augmentAttribute(values[i]);
      }
    }
  }

  /// @notice Increase/decrease positive/negative value on ceil(value/20) but at least on 1
  function _augmentAttribute(int32 value) internal pure returns (int32) {
    if (value == 0) {
      return 0;
    }
    // bonus must be not lower than 1
    if (value > 0) {
      return value + int32(int(Math.max(Math.ceilDiv(value.toUint(), AUGMENT_FACTOR), 1)));
    } else {
      return value - int32(int(Math.max(Math.ceilDiv((- value).toUint(), AUGMENT_FACTOR), 1)));
    }
  }
  //endregion ------------------------ Internal logic - augment, repair

  //region ------------------------ Internal logic - durability, destroy, fee, fragility

  /// @notice newDurability Calculate new durability for the {item}, update {itemInfo}
  function _reduceDurabilityForItem(
    IItemController.MainState storage s,
    address item,
    uint itemId,
    uint biome
  ) internal returns (uint16 newDurability) {
    IItemController.ItemMeta memory meta = ItemLib.unpackedItemMeta(s.itemMeta[item]);
    IItemController.ItemInfo memory _itemInfo = ItemLib.unpackedItemInfo(s.itemInfo[item.packNftId(itemId)]);

    newDurability = uint16(_calcReduceDurability(biome, _itemInfo.durability, meta.itemLevel, meta.itemType));

    _itemInfo.durability = newDurability;
    _itemInfo.durability = newDurability;
    s.itemInfo[item.packNftId(itemId)] = ItemLib.packItemInfo(_itemInfo);

    emit IApplicationEvents.ReduceDurability(item, itemId, newDurability);
  }

  /// @return New (reduced) value for the current durability
  function _calcReduceDurability(
    uint biome,
    uint currentDurability,
    uint8 itemLevel,
    IItemController.ItemType itemType
  ) internal pure returns (uint) {
    uint value = DURABILITY_REDUCTION;

    if (itemType != IItemController.ItemType.SKILL) {
      uint itemBiomeLevel = uint(itemLevel) / StatLib.BIOME_LEVEL_STEP + 1;
      if (itemBiomeLevel < biome) {
        value = DURABILITY_REDUCTION * ((biome - itemBiomeLevel + 1) ** 2 / 2);
      }
    }

    return currentDurability > value
      ? currentDurability - value
      : 0;
  }

  function _destroy(address item, uint itemId) internal {
    IItem(item).burn(itemId);
    emit IApplicationEvents.Destroyed(item, itemId);
  }

  /// @return augToken Return augToken to avoid repeat reading of augmentInfo inside augment()
  function _sendFee(
    IItemController.MainState storage s,
    IController controller,
    address item,
    address msgSender
  ) internal returns (address augToken) {
    (address token, uint amount) = s.augmentInfo[item].unpackAddressWithAmount();
    if (token != address(0)) {
      controller.process(token, amount, msgSender);
    }
    return token;
  }

  function _addItemFragility(address item, uint itemId, uint portion) internal {
    uint fragility = _S().itemFragility[item.packNftId(itemId)];
    _S().itemFragility[item.packNftId(itemId)] = fragility + portion > MAX_FRAGILITY
      ? MAX_FRAGILITY
      : fragility + portion;
  }
  //endregion ------------------------ Internal logic - durability, destroy, fee, fragility
}

File 34 of 48 : OtherItemLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../interfaces/IAppErrors.sol";
import "../interfaces/IHeroController.sol";
import "../interfaces/IItemController.sol";
import "./ItemLib.sol";
import "./PackingLib.sol";

/// @notice Implement all variants of other-items
library OtherItemLib {
  //region ------------------------ Restrictions
  function onlyNotEquippedItem(IItemController.MainState storage s, address item, uint itemId) internal view {
    if (s.equippedOn[PackingLib.packNftId(item, itemId)] != bytes32(0)) revert IAppErrors.ItemEquipped(item, itemId);
  }

  function onlyOwner(address token, uint tokenId, address sender) internal view {
    if (IERC721(token).ownerOf(tokenId) != sender) revert IAppErrors.ErrorNotOwner(token, tokenId);
  }

  //endregion ------------------------ Restrictions

  //region ------------------------ Main logic
  /// @notice Apply given other item
  /// @param data Data required by other item, encoded by abi.encode
  /// Format of the data depends on the other-item-sub-kind
  /// REDUCE_FRAGILITY_1: (item, itemId)
  ///     item - the item which fragility should be reduced
  /// USE_GUILD_REINFORCEMENT_2: (hero, heroId, helper, helperId)
  ///     hero - the hero that asks the guild reinforcement
  ///     helper - the hero staked in guild reinforcement which help is desired
  /// EXIT_FROM_DUNGEON_3: (hero, heroId)
  ///     hero - the hero that is going to exit from the dungeon
  /// REST_IN_SHELTER_4: (hero, heroId)
  ///     hero - the hero that is going to have a rest in the shelter of the guild to which the hero's owner belongs
  /// @param expectedKind Not 0 means that we expects that the {otherItem} should have such subtype kind. Can be 0.
  function useOtherItem(
    IItemController.MainState storage s,
    IController controller,
    address msgSender,
    address otherItem,
    uint otherItemId,
    bytes memory data,
    IItemController.OtherSubtypeKind expectedKind
  ) external {
    // get kind of the other-item
    IItemController.ItemMeta memory meta = ItemLib.unpackedItemMeta(s.itemMeta[otherItem]);
    if (meta.itemType != IItemController.ItemType.OTHER) revert IAppErrors.NotOther();
    bytes memory packedMetaData = s.packedItemMetaData[otherItem];
    IItemController.OtherSubtypeKind kind = PackingLib.getOtherItemTypeKind(packedMetaData);

    // ensure that the other item has expected kind
    if (expectedKind != IItemController.OtherSubtypeKind.UNKNOWN_0) {
      if (kind != expectedKind) revert IAppErrors.UnexpectedOtherItem(otherItem);
    }

    // make action assigned to the other-item
    if (kind == IItemController.OtherSubtypeKind.REDUCE_FRAGILITY_1) {
      (address item, uint itemId) = abi.decode(data, (address, uint));
      _repairFragility(s, msgSender, item, itemId, otherItem, otherItemId, packedMetaData);
    } else if (kind == IItemController.OtherSubtypeKind.USE_GUILD_REINFORCEMENT_2) {
      (address heroToken, uint heroTokenId, address helper, uint helperId) = abi.decode(data, (address, uint, address, uint));
      _askGuildReinforcement(controller, msgSender, otherItem, otherItemId, heroToken, heroTokenId, helper, helperId);
    } else if (kind == IItemController.OtherSubtypeKind.EXIT_FROM_DUNGEON_3) {
      (address heroToken, uint heroTokenId) = abi.decode(data, (address, uint));
      _actionExitFromDungeon(controller, msgSender, heroToken, heroTokenId);
    } else if (kind == IItemController.OtherSubtypeKind.REST_IN_SHELTER_4) {
      (address heroToken, uint heroTokenId) = abi.decode(data, (address, uint));
      _actionRestInShelter(controller, msgSender, heroToken, heroTokenId);
    } else {
      revert IAppErrors.UnexpectedOtherItem(otherItem);
    }

  }
  //endregion ------------------------ Main logic

  //region ------------------------ Other items logic

  /// @notice Call guild reinforcement
  /// @param item An other-item with subtype "USE_GUILD_REINFORCEMENT_2"
  /// @param msgSender Owner of the {heroTokenId}
  /// @param heroToken Hero which asks helper
  /// @param helper The hero staked in the guild reinforcement which help is being asked
  function _askGuildReinforcement(
    IController controller,
    address msgSender,
    address item,
    uint itemId,
    address heroToken,
    uint heroTokenId,
    address helper,
    uint helperId
  ) internal {
    onlyOwner(heroToken, heroTokenId, msgSender);
    IHeroController hc = IHeroController(controller.heroController());
    hc.askGuildReinforcement(heroToken, heroTokenId, helper, helperId);

    emit IApplicationEvents.OtherItemGuildReinforcement(item, itemId, heroToken, heroTokenId, helper, helperId);
  }

  /// @notice Reduce fragility of the {item} on the value taken from the metadata of the {consumedItem}.
  /// Destroy the consumed item.
  /// New fragility = initial fragility - value from metadata.
  /// @param consumedItem Item of type "Other" subtype "REDUCE_FRAGILITY_1"
  function _repairFragility(
    IItemController.MainState storage s,
    address msgSender,
    address item,
    uint itemId,
    address consumedItem,
    uint consumedItemId,
    bytes memory packedMetaData
  ) internal {
    onlyOwner(item, itemId, msgSender);

    if (item == consumedItem) revert IAppErrors.OtherTypeItemNotRepairable();
    onlyNotEquippedItem(s, item, itemId);
    // assume here that item of "Other" type cannot be equipped, so no need to call onlyNotEquippedItem(consumedItemId)

    uint delta = PackingLib.unpackOtherItemReduceFragility(packedMetaData);

    bytes32 packedItem = PackingLib.packNftId(item, itemId);
    uint fragility = s.itemFragility[packedItem];
    if (fragility == 0) revert IAppErrors.ZeroFragility();

    s.itemFragility[packedItem] = fragility > delta
      ? fragility - delta
      : 0;

    emit IApplicationEvents.FragilityReduced(item, itemId, consumedItem, consumedItemId, fragility);
  }

  /// @notice Exit from the dungeon: same to the death without reducing life chance
  function _actionExitFromDungeon(IController controller, address msgSender, address heroToken, uint heroTokenId) internal {
    ItemLib._onlyMemberOfGuildWithShelterMaxLevel(controller, msgSender);

    // exit from the dungeon ~ "soft kill"
    IDungeonFactory(controller.dungeonFactory()).exitForcibly(heroToken, heroTokenId, msgSender);
  }

  /// @notice Rest in the shelter of 3d level: restore of hp & mp, clear temporally attributes, clear used consumables
  function _actionRestInShelter(
    IController controller,
    address msgSender,
    address heroToken,
    uint heroTokenId
  ) internal {
    ItemLib._onlyMemberOfGuildWithShelterMaxLevel(controller, msgSender);
    IStatController statController = IStatController(controller.statController());

    // restore life and mana to default values from the total attributes
    statController.restoreLifeAndMana(heroToken, heroTokenId, statController.heroAttributes(heroToken, heroTokenId));

    statController.clearTemporallyAttributes(heroToken, heroTokenId);
    statController.clearUsedConsumables(heroToken, heroTokenId);

    emit IApplicationEvents.RestInShelter(msgSender, heroToken, heroTokenId);
  }
  //endregion ------------------------ Other items logic
}

File 35 of 48 : PackingLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../interfaces/IItemController.sol";
import "../interfaces/IStatController.sol";
import "../interfaces/IAppErrors.sol";

library PackingLib {

  //////////////////////////
  // ---- PACKING LOGIC ----
  //////////////////////////

  //region ------------------------------------ COMMON

  function packNftId(address token, uint id) internal pure returns (bytes32 serialized) {
    if (id > uint(type(uint64).max)) revert IAppErrors.TooHighValue(id);
    serialized = bytes32(uint(uint160(token)));
    serialized |= bytes32(uint(uint64(id))) << 160;
  }

  function unpackNftId(bytes32 data) internal pure returns (address token, uint id) {
    token = address(uint160(uint(data)));
    id = uint(data) >> 160;
  }

  function packAddressWithAmount(address token, uint amount) internal pure returns (bytes32 data) {
    if (amount > uint(type(uint96).max)) revert IAppErrors.TooHighValue(amount);
    data = bytes32(uint(uint160(token)));
    data |= bytes32(uint(uint96(amount))) << 160;
  }

  function unpackAddressWithAmount(bytes32 data) internal pure returns (address token, uint amount) {
    token = address(uint160(uint(data)));
    amount = uint(data) >> 160;
  }

  function packItemMintInfo(address item, uint32 chance) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(item)));
    data |= bytes32(uint(chance)) << 160;
  }

  function unpackItemMintInfo(bytes32 data) internal pure returns (address item, uint32 chance) {
    item = address(uint160(uint(data)));
    chance = uint32(uint(data) >> 160);
  }

  /// @param customDataIndex We assume, that two lowest bytes of this string are always zero
  /// So, the string looks like following: 0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0000
  /// Last 2 bytes will be used to encode {value}
  function packCustomDataChange(bytes32 customDataIndex, int16 value) internal pure returns (bytes32 data) {
    if (uint(customDataIndex) != (uint(customDataIndex) >> 16) << 16) revert IAppErrors.IncompatibleInputString();
    data = bytes32(uint(customDataIndex));
    data |= bytes32(uint(uint16(value)));
  }

  function unpackCustomDataChange(bytes32 data) internal pure returns (bytes32 customDataIndex, int16 value) {
    customDataIndex = bytes32((uint(data) >> 16) << 16);
    value = int16(int(uint(uint16(uint(data)))));
  }

  /// @dev min(uint64) + max(uint64) + isHeroData/isMandatory(uint8)
  function packCustomDataRequirements(uint64 min, uint64 max, bool key) internal pure returns (bytes32 data) {
    data = bytes32(uint(min));
    data |= bytes32(uint(max)) << 64;
    data |= bytes32(uint(key ? uint8(1) : uint8(0))) << (64 + 64);
  }

  function unpackCustomDataRequirements(bytes32 data) internal pure returns (uint64 min, uint64 max, bool key) {
    min = uint64(uint(data));
    max = uint64(uint(data) >> 64);
    key = uint8(uint(data) >> (64 + 64)) == uint8(1);
  }

  function packStatsChange(
    uint32 experience,
    int32 heal,
    int32 manaRegen,
    int32 lifeChancesRecovered,
    int32 damage,
    int32 manaConsumed
  ) internal pure returns (bytes32 data) {
    data = bytes32(uint(experience));
    data |= bytes32(uint(uint32(heal))) << 32;
    data |= bytes32(uint(uint32(manaRegen))) << (32 + 32);
    data |= bytes32(uint(uint32(lifeChancesRecovered))) << (32 + 32 + 32);
    data |= bytes32(uint(uint32(damage))) << (32 + 32 + 32 + 32);
    data |= bytes32(uint(uint32(manaConsumed))) << (32 + 32 + 32 + 32 + 32);
  }

  function unpackStatsChange(bytes32 data) internal pure returns (
    uint32 experience,
    int32 heal,
    int32 manaRegen,
    int32 lifeChancesRecovered,
    int32 damage,
    int32 manaConsumed
  ) {
    experience = uint32(uint(data));
    heal = int32(int(uint(data) >> 32));
    manaRegen = int32(int(uint(data) >> (32 + 32)));
    lifeChancesRecovered = int32(int(uint(data) >> (32 + 32 + 32)));
    damage = int32(int(uint(data) >> (32 + 32 + 32 + 32)));
    manaConsumed = int32(int(uint(data) >> (32 + 32 + 32 + 32 + 32)));
  }

  function packNftIdWithValue(address token, uint id, uint32 value) internal pure returns (bytes32 serialized) {
    if (id > uint(type(uint64).max)) revert IAppErrors.TooHighValue(id);
    serialized = bytes32(uint(uint160(token)));
    serialized |= bytes32(uint(uint64(id))) << 160;
    serialized |= bytes32(uint(value)) << 160 + 64;
  }

  function unpackNftIdWithValue(bytes32 data) internal pure returns (address token, uint id, uint32 value) {
    token = address(uint160(uint(data)));
    id = uint64(uint(data) >> 160);
    value = uint32(uint(data) >> 160 + 64);
  }
  //endregion ------------------------------------ COMMON

  //region ------------------------------------ WORLD/BATTLEFIELD MAP

  function packMapObject(address objectAddress, uint64 objectId, uint8 objectType) internal pure returns (bytes32 packedData) {
    packedData = bytes32(bytes20(objectAddress));
    packedData |= bytes32(uint(objectId) << 32);
    packedData |= bytes32(uint(objectType) << 24);
  }

  function unpackMapObject(bytes32 packedData) internal pure returns (address objectAddress, uint64 objectId, uint8 objectType) {
    objectAddress = address(bytes20(packedData));
    objectId = uint64(uint(packedData) >> 32);
    objectType = uint8(uint(packedData) >> 24);
  }

  function packCoordinate(uint128 x, uint128 y) internal pure returns (bytes32 packedData) {
    packedData = bytes32(uint(x));
    packedData |= bytes32(uint(y) << 128);
  }

  function unpackCoordinate(bytes32 packedData) internal pure returns (uint128 x, uint128 y) {
    x = uint128(uint(packedData));
    y = uint128(uint(packedData) >> 128);
  }

  /// @param x Assume x <= max uint64
  /// @param y Assume y <= max uint64
  function packBattlefieldId(uint8 biomeMapFieldId, uint8 territoryNumber, uint128 x, uint128 y) internal pure returns (bytes32 packedData) {
    // 256 => 128 + 128;
    // 1) 128 is used for biomeMapFieldId, territoryNumber and probably other fields in the future
    // 2) 128 is used to store x, y as uint64, uint64

    // we will use uint64 for coordinates assuming it is more than enough for biome map
    packedData = bytes32(uint(biomeMapFieldId));
    packedData |= bytes32(uint(territoryNumber) << (8));
    packedData |= bytes32(uint(uint64(x)) << 128);
    packedData |= bytes32(uint(uint64(y)) << (64 + 128));
  }

  function unpackBattlefieldId(bytes32 packedData) internal pure returns (uint8 biomeMapFieldId, uint8 territoryNumber, uint128 x, uint128 y) {
    biomeMapFieldId = uint8(uint(packedData));
    territoryNumber = uint8(uint(packedData) >> (8));
    x = uint128(uint64(uint(packedData) >> (128)));
    y = uint128(uint64(uint(packedData) >> (64 + 128)));
  }
  //endregion ------------------------------------ WORLD/BATTLEFIELD MAP

  //region ------------------------------------ REINFORCEMENT

  function packReinforcementHeroInfo(uint8 biome, uint128 score, uint8 fee, uint64 stakeTs) internal pure returns (bytes32 packedData) {
    packedData = bytes32(uint(biome));
    packedData |= bytes32(uint(score) << 8);
    packedData |= bytes32(uint(fee) << (8 + 128));
    packedData |= bytes32(uint(stakeTs) << (8 + 128 + 8));
  }

  function unpackReinforcementHeroInfo(bytes32 packedData) internal pure returns (uint8 biome, uint128 score, uint8 fee, uint64 stakeTs) {
    biome = uint8(uint(packedData));
    score = uint128(uint(packedData) >> 8);
    fee = uint8(uint(packedData) >> (8 + 128));
    stakeTs = uint64(uint(packedData) >> (8 + 128 + 8));
  }

  function packConfigReinforcementV2(uint32 min, uint32 max, uint32 lowDivider, uint32 highDivider, uint8 levelLimit) internal pure returns (bytes32 packedData) {
    packedData = bytes32(uint(min));
    packedData |= bytes32(uint(max) << 32);
    packedData |= bytes32(uint(lowDivider) << 64);
    packedData |= bytes32(uint(highDivider) << 96);
    packedData |= bytes32(uint(levelLimit) << 128);
  }

  function unpackConfigReinforcementV2(bytes32 packedData) internal pure returns (uint32 min, uint32 max, uint32 lowDivider, uint32 highDivider, uint8 levelLimit) {
    min = uint32(uint(packedData));
    max = uint32(uint(packedData) >> 32);
    lowDivider = uint32(uint(packedData) >> 64);
    highDivider = uint32(uint(packedData) >> 96);
    levelLimit = uint8(uint(packedData) >> 128);
  }
  //endregion ------------------------------------ REINFORCEMENT

  //region ------------------------------------ DUNGEON

  function packDungeonKey(address heroAdr, uint80 heroId, uint16 dungLogicNum) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(heroAdr)));
    data |= bytes32(uint(heroId)) << 160;
    data |= bytes32(uint(dungLogicNum)) << (160 + 80);
  }

  function unpackDungeonKey(bytes32 data) internal pure returns (address heroAdr, uint80 heroId, uint16 dungLogicNum) {
    heroAdr = address(uint160(uint(data)));
    heroId = uint80(uint(data) >> 160);
    dungLogicNum = uint16(uint(data) >> (160 + 80));
  }

  // --- GAME OBJECTS ---

  function packIterationKey(address heroAdr, uint64 heroId, uint32 objId) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(heroAdr)));
    data |= bytes32(uint(heroId)) << 160;
    data |= bytes32(uint(objId)) << (160 + 64);
  }

  function unpackIterationKey(bytes32 data) internal pure returns (address heroAdr, uint64 heroId, uint32 objId) {
    heroAdr = address(uint160(uint(data)));
    heroId = uint64(uint(data) >> 160);
    objId = uint32(uint(data) >> (160 + 64));
  }

  function packMonsterStats(
    uint8 level,
    uint8 race,
    uint32 experience,
    uint8 maxDropItems
  ) internal pure returns (bytes32 data) {
    data = bytes32(uint(level));
    data |= bytes32(uint(race)) << 8;
    data |= bytes32(uint(experience)) << (8 + 8);
    data |= bytes32(uint(maxDropItems)) << (8 + 8 + 32);
  }

  function unpackMonsterStats(bytes32 data) internal pure returns (
    uint8 level,
    uint8 race,
    uint32 experience,
    uint8 maxDropItems
  ) {
    level = uint8(uint(data));
    race = uint8(uint(data) >> 8);
    experience = uint32(uint(data) >> (8 + 8));
    maxDropItems = uint8(uint(data) >> (8 + 8 + 32));
  }

  function packAttackInfo(
    address attackToken,
    uint64 attackTokenId,
    uint8 attackType
  ) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(attackToken)));
    data |= bytes32(uint(attackTokenId)) << 160;
    data |= bytes32(uint(attackType)) << (160 + 64);
  }

  function unpackAttackInfo(bytes32 data) internal pure returns (
    address attackToken,
    uint64 attackTokenId,
    uint8 attackType
  ) {
    attackToken = address(uint160(uint(data)));
    attackTokenId = uint64(uint(data) >> 160);
    attackType = uint8(uint(data) >> (160 + 64));
  }

  function packPlayedObjKey(address heroAdr, uint64 heroId, uint8 oType, uint8 biome) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(heroAdr)));
    data |= bytes32(uint(heroId)) << 160;
    data |= bytes32(uint(oType)) << (160 + 64);
    data |= bytes32(uint(biome)) << (160 + 64 + 8);
  }

  function unpackPlayedObjKey(bytes32 data) internal pure returns (address heroAdr, uint64 heroId, uint8 oType, uint8 biome) {
    heroAdr = address(uint160(uint(data)));
    heroId = uint64(uint(data) >> 160);
    oType = uint8(uint(data) >> (160 + 64));
    biome = uint8(uint(data) >> (160 + 64 + 8));
  }

  function packGeneratedMonster(bool generated, uint32 amplifier, int32 hp, uint8 turnCounter) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint8(generated ? 1 : 0)));
    data |= bytes32(uint(amplifier)) << 8;
    data |= bytes32(uint(uint32(hp))) << (8 + 32);
    data |= bytes32(uint(turnCounter)) << (8 + 32 + 32);
  }

  function unpackGeneratedMonster(bytes32 data) internal pure returns (bool generated, uint32 amplifier, int32 hp, uint8 turnCounter) {
    generated = uint8(uint(data)) == uint8(1);
    amplifier = uint32(uint(data) >> 8);
    hp = int32(int(uint(data) >> (8 + 32)));
    turnCounter = uint8(uint(data) >> (8 + 32 + 32));
  }
  //endregion ------------------------------------ DUNGEON

  //region ------------------------------------ ITEMS

  /// @notice itemMetaType8 + itemLvl8 + itemType8 + baseDurability16 + defaultRarity8 + minAttr8 + maxAttr8 + manaCost32 + req(packed core 128)
  /// @param itemType This is ItemType enum
  function packItemMeta(
    uint8 itemMetaType,
    uint8 itemLvl,
    uint8 itemType,
    uint16 baseDurability,
    uint8 defaultRarity,
    uint8 minAttr,
    uint8 maxAttr,
    uint32 manaCost,
    IStatController.CoreAttributes memory req
  ) internal pure returns (bytes32 data) {
    data = bytes32(uint(itemMetaType));
    data |= bytes32(uint(itemLvl)) << 8;
    data |= bytes32(uint(itemType)) << (8 + 8);
    data |= bytes32(uint(baseDurability)) << (8 + 8 + 8);
    data |= bytes32(uint(defaultRarity)) << (8 + 8 + 8 + 16);
    data |= bytes32(uint(minAttr)) << (8 + 8 + 8 + 16 + 8);
    data |= bytes32(uint(maxAttr)) << (8 + 8 + 8 + 16 + 8 + 8);
    data |= bytes32(uint(manaCost)) << (8 + 8 + 8 + 16 + 8 + 8 + 8);
    data |= bytes32(uint(int(req.strength))) << (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32);
    data |= bytes32(uint(int(req.dexterity))) << (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32 + 32);
    data |= bytes32(uint(int(req.vitality))) << (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32 + 32 + 32);
    data |= bytes32(uint(int(req.energy))) << (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32 + 32 + 32 + 32);
  }

  function unpackItemMeta(bytes32 data) internal pure returns (IItemController.ItemMeta memory) {
    IItemController.ItemMeta memory result;

    result.itemMetaType = uint8(uint(data));
    result.itemLevel = uint8(uint(data) >> 8);
    result.itemType = IItemController.ItemType(uint8(uint(data) >> (8 + 8)));
    result.baseDurability = uint16(uint(data) >> (8 + 8 + 8));
    result.defaultRarity = uint8(uint(data) >> (8 + 8 + 8 + 16));
    result.minRandomAttributes = uint8(uint(data) >> (8 + 8 + 8 + 16 + 8));
    result.maxRandomAttributes = uint8(uint(data) >> (8 + 8 + 8 + 16 + 8 + 8));
    result.manaCost = uint32(uint(data) >> (8 + 8 + 8 + 16 + 8 + 8 + 8));
    result.requirements.strength = int32(int(uint(data) >> (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32)));
    result.requirements.dexterity = int32(int(uint(data) >> (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32 + 32)));
    result.requirements.vitality = int32(int(uint(data) >> (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32 + 32 + 32)));
    result.requirements.energy = int32(int(uint(data) >> (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32 + 32 + 32 + 32)));

    return result;
  }

  function packItemGenerateInfo(uint8 id, int32 min, int32 max, uint32 chance) internal pure returns (bytes32 data) {
    data = bytes32(uint(id));
    data |= bytes32(uint(uint32(min))) << 8;
    data |= bytes32(uint(uint32(max))) << (8 + 32);
    data |= bytes32(uint(chance)) << (8 + 32 + 32);
  }

  function unpackItemGenerateInfo(bytes32 data) internal pure returns (uint8 id, int32 min, int32 max, uint32 chance) {
    id = uint8(uint(data));
    min = int32(int(uint(data) >> 8));
    max = int32(int(uint(data) >> (8 + 32)));
    chance = uint32(uint(data) >> (8 + 32 + 32));
  }

  function packItemAttackInfo(
    uint8 attackType,
    int32 min,
    int32 max,
    int32 factorStr,
    int32 factorDex,
    int32 factorVit,
    int32 factorEng
  ) internal pure returns (bytes32 data) {
    data = bytes32(uint(attackType));
    data |= bytes32(uint(uint32(min))) << 8;
    data |= bytes32(uint(uint32(max))) << (8 + 32);
    data |= bytes32(uint(int(factorStr))) << (8 + 32 + 32);
    data |= bytes32(uint(int(factorDex))) << (8 + 32 + 32 + 32);
    data |= bytes32(uint(int(factorVit))) << (8 + 32 + 32 + 32 + 32);
    data |= bytes32(uint(int(factorEng))) << (8 + 32 + 32 + 32 + 32 + 32);
  }

  function unpackItemAttackInfo(bytes32 data) internal pure returns (
    uint8 attackType,
    int32 min,
    int32 max,
    int32 factorStr,
    int32 factorDex,
    int32 factorVit,
    int32 factorEng
  ) {
    attackType = uint8(uint(data));
    min = int32(int(uint(data) >> 8));
    max = int32(int(uint(data) >> (8 + 32)));
    factorStr = int32(int(uint(data) >> (8 + 32 + 32)));
    factorDex = int32(int(uint(data) >> (8 + 32 + 32 + 32)));
    factorVit = int32(int(uint(data) >> (8 + 32 + 32 + 32 + 32)));
    factorEng = int32(int(uint(data) >> (8 + 32 + 32 + 32 + 32 + 32)));
  }

  function packItemInfo(uint8 rarity, uint8 augmentationLevel, uint16 durability) internal pure returns (bytes32 data) {
    data = bytes32(uint(rarity));
    data |= bytes32(uint(augmentationLevel)) << 8;
    data |= bytes32(uint(durability)) << (8 + 8);
  }

  function unpackItemInfo(bytes32 data) internal pure returns (uint8 rarity, uint8 augmentationLevel, uint16 durability) {
    rarity = uint8(uint(data));
    augmentationLevel = uint8(uint(data) >> 8);
    durability = uint16(uint(data) >> (8 + 8));
  }
  //endregion ------------------------------------ ITEMS

  //region ------------------------------------ STORIES

  function packStoryPageId(uint16 storyId, uint16 pageId, uint8 heroClass) internal pure returns (bytes32 data) {
    data = bytes32(uint(storyId));
    data |= bytes32(uint(pageId)) << 16;
    data |= bytes32(uint(heroClass)) << (16 + 16);
  }

  function unpackStoryPageId(bytes32 data) internal pure returns (uint16 storyId, uint16 pageId, uint8 heroClass) {
    storyId = uint16(uint(data));
    pageId = uint16(uint(data) >> 16);
    heroClass = uint8(uint(data) >> (16 + 16));
  }

  function packStoryAnswerId(uint16 storyId, uint16 pageId, uint8 heroClass, uint16 answerId) internal pure returns (bytes32 data) {
    data = bytes32(uint(storyId));
    data |= bytes32(uint(pageId)) << 16;
    data |= bytes32(uint(heroClass)) << (16 + 16);
    data |= bytes32(uint(answerId)) << (16 + 16 + 8);
  }

  function unpackStoryAnswerId(bytes32 data) internal pure returns (uint16 storyId, uint16 pageId, uint8 heroClass, uint16 answerId) {
    storyId = uint16(uint(data));
    pageId = uint16(uint(data) >> 16);
    heroClass = uint8(uint(data) >> (16 + 16));
    answerId = uint16(uint(data) >> (16 + 16 + 8));
  }

  function packStoryNextPagesId(uint16 storyId, uint16 pageId, uint8 heroClass, uint16 answerId, uint8 resultId) internal pure returns (bytes32 data) {
    data = bytes32(uint(storyId));
    data |= bytes32(uint(pageId)) << 16;
    data |= bytes32(uint(heroClass)) << (16 + 16);
    data |= bytes32(uint(answerId)) << (16 + 16 + 8);
    data |= bytes32(uint(resultId)) << (16 + 16 + 8 + 16);
  }

  function unpackStoryNextPagesId(bytes32 data) internal pure returns (uint16 storyId, uint16 pageId, uint8 heroClass, uint16 answerId, uint8 resultId) {
    storyId = uint16(uint(data));
    pageId = uint16(uint(data) >> 16);
    heroClass = uint8(uint(data) >> (16 + 16));
    answerId = uint16(uint(data) >> (16 + 16 + 8));
    resultId = uint8(uint(data) >> (16 + 16 + 8 + 16));
  }

  function packStoryAttributeRequirement(uint8 attributeIndex, int32 value, bool isCore) internal pure returns (bytes32 data) {
    data = bytes32(uint(attributeIndex));
    data |= bytes32(uint(uint32(value))) << 8;
    data |= bytes32(uint(isCore ? uint8(1) : uint8(0))) << (8 + 32);
  }

  function unpackStoryAttributeRequirement(bytes32 data) internal pure returns (uint8 attributeIndex, int32 value, bool isCore) {
    attributeIndex = uint8(uint(data));
    value = int32(int(uint(data) >> 8));
    isCore = uint8(uint(data) >> (8 + 32)) == uint8(1);
  }

  function packStoryItemRequirement(address item, bool requireItemBurn, bool requireItemEquipped) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(item)));
    data |= bytes32(uint(requireItemBurn ? uint8(1) : uint8(0))) << 160;
    data |= bytes32(uint(requireItemEquipped ? uint8(1) : uint8(0))) << (160 + 8);
  }

  function unpackStoryItemRequirement(bytes32 data) internal pure returns (address item, bool requireItemBurn, bool requireItemEquipped) {
    item = address(uint160(uint(data)));
    requireItemBurn = uint8(uint(data) >> 160) == uint8(1);
    requireItemEquipped = uint8(uint(data) >> (160 + 8)) == uint8(1);
  }

  /// @dev max amount is 309,485,009 for token with 18 decimals
  function packStoryTokenRequirement(address token, uint88 amount, bool requireTransfer) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(token)));
    data |= bytes32(uint(amount)) << 160;
    data |= bytes32(uint(requireTransfer ? uint8(1) : uint8(0))) << (160 + 88);
  }

  function unpackStoryTokenRequirement(bytes32 data) internal pure returns (address token, uint88 amount, bool requireTransfer) {
    token = address(uint160(uint(data)));
    amount = uint88(uint(data) >> 160);
    requireTransfer = uint8(uint(data) >> (160 + 88)) == uint8(1);
  }

  function packStoryCustomDataResult(uint16 storyId, uint16 pageId, uint8 heroClass, uint16 answerId, uint8 customDataResultId) internal pure returns (bytes32 data) {
    data = bytes32(uint(storyId));
    data |= bytes32(uint(pageId)) << 16;
    data |= bytes32(uint(heroClass)) << (16 + 16);
    data |= bytes32(uint(answerId)) << (16 + 16 + 8);
    data |= bytes32(uint(customDataResultId)) << (16 + 16 + 8 + 16);
  }

  function unpackStoryCustomDataResult(bytes32 data) internal pure returns (uint16 storyId, uint16 pageId, uint8 heroClass, uint16 answerId, uint8 customDataResultId) {
    storyId = uint16(uint(data));
    pageId = uint16(uint(data) >> 16);
    heroClass = uint8(uint(data) >> (16 + 16));
    answerId = uint16(uint(data) >> (16 + 16 + 8));
    customDataResultId = uint8(uint(data) >> (16 + 16 + 8 + 16));
  }

  function packStoryHeroState(uint16 pageId, uint40 heroLastActionTS) internal pure returns (bytes32 data) {
    data = bytes32(uint(pageId));
    data |= bytes32(uint(heroLastActionTS)) << 16;
  }

  function unpackStoryHeroState(bytes32 data) internal pure returns (uint16 pageId, uint40 heroLastActionTS) {
    pageId = uint16(uint(data));
    heroLastActionTS = uint40(uint(data) >> 16);
  }

  function packStoryHeroStateId(address heroAdr, uint80 heroId, uint16 storyId) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(heroAdr)));
    data |= bytes32(uint(heroId)) << 160;
    data |= bytes32(uint(storyId)) << (160 + 80);
  }

  function unpackStoryHeroStateId(bytes32 data) internal pure returns (address heroAdr, uint80 heroId, uint16 storyId) {
    heroAdr = address(uint160(uint(data)));
    heroId = uint80(uint(data) >> 160);
    storyId = uint16(uint(data) >> (160 + 80));
  }

  function packStorySimpleRequirement(uint32 randomRequirement, uint32 delayRequirement, bool isFinalAnswer) internal pure returns (bytes32 data) {
    data = bytes32(uint(randomRequirement));
    data |= bytes32(uint(delayRequirement)) << 32;
    data |= bytes32(uint(isFinalAnswer ? uint8(1) : uint8(0))) << (32 + 32);
  }

  function unpackStorySimpleRequirement(bytes32 data) internal pure returns (uint32 randomRequirement, uint32 delayRequirement, bool isFinalAnswer) {
    randomRequirement = uint32(uint(data));
    delayRequirement = uint32(uint(data) >> 32);
    isFinalAnswer = uint8(uint(data) >> (32 + 32)) == uint8(1);
  }

  function packBreakInfo(uint8 slot, uint64 chance, bool stopIfBroken) internal pure returns (bytes32 data) {
    data = bytes32(uint(slot));
    data |= bytes32(uint(chance)) << 8;
    data |= bytes32(uint(stopIfBroken ? uint8(1) : uint8(0))) << (8 + 64);
  }

  function unpackBreakInfo(bytes32 data) internal pure returns (uint8 slot, uint64 chance, bool stopIfBurned) {
    slot = uint8(uint(data));
    chance = uint64(uint(data) >> 8);
    stopIfBurned = uint8(uint(data) >> (8 + 64)) == uint8(1);
  }
  //endregion ------------------------------------ STORIES

  //region ------------------------------------ Hero controller
  function packTierHero(uint8 tier, address hero) internal pure returns (bytes32 packedTierHero) {
    packedTierHero = bytes32(uint(tier));
    packedTierHero |= bytes32(uint(uint160(hero)) << 8);
  }

  function unpackTierHero(bytes32 packedTierHero) internal pure returns (uint8 tier, address hero) {
    tier = uint8(uint(packedTierHero));
    hero = address(uint160(uint(packedTierHero) >> 8));
  }

  //endregion ------------------------------------ Hero controller

  ////////////////////////////////////////////////////////////////////////////////////
  // ---- ARRAYS LOGIC ----
  ////////////////////////////////////////////////////////////////////////////////////

  //region ------------------------------------ SIMPLE ARRAYS


  function packUint8Array(uint8[] memory data) internal pure returns (bytes32) {
    uint len = data.length;
    if (len > 32) revert IAppErrors.OutOfBounds(len, 32);
    bytes32 result;
    for (uint i = 0; i < len; i++) {
      result |= bytes32(uint(data[i])) << (i * 8);
    }
    return result;
  }

  /// @notice Simple faster version of {packUint8Array} for small number of items
  ///         It allows to exclude dynamic array creation.
  function packUint8Array3(uint8 a, uint8 b, uint8 c) internal pure returns (bytes32) {
    bytes32 result = bytes32(uint(a));
    result |= bytes32(uint(b)) << (1 * 8);
    result |= bytes32(uint(c)) << (2 * 8);
    return result;
  }


  function unpackUint8Array(bytes32 data) internal pure returns (uint8[] memory) {
    uint8[] memory result = new uint8[](32);
    for (uint i = 0; i < 32; i++) {
      result[i] = uint8(uint(data) >> (i * 8));
    }
    return result;
  }

  /// @notice Simple faster version of {unpackUint8Array} for small number of items
  ///         It allows to exclude only first 3 values
  function unpackUint8Array3(bytes32 data) internal pure returns (uint8 a, uint8 b, uint8 c) {
    a = uint8(uint(data));
    b = uint8(uint(data) >> (1 * 8));
    c = uint8(uint(data) >> (2 * 8));
  }

  function changeUnit8ArrayWithCheck(bytes32 data, uint index, uint8 value, uint8 expectedPrevValue) internal pure returns (bytes32 newData) {
    uint8[] memory arr = unpackUint8Array(data);
    if (arr[index] != expectedPrevValue) revert IAppErrors.UnexpectedValue(uint(expectedPrevValue), uint(arr[index]));
    arr[index] = value;
    return packUint8Array(arr);
  }

  function packInt32Array(int32[] memory data) internal pure returns (bytes32) {
    uint len = data.length;
    if (len > 8) revert IAppErrors.OutOfBounds(len, 8);
    bytes32 result;
    for (uint i; i < len; i++) {
      result |= bytes32(uint(uint32(data[i]))) << (i * 32);
    }
    return result;
  }

  function unpackInt32Array(bytes32 data) internal pure returns (int32[] memory) {
    int32[] memory result = new int32[](8);
    for (uint i = 0; i < 8; i++) {
      result[i] = int32(int(uint(data) >> (i * 32)));
    }
    return result;
  }

  function packUint32Array(uint32[] memory data) internal pure returns (bytes32) {
    uint len = data.length;
    if (len > 8) revert IAppErrors.OutOfBounds(len, 8);
    bytes32 result;
    for (uint i = 0; i < len; i++) {
      result |= bytes32(uint(data[i])) << (i * 32);
    }
    return result;
  }

  function unpackUint32Array(bytes32 data) internal pure returns (uint32[] memory) {
    uint32[] memory result = new uint32[](8);
    for (uint i = 0; i < 8; i++) {
      result[i] = uint32(uint(data) >> (i * 32));
    }
    return result;
  }
  //endregion ------------------------------------ SIMPLE ARRAYS

  //region ------------------------------------ COMPLEX ARRAYS

  // We should represent arrays without concrete size.
  // For this reason we must not revert IAppErrors.on out of bounds but return zero value instead.

  // we need it for properly unpack packed arrays with ids
//  function getInt32AsInt24(bytes32[] memory arr, uint idx) internal pure returns (int32) {
//    if (idx / 8 >= arr.length) {
//      return int32(0);
//    }
//    return int32(int24(int(uint(arr[idx / 8]) >> ((idx % 8) * 32))));
//  }

  // we need it for properly unpack packed arrays with ids
//  function getUnit8From32Step(bytes32[] memory arr, uint idx) internal pure returns (uint8) {
//    if (idx / 8 >= arr.length) {
//      return uint8(0);
//    }
//    return uint8(uint(arr[idx / 8]) >> ((idx % 8) * 32 + 24));
//  }

  function getInt32Memory(bytes32[] memory arr, uint idx) internal pure returns (int32) {
    if (idx / 8 >= arr.length) {
      return int32(0);
    }
    return int32(int(uint(arr[idx / 8]) >> ((idx % 8) * 32)));
  }

  function getInt32(bytes32[] storage arr, uint idx) internal view returns (int32) {
    // additional gas usage, but we should not revert IAppErrors.on out of bounds
    if (idx / 8 >= arr.length) {
      return int32(0);
    }
    return int32(int(uint(arr[idx / 8]) >> ((idx % 8) * 32)));
  }

  function setInt32(bytes32[] storage arr, uint idx, int32 value) internal {
    uint pos = idx / 8;
    uint shift = (idx % 8) * 32;

    uint curLength = arr.length;
    if (pos >= curLength) {
      arr.push(0);
      for (uint i = curLength; i < pos; ++i) {
        arr.push(0);
      }
    }

    arr[pos] = bytes32(uint(arr[pos]) & ~(uint(0xffffffff) << shift) | (uint(uint32(value)) & 0xffffffff) << shift);
  }

  /// @notice Increment {idx}-th item on {value}
  function changeInt32(bytes32[] storage arr, uint idx, int32 value) internal returns (int32 newValue, int32 change) {
    int32 cur = int32(int(getInt32(arr, idx)));
    int newValueI = int(cur) + int(value);
    newValue = int32(newValueI);
    change = int32(newValueI - int(cur));

    setInt32(arr, idx, newValue);
  }

  function toInt32Array(bytes32[] memory arr, uint size) internal pure returns (int32[] memory) {
    int32[] memory result = new int32[](size);
    for (uint i = 0; i < arr.length; i++) {
      for (uint j; j < 8; ++j) {
        uint idx = i * 8 + j;
        if (idx >= size) break;
        result[idx] = getInt32Memory(arr, idx);
      }
    }
    return result;
  }

  /// @dev pack int32 array into bytes32 array
  function toBytes32Array(int32[] memory arr) internal pure returns (bytes32[] memory) {
    uint size = arr.length / 8 + 1;
    bytes32[] memory result = new bytes32[](size);
    for (uint i; i < size; ++i) {
      for (uint j; j < 8; ++j) {
        uint idx = i * 8 + j;
        if (idx >= arr.length) break;
        result[i] |= bytes32(uint(uint32(arr[idx]))) << (j * 32);
      }
    }
    return result;
  }

  /// @dev pack int32 array into bytes32 array using last 8bytes for ids
  ///      we can not use zero values coz will not able to properly unpack it later
  function toBytes32ArrayWithIds(int32[] memory arr, uint8[] memory ids) internal pure returns (bytes32[] memory) {
    if (arr.length != ids.length) revert IAppErrors.LengthsMismatch();

    uint size = arr.length / 8 + 1;
    bytes32[] memory result = new bytes32[](size);
    for (uint i; i < size; ++i) {
      for (uint j; j < 8; ++j) {
        uint idx = i * 8 + j;
        if (idx >= arr.length) break;

        if (arr[idx] > type(int24).max || arr[idx] < type(int24).min) revert IAppErrors.IntOutOfRange(int(arr[idx]));
        if (arr[idx] == 0) revert IAppErrors.ZeroValue();
        result[i] |= bytes32(uint(uint24(int24(arr[idx])))) << (j * 32);
        result[i] |= bytes32(uint(ids[idx])) << (j * 32 + 24);
      }
    }
    return result;
  }

  /// @dev we do not know exact size of array, assume zero values is not acceptable for this array
  function toInt32ArrayWithIds(bytes32[] memory arr) internal pure returns (int32[] memory values, uint8[] memory ids) {
    uint len = arr.length;
    uint size = len * 8;
    int32[] memory valuesTmp = new int32[](size);
    uint8[] memory idsTmp = new uint8[](size);
    uint counter;
    for (uint i = 0; i < len; i++) {
      for (uint j; j < 8; ++j) {
        uint idx = i * 8 + j;
        // if (idx >= size) break;  // it looks like a useless check
        valuesTmp[idx] = int32(int24(int(uint(arr[i]) >> (j * 32)))); // getInt32AsInt24(arr, idx);
        idsTmp[idx] = uint8(uint(arr[i]) >> (j * 32 + 24)); // getUnit8From32Step(arr, idx);
        if (valuesTmp[idx] == 0) {
          break;
        }
        counter++;
      }
    }

    values = new int32[](counter);
    ids = new uint8[](counter);
    for (uint i; i < counter; ++i) {
      values[i] = valuesTmp[i];
      ids[i] = idsTmp[i];
    }
  }
  //endregion ------------------------------------ COMPLEX ARRAYS

  //region ------------------------------------ Guilds
  /// @dev ShelterID is uint. But in the code we assume that this ID can be stored as uint64 (see auctions)
  /// @param biome 1, 2, 3...
  /// @param shelterLevel 1, 2 or 3.
  /// @param shelterIndex 0, 1, 2 ...
  function packShelterId(uint8 biome, uint8 shelterLevel, uint8 shelterIndex) internal pure returns (uint) {
    return uint(biome) | (uint(shelterLevel) << 8) | (uint(shelterIndex) << 16);
  }

  function unpackShelterId(uint shelterId) internal pure returns (uint8 biome, uint8 shelterLevel, uint8 shelterIndex) {
    return (uint8(shelterId), uint8(shelterId >> 8), uint8(shelterId >> 16));
  }
  //endregion ------------------------------------ Guilds

  //region ------------------------------------ Metadata of IItemController.OtherSubtypeKind

  function getOtherItemTypeKind(bytes memory packedData) internal pure returns (IItemController.OtherSubtypeKind) {
    bytes32 serialized;
    assembly {
      serialized := mload(add(packedData, 32))
    }
    uint8 kind = uint8(uint(serialized));
    if (kind == 0 || kind >= uint8(IItemController.OtherSubtypeKind.END_SLOT)) revert IAppErrors.IncorrectOtherItemTypeKind(kind);
    return IItemController.OtherSubtypeKind(kind);
  }

  function packOtherItemReduceFragility(uint value) internal pure returns (bytes memory packedData) {
    bytes32 serialized = bytes32(uint(uint8(IItemController.OtherSubtypeKind.REDUCE_FRAGILITY_1)));
    serialized |= bytes32(uint(uint248(value))) << 8;
    return bytes.concat(serialized);
  }

  function unpackOtherItemReduceFragility(bytes memory packedData) internal pure returns (uint) {
    bytes32 serialized;
    assembly {
      serialized := mload(add(packedData, 32))
    }
    uint8 kind = uint8(uint(serialized));
    if (kind != uint8(IItemController.OtherSubtypeKind.REDUCE_FRAGILITY_1)) revert IAppErrors.IncorrectOtherItemTypeKind(kind);
    uint value = uint248(uint(serialized) >> 8);
    return value;
  }
  //endregion ------------------------------------ Metadata of IItemController.OtherSubtypeKind
}

File 36 of 48 : ScoreLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../openzeppelin/Math.sol";
import "../interfaces/IStatController.sol";
import "./CalcLib.sol";

library ScoreLib {
  using CalcLib for int32;

  // core
  uint public constant STRENGTH = 100;
  uint public constant DEXTERITY = 100;
  uint public constant VITALITY = 100;
  uint public constant ENERGY = 100;

  // attributes
  uint public constant MELEE_DAMAGE = 10;
  uint public constant ATTACK_RATING = 3;
  uint public constant DEFENCE = 10;
  uint public constant BLOCK_RATING = 500;
  uint public constant LIFE = 10;
  uint public constant MANA = 10;

  uint public constant LIFE_CHANCES = 10_000;
  uint public constant MAGIC_FIND = 300;
  uint public constant CRITICAL_HIT = 150;
  uint public constant DMG_FACTOR = 200;

  uint public constant AR_FACTOR = 200;
  uint public constant LIFE_STOLEN_PER_HIT = 1000;
  uint public constant MANA_AFTER_KILL = 1000;
  uint public constant DAMAGE_REDUCTION = 500;
  uint public constant REFLECT_DAMAGE = 250;
  uint public constant RESIST_TO_STATUSES = 70;

  // resistance
  uint public constant ELEMENT_RESIST = 100;

  // race specific attributes
  uint public constant RACE_SPECIFIC = 20;

  // statuses
  uint public constant STATUSES = 100;

  // items
  uint public constant DURABILITY_SCORE = 1;

  // hero
  uint public constant HERO_LEVEL_SCORE = 1000;

  /// @param isForReinforcement If true calculate score using 12 main attributes only. Otherwise use all attributes.
  function attributesScore(int32[] memory attributes, bool isForReinforcement) internal pure returns (uint) {
    uint result;
    {
      result += (attributes[uint(IStatController.ATTRIBUTES.STRENGTH)]).toUint() * STRENGTH
        + (attributes[uint(IStatController.ATTRIBUTES.DEXTERITY)]).toUint() * DEXTERITY
        + (attributes[uint(IStatController.ATTRIBUTES.VITALITY)]).toUint() * VITALITY
        + (attributes[uint(IStatController.ATTRIBUTES.ENERGY)]).toUint() * ENERGY
        + (attributes[uint(IStatController.ATTRIBUTES.ATTACK_RATING)]).toUint() * ATTACK_RATING
        + (attributes[uint(IStatController.ATTRIBUTES.DEFENSE)]).toUint() * DEFENCE
        + (attributes[uint(IStatController.ATTRIBUTES.BLOCK_RATING)]).toUint() * BLOCK_RATING
        + Math.average(attributes[uint(IStatController.ATTRIBUTES.DAMAGE_MIN)].toUint(), attributes[uint(IStatController.ATTRIBUTES.DAMAGE_MAX)].toUint()) * MELEE_DAMAGE
      ;
    }
    {
      result +=
        (attributes[uint(IStatController.ATTRIBUTES.FIRE_RESISTANCE)]).toUint() * ELEMENT_RESIST
        + (attributes[uint(IStatController.ATTRIBUTES.COLD_RESISTANCE)]).toUint() * ELEMENT_RESIST
        + (attributes[uint(IStatController.ATTRIBUTES.LIGHTNING_RESISTANCE)]).toUint() * ELEMENT_RESIST;
    }

    if (! isForReinforcement) {
      {
        result +=
          (attributes[uint(IStatController.ATTRIBUTES.LIFE)]).toUint() * LIFE
          + (attributes[uint(IStatController.ATTRIBUTES.MANA)]).toUint() * MANA;
      }
      {
        result +=
          (attributes[uint(IStatController.ATTRIBUTES.DMG_AGAINST_HUMAN)]).toUint() * RACE_SPECIFIC
          + (attributes[uint(IStatController.ATTRIBUTES.DMG_AGAINST_UNDEAD)]).toUint() * RACE_SPECIFIC
          + (attributes[uint(IStatController.ATTRIBUTES.DMG_AGAINST_DAEMON)]).toUint() * RACE_SPECIFIC
          + (attributes[uint(IStatController.ATTRIBUTES.DMG_AGAINST_BEAST)]).toUint() * RACE_SPECIFIC
          + (attributes[uint(IStatController.ATTRIBUTES.DEF_AGAINST_HUMAN)]).toUint() * RACE_SPECIFIC
          + (attributes[uint(IStatController.ATTRIBUTES.DEF_AGAINST_UNDEAD)]).toUint() * RACE_SPECIFIC
          + (attributes[uint(IStatController.ATTRIBUTES.DEF_AGAINST_DAEMON)]).toUint() * RACE_SPECIFIC
          + (attributes[uint(IStatController.ATTRIBUTES.DEF_AGAINST_BEAST)]).toUint() * RACE_SPECIFIC;
      }
      {
        result +=
          (attributes[uint(IStatController.ATTRIBUTES.STUN)]).toUint() * STATUSES
          + (attributes[uint(IStatController.ATTRIBUTES.BURN)]).toUint() * STATUSES
          + (attributes[uint(IStatController.ATTRIBUTES.FREEZE)]).toUint() * STATUSES
          + (attributes[uint(IStatController.ATTRIBUTES.CONFUSE)]).toUint() * STATUSES
          + (attributes[uint(IStatController.ATTRIBUTES.CURSE)]).toUint() * STATUSES
          + (attributes[uint(IStatController.ATTRIBUTES.POISON)]).toUint() * STATUSES;
      }
      {
        result +=
          (attributes[uint(IStatController.ATTRIBUTES.LIFE_CHANCES)]).toUint() * LIFE_CHANCES
          + (attributes[uint(IStatController.ATTRIBUTES.MAGIC_FIND)]).toUint() * MAGIC_FIND
          + (attributes[uint(IStatController.ATTRIBUTES.CRITICAL_HIT)]).toUint() * CRITICAL_HIT
          + (attributes[uint(IStatController.ATTRIBUTES.MELEE_DMG_FACTOR)]).toUint() * DMG_FACTOR
          + (attributes[uint(IStatController.ATTRIBUTES.FIRE_DMG_FACTOR)]).toUint() * DMG_FACTOR
          + (attributes[uint(IStatController.ATTRIBUTES.COLD_DMG_FACTOR)]).toUint() * DMG_FACTOR
          + (attributes[uint(IStatController.ATTRIBUTES.LIGHTNING_DMG_FACTOR)]).toUint() * DMG_FACTOR;
      }
      {
        result +=
          (attributes[uint(IStatController.ATTRIBUTES.AR_FACTOR)]).toUint() * AR_FACTOR
          + (attributes[uint(IStatController.ATTRIBUTES.LIFE_STOLEN_PER_HIT)]).toUint() * LIFE_STOLEN_PER_HIT
          + (attributes[uint(IStatController.ATTRIBUTES.MANA_AFTER_KILL)]).toUint() * MANA_AFTER_KILL
          + (attributes[uint(IStatController.ATTRIBUTES.DAMAGE_REDUCTION)]).toUint() * DAMAGE_REDUCTION
          + (attributes[uint(IStatController.ATTRIBUTES.REFLECT_DAMAGE_MELEE)]).toUint() * REFLECT_DAMAGE
          + (attributes[uint(IStatController.ATTRIBUTES.REFLECT_DAMAGE_MAGIC)]).toUint() * REFLECT_DAMAGE
          + (attributes[uint(IStatController.ATTRIBUTES.RESIST_TO_STATUSES)]).toUint() * RESIST_TO_STATUSES;
      }
    }
    return result;
  }

  function itemScore(int32[] memory attributes, uint16 baseDurability) internal pure returns (uint) {
    return attributesScore(attributes, false) + baseDurability * DURABILITY_SCORE;
  }

  function heroScore(int32[] memory attributes, uint level) internal pure returns (uint) {
    return attributesScore(attributes, true) + level * HERO_LEVEL_SCORE;
  }

}

File 37 of 48 : ShelterLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../interfaces/IAppErrors.sol";
import "../interfaces/IApplicationEvents.sol";
import "../interfaces/IShelterController.sol";
import "../interfaces/IShelterController.sol";
import "../interfaces/IUserController.sol";
import "../lib/StringLib.sol";
import "../token/GuildBank.sol";
import "./StatLib.sol";
import "../interfaces/IShelterAuction.sol";

library ShelterLib {
  using EnumerableSet for EnumerableSet.UintSet;
  using EnumerableSet for EnumerableSet.AddressSet;
  using EnumerableSet for EnumerableSet.UintSet;

  //region ------------------------ Constants
  /// @dev keccak256(abi.encode(uint256(keccak256("shelter.controller.main")) - 1)) & ~bytes32(uint256(0xff))
  bytes32 internal constant SHELTER_CONTROLLER_STORAGE_LOCATION = 0x5a293071b39954a4fcf98ae7184af7c6201e972e15842b884f1ad071e9bded00; // shelter.controller.main

  uint8 internal constant MIN_SHELTER_LEVEL = 1;
  uint8 internal constant MAX_SHELTER_LEVEL = 3;
  //endregion ------------------------ Constants

  //region ------------------------ Restrictions
  function _onlyDeployer(IController controller) internal view {
    if (!controller.isDeployer(msg.sender)) revert IAppErrors.ErrorNotDeployer(msg.sender);
  }

  function _onlyGuildController(address guildController) internal view {
    if (msg.sender != guildController) revert IAppErrors.ErrorNotGuildController();
  }

  function _notPaused(IController controller) internal view {
    if (controller.onPause()) revert IAppErrors.ErrorPaused();
  }
  //endregion ------------------------ Restrictions

  //region ------------------------ Storage

  function _S() internal pure returns (IShelterController.MainState storage s) {
    assembly {
      s.slot := SHELTER_CONTROLLER_STORAGE_LOCATION
    }
    return s;
  }
  //endregion ------------------------ Storage

  //region ------------------------ Shelter view
  /// @notice Get list of all registered shelters in the given {biome}
  function getShelters(uint8 biome) internal view returns (uint[] memory shelterIds) {
    return _S().shelters[biome].values();
  }

  /// @notice Get initial price of the given shelter. The price is used if the shelter doesn't belong to any guild
  function getShelterPrice(uint shelterId) internal view returns (uint price) {
    return _S().shelterPrices[shelterId];
  }

  /// @notice Get shelter which belongs to the given guild
  function guildToShelter(uint guildId) internal view returns (uint shelterId) {
    return _S().guildToShelter[guildId];
  }

  /// @notice Get guild to which the given shelter belongs
  function shelterToGuild(uint shelterId) internal view returns (uint guildId) {
    return _S().shelterToGuild[shelterId];
  }

  /// @notice Get shelter of the guild to which the user belongs
  function getShelterId(IGuildController guildController, address user) internal view returns (uint shelterId) {
    uint guildId = guildController.memberOf(user);
    return guildId == 0
      ? 0
      : _S().guildToShelter[guildId];
  }

  /// @notice List of items that can be bought in the shelter of the given level in the given biome
  function getShelterItems(uint shelterId) internal view returns (address[] memory items) {
    return _S().shelterItems[shelterId].values();
  }

  function getShelterItemData(uint shelterId, address item) internal view returns (
    uint64 priceInPvpPoints,
    uint128 priceInGameToken,
    uint16 maxItemsPerDayLimit
  ) {
    IShelterController.ShelterItemData memory data = _S().shelterItemData[shelterId][item];
    return (
      data.priceInPvpPoints,
      data.priceInGameToken,
      data.maxItemsPerDayLimit
    );
  }

  /// @notice How many {item} instances were purchased per {epochDay} in the given {shelterId}
  /// @param epochDay TimestampInSeconds / 24 * 60 * 60
  function getCountPurchasedItems(address item, uint shelterId, uint32 epochDay) internal view returns (uint) {
    return _S().countPurchasedItems[shelterId][epochDay][item];
  }

  //endregion ------------------------ Shelter view

  //region ------------------------ Shelter config
  /// @notice Register new shelter or overwrite exist. Only registered shelters can be purchased.
  /// @param shelterId ID should be generated using {PackingLib.packShelterId}
  /// @param price Initial shelter price in game tokens
  function setShelter(IController controller, uint shelterId, uint price) internal {
    ShelterLib._onlyDeployer(controller);

    (uint8 biome, uint8 shelterLevel, ) = PackingLib.unpackShelterId(shelterId);

    if (biome == 0 || biome > StatLib.MAX_POSSIBLE_BIOME) revert IAppErrors.ErrorIncorrectBiome(biome);
    if (price == 0) revert IAppErrors.ZeroValueNotAllowed();
    if (shelterLevel < MIN_SHELTER_LEVEL || shelterLevel > MAX_SHELTER_LEVEL) revert IAppErrors.IncorrectShelterLevel(shelterLevel);

    _S().shelterPrices[shelterId] = price;
    _S().shelters[biome].add(shelterId);

    emit IApplicationEvents.RegisterShelter(shelterId, price);
  }

  /// @notice Set items that can be purchases in the given shelter: remove previously stored items, add new items.
  /// @param shelterId ID should be generated using {PackingLib.packShelterId}
  /// @param items List of item tokens
  /// @param pricesInPvpPoints Prices in pvp-points. The points are taken from guild balance at the moment of purchasing
  /// @param pricesInGameTokens Additional prices in game tokens. Can contain zeros.
  /// @param maxItemsPerDayLimits Indicate how many item instances the users can purchase per day. 0 - no limitations
  function setShelterItems(
    IController controller,
    uint shelterId,
    address[] memory items,
    uint64[] memory pricesInPvpPoints,
    uint128[] memory pricesInGameTokens,
    uint16[] memory maxItemsPerDayLimits
  ) internal {
    ShelterLib._onlyDeployer(controller);

    uint len = items.length;
    if (len != pricesInPvpPoints.length || len != pricesInGameTokens.length || len != maxItemsPerDayLimits.length) {
      revert IAppErrors.LengthsMismatch();
    }

    EnumerableSet.AddressSet storage set = _S().shelterItems[shelterId];

    // remove previously stored items
    address[] memory prevItems = set.values();
    uint prevItemsLen = prevItems.length;
    for (uint i; i < prevItemsLen; ++i) {
      set.remove(prevItems[i]);
      delete _S().shelterItemData[shelterId][prevItems[i]];
    }

    // add new items
    for (uint i; i < len; ++i) {
      set.add(items[i]);
      if (pricesInPvpPoints[i] == 0 && pricesInGameTokens[i] == 0) revert IAppErrors.FreeShelterItemsAreNotAllowed(shelterId, items[i]);
      _S().shelterItemData[shelterId][items[i]] = IShelterController.ShelterItemData({
        priceInPvpPoints: pricesInPvpPoints[i],
        priceInGameToken: pricesInGameTokens[i],
        maxItemsPerDayLimit: maxItemsPerDayLimits[i]
      });
    }

    emit IApplicationEvents.SetShelterItems(shelterId, items, pricesInPvpPoints, pricesInGameTokens, maxItemsPerDayLimits);
  }
  //endregion ------------------------ Shelter config

  //region ------------------------ Shelter actions

  /// @notice Guild buys a shelter that doesn't belong to any guild. It pays default prices and changes owner of the shelter.
  function buyShelter(IController controller, address msgSender, uint shelterId) internal {
    _notPaused(controller);

    IGuildController guildController = IGuildController(controller.guildController());
    (uint guildId,) = guildController.checkPermissions(msgSender, uint(IGuildController.GuildRightBits.CHANGE_SHELTER_3));

    // only registered shelter can be purchased
    (uint8 biome, , ) = PackingLib.unpackShelterId(shelterId);
    if (!_S().shelters[biome].contains(shelterId)) revert IAppErrors.ShelterIsNotRegistered();

    // Each guild is able to have only 1 shelter. Exist shelter should be sold or left
    if (_S().guildToShelter[guildId] != 0) revert IAppErrors.GuildAlreadyHasShelter();
    if (_S().shelterToGuild[shelterId] != 0) revert IAppErrors.ShelterIsBusy();

    { // Shelter can be bought only if there is no auction bid
      address shelterAuction = guildController.shelterAuctionController();
      if (shelterAuction != address(0)) {
        (uint positionId,) = IShelterAuction(shelterAuction).positionByBuyer(guildId);
        if (positionId != 0) revert IAppErrors.AuctionBidOpened(positionId);
      }
    }

    // pay for the shelter from the guild bank
    uint shelterPrice = getShelterPrice(shelterId);
    guildController.payFromGuildBank(guildId, shelterPrice);

    // register ownership
    _S().guildToShelter[guildId] = shelterId;
    _S().shelterToGuild[shelterId] = guildId;

    emit IApplicationEvents.BuyShelter(guildId, shelterId);
  }

  /// @notice Guild leaves the shelter. The shelter becomes free, it can be bought by any guild by default price
  function leaveShelter(IController controller, address msgSender, uint shelterId) internal {
    _notPaused(controller);

    IGuildController guildController = IGuildController(controller.guildController());
    (uint guildId,) = guildController.checkPermissions(msgSender, uint(IGuildController.GuildRightBits.CHANGE_SHELTER_3));

    if (_S().guildToShelter[guildId] != shelterId) revert IAppErrors.ShelterIsNotOwnedByTheGuild();
    if (shelterId == 0) revert IAppErrors.GuildHasNoShelter();

    { // Shelter can be sold only if there is no opened auction position
      address shelterAuction = guildController.shelterAuctionController();
      if (shelterAuction != address(0)) {
        uint positionId = IShelterAuction(shelterAuction).positionBySeller(guildId);
        if (positionId != 0) revert IAppErrors.AuctionPositionOpened(positionId);
      }
    }

    // unregister ownership
    delete _S().guildToShelter[guildId];
    delete _S().shelterToGuild[shelterId];

    emit IApplicationEvents.LeaveShelter(guildId, shelterId);
  }

  /// @notice Purchase the {item} in the shelter that belongs to the guild to which {msgSender} belongs
  function purchaseShelterItem(IController controller, address msgSender, address item, uint blockTimestamp) internal {
    _notPaused(controller);

    IGuildController guildController = IGuildController(controller.guildController());
    // no permission are required - any guild member is able to purchase shelter item
    // but the member should either be owner or should have enough pvp-points capacity, see restriction below
    uint guildId = _getValidGuildId(guildController, msgSender);

    uint shelterId = _S().guildToShelter[guildId];
    if (shelterId == 0) revert IAppErrors.GuildHasNoShelter();

    if (! _S().shelterItems[shelterId].contains(item)) revert IAppErrors.ShelterHasNotItem(shelterId, item);

    // total number of the item instances that can be minted per day CAN BE limited
    IShelterController.ShelterItemData memory itemData = _S().shelterItemData[shelterId][item];
    uint numSoldItems;
    {
      uint32 epochDay = uint32(blockTimestamp / 86400);

      mapping(address => uint) storage countPurchasedItems = _S().countPurchasedItems[shelterId][epochDay];
      numSoldItems = countPurchasedItems[item];

      if (itemData.maxItemsPerDayLimit != 0) {
        if (numSoldItems >= itemData.maxItemsPerDayLimit) revert IAppErrors.MaxNumberItemsSoldToday(numSoldItems, itemData.maxItemsPerDayLimit);
      }
      countPurchasedItems[item] = numSoldItems + 1;
    }

    // user pays for the item by pvp-points and/or by game token (it depends on the item settings)
    if (itemData.priceInPvpPoints != 0) {
      guildController.usePvpPoints(guildId, msgSender, itemData.priceInPvpPoints);
    }

    if (itemData.priceInGameToken != 0) {
      guildController.payFromBalance(itemData.priceInGameToken, msgSender);
      //_process(controller, itemData.priceInGameToken, msgSender);
    }

    // mint the item
    IItemController(controller.itemController()).mint(item, msgSender);

    emit IApplicationEvents.PurchaseShelterItem(msgSender, item, numSoldItems + 1, itemData.priceInPvpPoints, itemData.priceInGameToken);
  }

  /// @notice clear necessary data to indicate that the guiles leaves the shelter
  function clearShelter(address guildController, uint guildId) internal {
    _onlyGuildController(guildController);

    uint shelterId = _S().guildToShelter[guildId];
    if (shelterId != 0) {
      // assume, that msgSender shouldn't have permission CHANGE_SHELTER_3 here

      // ensure that there is no open position for the shelter on auction
      address shelterAuction = IGuildController(guildController).shelterAuctionController();
      if (shelterAuction != address(0)) {
        uint positionId = IShelterAuction(shelterAuction).positionBySeller(guildId);
        if (positionId != 0) revert IAppErrors.AuctionPositionOpened(positionId);
      }

      delete _S().guildToShelter[guildId];
      delete _S().shelterToGuild[shelterId];

      emit IApplicationEvents.LeaveShelter(guildId, shelterId);
    }

  }
  //endregion ------------------------ Shelter actions

  //region ------------------------ Interaction with auctions
  function changeShelterOwner(IController controller, uint shelterId, uint newOwnerGuildId) internal {
    // we assume, that all checks are performed on ShelterAuction side, so we need min checks here
    address shelterAuction = IGuildController(controller.guildController()).shelterAuctionController();
    if (shelterAuction == address(0) || msg.sender != shelterAuction) revert IAppErrors.NotShelterAuction();

    uint prevGuildId = _S().shelterToGuild[shelterId];
    delete _S().guildToShelter[prevGuildId];
    _S().shelterToGuild[shelterId] = newOwnerGuildId;
    _S().guildToShelter[newOwnerGuildId] = shelterId;

    emit IApplicationEvents.ChangeShelterOwner(shelterId, prevGuildId, newOwnerGuildId);
  }

  //endregion ------------------------ Interaction with auctions

  //region ------------------------ Internal logic
  function _getValidGuildId(IGuildController guildController, address user) internal view returns (uint guildId) {
    guildId = guildController.memberOf(user);
    if (guildId == 0) revert IAppErrors.NotGuildMember();
  }
  //endregion ------------------------ Internal logic


}

File 38 of 48 : SlotsLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

/// @title Library for setting / getting slot variables (used in upgradable proxy contracts)
/// @author bogdoslav
library SlotsLib {

  /// @notice Version of the contract
  /// @dev Should be incremented when contract changed
  string public constant SLOT_LIB_VERSION = "1.0.0";

  // ************* GETTERS *******************

  /// @dev Gets a slot as bytes32
  function getBytes32(bytes32 slot) internal view returns (bytes32 result) {
    assembly {
      result := sload(slot)
    }
  }

  /// @dev Gets a slot as an address
  function getAddress(bytes32 slot) internal view returns (address result) {
    assembly {
      result := sload(slot)
    }
  }

  /// @dev Gets a slot as uint256
  function getUint(bytes32 slot) internal view returns (uint result) {
    assembly {
      result := sload(slot)
    }
  }

  // ************* ARRAY GETTERS *******************

  /// @dev Gets an array length
  function arrayLength(bytes32 slot) internal view returns (uint result) {
    assembly {
      result := sload(slot)
    }
  }

  /// @dev Gets a slot array by index as address
  /// @notice First slot is array length, elements ordered backward in memory
  /// @notice This is unsafe, without checking array length.
  function addressAt(bytes32 slot, uint index) internal view returns (address result) {
    bytes32 pointer = bytes32(uint(slot) - 1 - index);
    assembly {
      result := sload(pointer)
    }
  }

  // ************* SETTERS *******************

  /// @dev Sets a slot with bytes32
  /// @notice Check address for 0 at the setter
  function set(bytes32 slot, bytes32 value) internal {
    assembly {
      sstore(slot, value)
    }
  }

  /// @dev Sets a slot with address
  /// @notice Check address for 0 at the setter
  function set(bytes32 slot, address value) internal {
    assembly {
      sstore(slot, value)
    }
  }

  /// @dev Sets a slot with uint
  function set(bytes32 slot, uint value) internal {
    assembly {
      sstore(slot, value)
    }
  }

}

File 39 of 48 : StatLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../interfaces/IStatController.sol";
import "../interfaces/IHeroController.sol";
import "../interfaces/IAppErrors.sol";
import "../openzeppelin/Math.sol";
import "./CalcLib.sol";
import "./PackingLib.sol";

library StatLib {
  using PackingLib for bytes32[];
  using PackingLib for bytes32;
  using PackingLib for uint32[];
  using PackingLib for int32[];
  using CalcLib for int32;

  //region --------------------------- Constants

  /// @notice Version of the contract
  /// @dev Should be incremented when contract changed
  string public constant STAT_LIB_VERSION = "1.0.0";
  uint32 public constant MAX_LEVEL = 99;

  uint public constant BASE_EXPERIENCE = 100_000;
  uint public constant BIOME_LEVEL_STEP = 5;
  uint internal constant _MAX_AMPLIFIER = 1e18;
  uint private constant _PRECISION = 1e18;
  uint private constant VIRTUAL_LEVEL_GAP = 2;

  /// @dev Assume MAX_BIOME * BIOME_LEVEL_STEP < MAX_LEVEL + 1, see dungeonTreasuryReward
  uint public constant MAX_POSSIBLE_BIOME = 19;
  //endregion --------------------------- Constants

  //region --------------------------- Data types

  struct BaseMultiplier {
    uint minDamage;
    uint maxDamage;
    uint attackRating;
    uint defense;
    uint blockRating;
    uint life;
    uint mana;
  }

  struct LevelUp {
    uint life;
    uint mana;
  }

  struct InitialHero {
    IStatController.CoreAttributes core;
    BaseMultiplier multiplier;
    LevelUp levelUp;
    int32 baseLifeChances;
  }

  enum HeroClasses {
    UNKNOWN,
    THRALL,
    SAVAGE,
    MAGE,
    ASSASSIN,
    GHOST,
    HAMMERGINA,
    END_SLOT
  }
  //endregion --------------------------- Data types

  //region --------------------------- BASE

  function isNetworkWithOldSavage() public view returns (bool) {
    return block.chainid == uint(111188) || block.chainid == uint(250);
  }

  // --- HERO 1 (Slave) ---

  function initialHero1() internal pure returns (InitialHero memory) {
    return InitialHero({
      core: IStatController.CoreAttributes({
      strength: 15,
      dexterity: 15,
      vitality: 30,
      energy: 10
    }),

      multiplier: BaseMultiplier({
      minDamage: 0.1e18,
      maxDamage: 0.2e18,
      attackRating: 2e18,
      defense: 2e18,
      blockRating: 0.1e18,
      life: 1.5e18,
      mana: 0.5e18
    }),

      levelUp: LevelUp({
      life: 2e18,
      mana: 1e18
    }),

      baseLifeChances: 5
    });
  }

  // --- HERO 2 (Spata) ---

  function initialHero2() internal view returns (InitialHero memory) {

    bool old = isNetworkWithOldSavage();

    return InitialHero({
      core: IStatController.CoreAttributes({
      strength: 30,
      dexterity: 5,
      vitality: 25,
      energy: 10
    }),

      multiplier: BaseMultiplier({
      minDamage: 0.15e18,
      maxDamage: old ? 0.25e18 : 0.5e18,
      attackRating: old ? 2e18 : 3e18,
      defense: 1e18,
      blockRating: 0.08e18,
      life: 1.3e18,
      mana: 0.5e18
    }),

      levelUp: LevelUp({
      life: 1.8e18,
      mana: 1e18
    }),

      baseLifeChances: 5
    });
  }

  // --- HERO 3 (Decidia) ---

  function initialHero3() internal pure returns (InitialHero memory) {
    return InitialHero({
      core: IStatController.CoreAttributes({
      strength: 10,
      dexterity: 15,
      vitality: 20,
      energy: 25
    }),

      multiplier: BaseMultiplier({
      minDamage: 0.1e18,
      maxDamage: 0.2e18,
      attackRating: 2e18,
      defense: 1e18,
      blockRating: 0.1e18,
      life: 1e18,
      mana: 2e18
    }),

      levelUp: LevelUp({
      life: 1.3e18,
      mana: 2e18
    }),

      baseLifeChances: 5
    });
  }

  // --- HERO 4 (Innatus) ---

  function initialHero4() internal pure returns (InitialHero memory) {
    return InitialHero({
      core: IStatController.CoreAttributes({
      strength: 15,
      dexterity: 25,
      vitality: 15,
      energy: 15
    }),

      multiplier: BaseMultiplier({
      minDamage: 0.1e18,
      maxDamage: 0.2e18,
      attackRating: 4e18,
      defense: 3e18,
      blockRating: 0.2e18,
      life: 1.2e18,
      mana: 1e18
    }),

      levelUp: LevelUp({
      life: 1.7e18,
      mana: 1.5e18
    }),

      baseLifeChances: 5
    });
  }

  // --- HERO 5 (F2P) ---

  function initialHero5() internal pure returns (InitialHero memory) {
    return InitialHero({
      core: IStatController.CoreAttributes({
      strength: 20,
      dexterity: 20,
      vitality: 20,
      energy: 10
    }),

      multiplier: BaseMultiplier({
      minDamage: 0.15e18,
      maxDamage: 0.25e18,
      attackRating: 3e18,
      defense: 2.5e18,
      blockRating: 0.15e18,
      life: 1.5e18,
      mana: 1.5e18
    }),

      levelUp: LevelUp({
      life: 1.5e18,
      mana: 1.5e18
    }),

      baseLifeChances: 1
    });
  }

  // --- HERO 6 (F2P) HAMMERGINA ---

  function initialHero6() internal pure returns (InitialHero memory) {
    return InitialHero({
      core: IStatController.CoreAttributes({
      strength: 50,
      dexterity: 30,
      vitality: 50,
      energy: 15
    }),

      multiplier: BaseMultiplier({
      minDamage: 0.2e18,
      maxDamage: 0.3e18,
      attackRating: 5e18,
      defense: 3e18,
      blockRating: 0.15e18,
      life: 2e18,
      mana: 2e18
    }),

      levelUp: LevelUp({
      life: 1.7e18,
      mana: 1.5e18
    }),

      baseLifeChances: 1
    });
  }

  // ------

  function initialHero(uint heroClass) internal view returns (InitialHero memory) {
    if (heroClass == 1) {
      return initialHero1();
    } else if (heroClass == 2) {
      return initialHero2();
    } else if (heroClass == 3) {
      return initialHero3();
    } else if (heroClass == 4) {
      return initialHero4();
    } else if (heroClass == 5) {
      return initialHero5();
    } else if (heroClass == 6) {
      return initialHero6();
    } else {
      revert IAppErrors.UnknownHeroClass(heroClass);
    }
  }
  //endregion --------------------------- BASE

  //region --------------------------- CALCULATIONS

  function minDamage(int32 strength, uint heroClass) internal view returns (int32) {
    return int32(int(strength.toUint() * initialHero(heroClass).multiplier.minDamage / _PRECISION));
  }

  function maxDamage(int32 strength, uint heroClass) internal view returns (int32){
    return int32(int(strength.toUint() * initialHero(heroClass).multiplier.maxDamage / _PRECISION));
  }

  function attackRating(int32 dexterity, uint heroClass) internal view returns (int32){
    return int32(int(dexterity.toUint() * initialHero(heroClass).multiplier.attackRating / _PRECISION));
  }

  function defense(int32 dexterity, uint heroClass) internal view returns (int32){
    return int32(int(dexterity.toUint() * initialHero(heroClass).multiplier.defense / _PRECISION));
  }

  function blockRating(int32 dexterity, uint heroClass) internal view returns (int32){
    return int32(int(Math.min((dexterity.toUint() * initialHero(heroClass).multiplier.blockRating / _PRECISION), 75)));
  }

  function life(int32 vitality, uint heroClass, uint32 level) internal view returns (int32){
    return int32(int(
      (vitality.toUint() * initialHero(heroClass).multiplier.life / _PRECISION)
      + (uint(level) * initialHero(heroClass).levelUp.life / _PRECISION)
    ));
  }

  function mana(int32 energy, uint heroClass, uint32 level) internal view returns (int32){
    return int32(int(
      (energy.toUint() * initialHero(heroClass).multiplier.mana / _PRECISION)
      + (uint(level) * initialHero(heroClass).levelUp.mana / _PRECISION)
    ));
  }

  function lifeChances(uint heroClass, uint32 /*level*/) internal view returns (int32){
    return initialHero(heroClass).baseLifeChances;
  }

  function levelExperience(uint32 level) internal pure returns (uint32) {
    if (level == 0 || level >= MAX_LEVEL) {
      return 0;
    }
    return uint32(uint(level) * BASE_EXPERIENCE * (67e17 - CalcLib.log2((uint(MAX_LEVEL - level + 2)) * 1e18)) / 1e18);
  }

  function chanceToHit(
    uint attackersAttackRating,
    uint defendersDefenceRating,
    uint attackersLevel,
    uint defendersLevel,
    uint arFactor
  ) internal pure returns (uint) {
    attackersAttackRating += attackersAttackRating * arFactor / 100;
    uint x = Math.max(attackersAttackRating, 1);
    uint y = Math.max(attackersAttackRating + defendersDefenceRating, 1);
    uint z = attackersLevel;
    uint k = defendersLevel / 2;
    uint xy = x * 1e18 / y;
    uint zk = z * 1e18 / (attackersLevel + k);
    uint base = 2 * xy * zk / 1e18;
    return Math.max(Math.min(base, 0.95e18), 0.2e18);
  }

  function experienceToVirtualLevel(uint experience, uint startFromLevel) internal pure returns (uint level) {
    level = startFromLevel;
    for (; level < MAX_LEVEL;) {
      if (levelExperience(uint32(level)) >= (experience + 1)) {
        break;
      }
      unchecked{++level;}
    }
  }

  function expPerMonster(uint32 monsterExp, uint monsterRarity, uint32 /*heroExp*/, uint32 /*heroCurrentLvl*/, uint /*monsterBiome*/) internal pure returns (uint32) {
    // do not reduce exp per level, it is no economical sense
    return uint32(uint(monsterExp) + uint(monsterExp) * monsterRarity / _MAX_AMPLIFIER);
  }

  /// @notice Allow to calculate delta param for {mintDropChance}
  function mintDropChanceDelta(uint heroCurrentExp, uint heroCurrentLevel, uint monsterBiome) internal pure returns (uint) {
    uint heroBiome = getVirtualLevel(heroCurrentExp, heroCurrentLevel, true) / StatLib.BIOME_LEVEL_STEP + 1;
    return heroBiome > monsterBiome ? 2 ** (heroBiome - monsterBiome + 10) : 0;
  }

  function getVirtualLevel(uint heroCurrentExp, uint heroCurrentLevel, bool withGap) internal pure returns (uint) {
    uint virtualLevel = StatLib.experienceToVirtualLevel(heroCurrentExp, heroCurrentLevel);
    if (withGap && (virtualLevel + 1) > VIRTUAL_LEVEL_GAP) {
      virtualLevel -= VIRTUAL_LEVEL_GAP;
    }
    return virtualLevel;
  }

  function initAttributes(
    bytes32[] storage attributes,
    uint heroClass,
    uint32 level,
    IStatController.CoreAttributes memory base
  ) internal returns (uint32[] memory result) {

    attributes.setInt32(uint(IStatController.ATTRIBUTES.STRENGTH), base.strength);
    attributes.setInt32(uint(IStatController.ATTRIBUTES.DEXTERITY), base.dexterity);
    attributes.setInt32(uint(IStatController.ATTRIBUTES.VITALITY), base.vitality);
    attributes.setInt32(uint(IStatController.ATTRIBUTES.ENERGY), base.energy);

    attributes.setInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MIN), minDamage(base.strength, heroClass));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MAX), maxDamage(base.strength, heroClass));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.ATTACK_RATING), attackRating(base.dexterity, heroClass));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.DEFENSE), defense(base.dexterity, heroClass));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.BLOCK_RATING), blockRating(base.dexterity, heroClass));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.LIFE), life(base.vitality, heroClass, level));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.MANA), mana(base.energy, heroClass, level));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.LIFE_CHANCES), lifeChances(heroClass, level));

    result = new uint32[](3);
    result[0] = uint32(life(base.vitality, heroClass, level).toUint());
    result[1] = uint32(mana(base.energy, heroClass, level).toUint());
    result[2] = uint32(lifeChances(heroClass, uint32(level)).toUint());
  }

  function updateCoreDependAttributesInMemory(
    int32[] memory attributes,
    int32[] memory bonus,
    uint heroClass,
    uint32 level
  ) internal view returns (int32[] memory) {
    int32 strength = attributes[uint(IStatController.ATTRIBUTES.STRENGTH)];
    int32 dexterity = attributes[uint(IStatController.ATTRIBUTES.DEXTERITY)];
    int32 vitality = attributes[uint(IStatController.ATTRIBUTES.VITALITY)];
    int32 energy = attributes[uint(IStatController.ATTRIBUTES.ENERGY)];

    attributes[uint(IStatController.ATTRIBUTES.DAMAGE_MIN)] = minDamage(strength, heroClass) + bonus[uint(IStatController.ATTRIBUTES.DAMAGE_MIN)];
    attributes[uint(IStatController.ATTRIBUTES.DAMAGE_MAX)] = maxDamage(strength, heroClass) + bonus[uint(IStatController.ATTRIBUTES.DAMAGE_MAX)];
    attributes[uint(IStatController.ATTRIBUTES.ATTACK_RATING)] = attackRating(dexterity, heroClass) + bonus[uint(IStatController.ATTRIBUTES.ATTACK_RATING)];
    attributes[uint(IStatController.ATTRIBUTES.DEFENSE)] = defense(dexterity, heroClass) + bonus[uint(IStatController.ATTRIBUTES.DEFENSE)];
    attributes[uint(IStatController.ATTRIBUTES.BLOCK_RATING)] = blockRating(dexterity, heroClass) + bonus[uint(IStatController.ATTRIBUTES.BLOCK_RATING)];
    attributes[uint(IStatController.ATTRIBUTES.LIFE)] = life(vitality, heroClass, level) + bonus[uint(IStatController.ATTRIBUTES.LIFE)];
    attributes[uint(IStatController.ATTRIBUTES.MANA)] = mana(energy, heroClass, level) + bonus[uint(IStatController.ATTRIBUTES.MANA)];
    return attributes;
  }

  function updateCoreDependAttributes(
    IController controller,
    bytes32[] storage attributes,
    bytes32[] storage bonusMain,
    bytes32[] storage bonusExtra,
    IStatController.ChangeableStats memory _heroStats,
    uint index,
    address heroToken,
    int32 base
  ) internal {
    uint heroClass = IHeroController(controller.heroController()).heroClass(heroToken);
    if (index == uint(IStatController.ATTRIBUTES.STRENGTH)) {

      attributes.setInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MIN),
        StatLib.minDamage(base, heroClass)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MIN))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MIN))
      );
      attributes.setInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MAX),
        StatLib.maxDamage(base, heroClass)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MAX))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MAX))
      );
    } else if (index == uint(IStatController.ATTRIBUTES.DEXTERITY)) {

      attributes.setInt32(uint(IStatController.ATTRIBUTES.ATTACK_RATING),
        StatLib.attackRating(base, heroClass)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.ATTACK_RATING))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.ATTACK_RATING))
      );

      attributes.setInt32(uint(IStatController.ATTRIBUTES.DEFENSE),
        StatLib.defense(base, heroClass)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.DEFENSE))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.DEFENSE))
      );

      attributes.setInt32(uint(IStatController.ATTRIBUTES.BLOCK_RATING),
        StatLib.blockRating(base, heroClass)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.BLOCK_RATING))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.BLOCK_RATING))
      );
    } else if (index == uint(IStatController.ATTRIBUTES.VITALITY)) {

      attributes.setInt32(uint(IStatController.ATTRIBUTES.LIFE),
        StatLib.life(base, heroClass, _heroStats.level)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.LIFE))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.LIFE))
      );
    } else if (index == uint(IStatController.ATTRIBUTES.ENERGY)) {

      attributes.setInt32(uint(IStatController.ATTRIBUTES.MANA),
        StatLib.mana(base, heroClass, _heroStats.level)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.MANA))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.MANA))
      );
    }
  }

  function attributesAdd(int32[] memory base, int32[] memory add) internal pure returns (int32[] memory) {
    unchecked{
      for (uint i; i < base.length; ++i) {
        base[i] += add[i];
      }
    }
    return base;
  }

// Currently this function is not used
//  function attributesRemove(int32[] memory base, int32[] memory remove) internal pure returns (int32[] memory) {
//    unchecked{
//      for (uint i; i < base.length; ++i) {
//        base[i] = CalcLib.minusWithMinFloorI32(base[i], remove[i]);
//      }
//    }
//    return base;
//  }

  function packChangeableStats(IStatController.ChangeableStats memory stats) internal pure returns (bytes32) {
    uint32[] memory cData = new uint32[](5);
    cData[0] = stats.level;
    cData[1] = stats.experience;
    cData[2] = stats.life;
    cData[3] = stats.mana;
    cData[4] = stats.lifeChances;

    return cData.packUint32Array();
  }

  function unpackChangeableStats(bytes32 data) internal pure returns (IStatController.ChangeableStats memory result) {
    uint32[] memory cData = data.unpackUint32Array();
    return IStatController.ChangeableStats({
      level: cData[0],
      experience: cData[1],
      life: cData[2],
      mana: cData[3],
      lifeChances: cData[4]
    });
  }

  function bytesToFullAttributesArray(bytes32[] memory attributes) internal pure returns (int32[] memory result) {
    (int32[] memory values, uint8[] memory ids) = attributes.toInt32ArrayWithIds();
    return valuesToFullAttributesArray(values, ids);
  }

  function valuesToFullAttributesArray(int32[] memory values, uint8[] memory ids) internal pure returns (int32[] memory result) {
    result = new int32[](uint(IStatController.ATTRIBUTES.END_SLOT));
    for (uint i; i < values.length; ++i) {
      int32 value = values[i];
      if (value != 0) {
        result[ids[i]] = value;
      }
    }
  }
  //endregion --------------------------- CALCULATIONS

}

File 40 of 48 : StringLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;


library StringLib {

  /// @dev Inspired by OraclizeAPI's implementation - MIT license
  ///      https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
  function toString(uint value) external pure returns (string memory) {
    return _toString(value);
  }

  function _toString(uint value) internal pure returns (string memory) {
    if (value == 0) {
      return "0";
    }
    uint temp = value;
    uint digits;
    while (temp != 0) {
      digits++;
      temp /= 10;
    }
    bytes memory buffer = new bytes(digits);
    while (value != 0) {
      digits -= 1;
      buffer[digits] = bytes1(uint8(48 + uint(value % 10)));
      value /= 10;
    }
    return string(buffer);
  }

  function toAsciiString(address x) external pure returns (string memory) {
    return _toAsciiString(x);
  }

  function _toAsciiString(address x) internal pure returns (string memory) {
    bytes memory s = new bytes(40);
    for (uint i = 0; i < 20; i++) {
      bytes1 b = bytes1(uint8(uint(uint160(x)) / (2 ** (8 * (19 - i)))));
      bytes1 hi = bytes1(uint8(b) / 16);
      bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi));
      s[2 * i] = _char(hi);
      s[2 * i + 1] = _char(lo);
    }
    return string(s);
  }

  function char(bytes1 b) external pure returns (bytes1 c) {
    return _char(b);
  }

  function _char(bytes1 b) internal pure returns (bytes1 c) {
    if (uint8(b) < 10) return bytes1(uint8(b) + 0x30);
    else return bytes1(uint8(b) + 0x57);
  }

  function concat(string memory a, string memory b) internal pure returns (string memory) {
    return string(abi.encodePacked(a, b));
  }

  function isASCIILettersOnly(string memory str) internal pure returns (bool) {
    bytes memory b = bytes(str);
    for (uint i = 0; i < b.length; i++) {
      if (uint8(b[i]) < 32 || uint8(b[i]) > 127) {
        return false;
      }
    }
    return true;
  }
}

File 41 of 48 : EnumerableMap.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js.

pragma solidity ^0.8.20;

import {EnumerableSet} from "./EnumerableSet.sol";

/**
 * @dev Library for managing an enumerable variant of Solidity's
 * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
 * type.
 *
 * Maps have the following properties:
 *
 * - Entries are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Entries are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableMap for EnumerableMap.UintToAddressMap;
 *
 *     // Declare a set state variable
 *     EnumerableMap.UintToAddressMap private myMap;
 * }
 * ```
 *
 * The following map types are supported:
 *
 * - `uint256 -> address` (`UintToAddressMap`) since v3.0.0
 * - `address -> uint256` (`AddressToUintMap`) since v4.6.0
 * - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0
 * - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0
 * - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableMap.
 * ====
 */
library EnumerableMap {
  using EnumerableSet for EnumerableSet.Bytes32Set;

  // To implement this library for multiple types with as little code repetition as possible, we write it in
  // terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions,
  // and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map.
  // This means that we can only create new EnumerableMaps for types that fit in bytes32.

  /**
   * @dev Query for a nonexistent map key.
     */
  error EnumerableMapNonexistentKey(bytes32 key);

  struct Bytes32ToBytes32Map {
    // Storage of keys
    EnumerableSet.Bytes32Set _keys;
    mapping(bytes32 key => bytes32) _values;
  }

  /**
   * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
  function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) {
    map._values[key] = value;
    return map._keys.add(key);
  }

  /**
   * @dev Removes a key-value pair from a map. O(1).
     *
     * Returns true if the key was removed from the map, that is if it was present.
     */
  function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) {
    delete map._values[key];
    return map._keys.remove(key);
  }

  /**
   * @dev Returns true if the key is in the map. O(1).
     */
  function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) {
    return map._keys.contains(key);
  }

  /**
   * @dev Returns the number of key-value pairs in the map. O(1).
     */
  function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) {
    return map._keys.length();
  }

  /**
   * @dev Returns the key-value pair stored at position `index` in the map. O(1).
     *
     * Note that there are no guarantees on the ordering of entries inside the
     * array, and it may change when more entries are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) {
    bytes32 key = map._keys.at(index);
    return (key, map._values[key]);
  }

  /**
   * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
  function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) {
    bytes32 value = map._values[key];
    if (value == bytes32(0)) {
      return (contains(map, key), bytes32(0));
    } else {
      return (true, value);
    }
  }

  /**
   * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
  function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) {
    bytes32 value = map._values[key];
    if (value == 0 && !contains(map, key)) {
      revert EnumerableMapNonexistentKey(key);
    }
    return value;
  }

  /**
   * @dev Return the an array containing all the keys
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) {
    return map._keys.values();
  }

  // UintToUintMap

  struct UintToUintMap {
    Bytes32ToBytes32Map _inner;
  }

  /**
   * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
  function set(UintToUintMap storage map, uint256 key, uint256 value) internal returns (bool) {
    return set(map._inner, bytes32(key), bytes32(value));
  }

  /**
   * @dev Removes a value from a map. O(1).
     *
     * Returns true if the key was removed from the map, that is if it was present.
     */
  function remove(UintToUintMap storage map, uint256 key) internal returns (bool) {
    return remove(map._inner, bytes32(key));
  }

  /**
   * @dev Returns true if the key is in the map. O(1).
     */
  function contains(UintToUintMap storage map, uint256 key) internal view returns (bool) {
    return contains(map._inner, bytes32(key));
  }

  /**
   * @dev Returns the number of elements in the map. O(1).
     */
  function length(UintToUintMap storage map) internal view returns (uint256) {
    return length(map._inner);
  }

  /**
   * @dev Returns the element stored at position `index` in the map. O(1).
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) {
    (bytes32 key, bytes32 value) = at(map._inner, index);
    return (uint256(key), uint256(value));
  }

  /**
   * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
  function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) {
    (bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
    return (success, uint256(value));
  }

  /**
   * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
  function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) {
    return uint256(get(map._inner, bytes32(key)));
  }

  /**
   * @dev Return the an array containing all the keys
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function keys(UintToUintMap storage map) internal view returns (uint256[] memory) {
    bytes32[] memory store = keys(map._inner);
    uint256[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }

  // UintToAddressMap

  struct UintToAddressMap {
    Bytes32ToBytes32Map _inner;
  }

  /**
   * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
  function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) {
    return set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
  }

  /**
   * @dev Removes a value from a map. O(1).
     *
     * Returns true if the key was removed from the map, that is if it was present.
     */
  function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) {
    return remove(map._inner, bytes32(key));
  }

  /**
   * @dev Returns true if the key is in the map. O(1).
     */
  function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) {
    return contains(map._inner, bytes32(key));
  }

  /**
   * @dev Returns the number of elements in the map. O(1).
     */
  function length(UintToAddressMap storage map) internal view returns (uint256) {
    return length(map._inner);
  }

  /**
   * @dev Returns the element stored at position `index` in the map. O(1).
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) {
    (bytes32 key, bytes32 value) = at(map._inner, index);
    return (uint256(key), address(uint160(uint256(value))));
  }

  /**
   * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
  function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) {
    (bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
    return (success, address(uint160(uint256(value))));
  }

  /**
   * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
  function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
    return address(uint160(uint256(get(map._inner, bytes32(key)))));
  }

  /**
   * @dev Return the an array containing all the keys
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function keys(UintToAddressMap storage map) internal view returns (uint256[] memory) {
    bytes32[] memory store = keys(map._inner);
    uint256[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }

  // AddressToUintMap

  struct AddressToUintMap {
    Bytes32ToBytes32Map _inner;
  }

  /**
   * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
  function set(AddressToUintMap storage map, address key, uint256 value) internal returns (bool) {
    return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value));
  }

  /**
   * @dev Removes a value from a map. O(1).
     *
     * Returns true if the key was removed from the map, that is if it was present.
     */
  function remove(AddressToUintMap storage map, address key) internal returns (bool) {
    return remove(map._inner, bytes32(uint256(uint160(key))));
  }

  /**
   * @dev Returns true if the key is in the map. O(1).
     */
  function contains(AddressToUintMap storage map, address key) internal view returns (bool) {
    return contains(map._inner, bytes32(uint256(uint160(key))));
  }

  /**
   * @dev Returns the number of elements in the map. O(1).
     */
  function length(AddressToUintMap storage map) internal view returns (uint256) {
    return length(map._inner);
  }

  /**
   * @dev Returns the element stored at position `index` in the map. O(1).
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) {
    (bytes32 key, bytes32 value) = at(map._inner, index);
    return (address(uint160(uint256(key))), uint256(value));
  }

  /**
   * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
  function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) {
    (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key))));
    return (success, uint256(value));
  }

  /**
   * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
  function get(AddressToUintMap storage map, address key) internal view returns (uint256) {
    return uint256(get(map._inner, bytes32(uint256(uint160(key)))));
  }

  /**
   * @dev Return the an array containing all the keys
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function keys(AddressToUintMap storage map) internal view returns (address[] memory) {
    bytes32[] memory store = keys(map._inner);
    address[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }

  // Bytes32ToUintMap

  struct Bytes32ToUintMap {
    Bytes32ToBytes32Map _inner;
  }

  /**
   * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
  function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) {
    return set(map._inner, key, bytes32(value));
  }

  /**
   * @dev Removes a value from a map. O(1).
     *
     * Returns true if the key was removed from the map, that is if it was present.
     */
  function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) {
    return remove(map._inner, key);
  }

  /**
   * @dev Returns true if the key is in the map. O(1).
     */
  function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) {
    return contains(map._inner, key);
  }

  /**
   * @dev Returns the number of elements in the map. O(1).
     */
  function length(Bytes32ToUintMap storage map) internal view returns (uint256) {
    return length(map._inner);
  }

  /**
   * @dev Returns the element stored at position `index` in the map. O(1).
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) {
    (bytes32 key, bytes32 value) = at(map._inner, index);
    return (key, uint256(value));
  }

  /**
   * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
  function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) {
    (bool success, bytes32 value) = tryGet(map._inner, key);
    return (success, uint256(value));
  }

  /**
   * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
  function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) {
    return uint256(get(map._inner, key));
  }

  /**
   * @dev Return the an array containing all the keys
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) {
    bytes32[] memory store = keys(map._inner);
    bytes32[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }
}

File 42 of 48 : EnumerableSet.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
  // To implement this library for multiple types with as little code
  // repetition as possible, we write it in terms of a generic Set type with
  // bytes32 values.
  // The Set implementation uses private functions, and user-facing
  // implementations (such as AddressSet) are just wrappers around the
  // underlying Set.
  // This means that we can only create new EnumerableSets for types that fit
  // in bytes32.

  struct Set {
    // Storage of set values
    bytes32[] _values;
    // Position is the index of the value in the `values` array plus 1.
    // Position 0 is used to mean a value is not in the set.
    mapping(bytes32 value => uint256) _positions;
  }

  /**
   * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
  function _add(Set storage set, bytes32 value) private returns (bool) {
    if (!_contains(set, value)) {
      set._values.push(value);
      // The value is stored at length-1, but we add 1 to all indexes
      // and use 0 as a sentinel value
      set._positions[value] = set._values.length;
      return true;
    } else {
      return false;
    }
  }

  /**
   * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
  function _remove(Set storage set, bytes32 value) private returns (bool) {
    // We cache the value's position to prevent multiple reads from the same storage slot
    uint256 position = set._positions[value];

    if (position != 0) {
      // Equivalent to contains(set, value)
      // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
      // the array, and then remove the last element (sometimes called as 'swap and pop').
      // This modifies the order of the array, as noted in {at}.

      uint256 valueIndex = position - 1;
      uint256 lastIndex = set._values.length - 1;

      if (valueIndex != lastIndex) {
        bytes32 lastValue = set._values[lastIndex];

        // Move the lastValue to the index where the value to delete is
        set._values[valueIndex] = lastValue;
        // Update the tracked position of the lastValue (that was just moved)
        set._positions[lastValue] = position;
      }

      // Delete the slot where the moved value was stored
      set._values.pop();

      // Delete the tracked position for the deleted slot
      delete set._positions[value];

      return true;
    } else {
      return false;
    }
  }

  /**
   * @dev Returns true if the value is in the set. O(1).
     */
  function _contains(Set storage set, bytes32 value) private view returns (bool) {
    return set._positions[value] != 0;
  }

  /**
   * @dev Returns the number of values on the set. O(1).
     */
  function _length(Set storage set) private view returns (uint256) {
    return set._values.length;
  }

  /**
   * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function _at(Set storage set, uint256 index) private view returns (bytes32) {
    return set._values[index];
  }

  /**
   * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function _values(Set storage set) private view returns (bytes32[] memory) {
    return set._values;
  }

  // Bytes32Set

  struct Bytes32Set {
    Set _inner;
  }

  /**
   * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
  function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
    return _add(set._inner, value);
  }

  /**
   * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
  function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
    return _remove(set._inner, value);
  }

  /**
   * @dev Returns true if the value is in the set. O(1).
     */
  function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
    return _contains(set._inner, value);
  }

  /**
   * @dev Returns the number of values in the set. O(1).
     */
  function length(Bytes32Set storage set) internal view returns (uint256) {
    return _length(set._inner);
  }

  /**
   * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
    return _at(set._inner, index);
  }

  /**
   * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
    bytes32[] memory store = _values(set._inner);
    bytes32[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }

  // AddressSet

  struct AddressSet {
    Set _inner;
  }

  /**
   * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
  function add(AddressSet storage set, address value) internal returns (bool) {
    return _add(set._inner, bytes32(uint256(uint160(value))));
  }

  /**
   * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
  function remove(AddressSet storage set, address value) internal returns (bool) {
    return _remove(set._inner, bytes32(uint256(uint160(value))));
  }

  /**
   * @dev Returns true if the value is in the set. O(1).
     */
  function contains(AddressSet storage set, address value) internal view returns (bool) {
    return _contains(set._inner, bytes32(uint256(uint160(value))));
  }

  /**
   * @dev Returns the number of values in the set. O(1).
     */
  function length(AddressSet storage set) internal view returns (uint256) {
    return _length(set._inner);
  }

  /**
   * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(AddressSet storage set, uint256 index) internal view returns (address) {
    return address(uint160(uint256(_at(set._inner, index))));
  }

  /**
   * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function values(AddressSet storage set) internal view returns (address[] memory) {
    bytes32[] memory store = _values(set._inner);
    address[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }

  // UintSet

  struct UintSet {
    Set _inner;
  }

  /**
   * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
  function add(UintSet storage set, uint256 value) internal returns (bool) {
    return _add(set._inner, bytes32(value));
  }

  /**
   * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
  function remove(UintSet storage set, uint256 value) internal returns (bool) {
    return _remove(set._inner, bytes32(value));
  }

  /**
   * @dev Returns true if the value is in the set. O(1).
     */
  function contains(UintSet storage set, uint256 value) internal view returns (bool) {
    return _contains(set._inner, bytes32(value));
  }

  /**
   * @dev Returns the number of values in the set. O(1).
     */
  function length(UintSet storage set) internal view returns (uint256) {
    return _length(set._inner);
  }

  /**
   * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(UintSet storage set, uint256 index) internal view returns (uint256) {
    return uint256(_at(set._inner, index));
  }

  /**
   * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function values(UintSet storage set) internal view returns (uint256[] memory) {
    bytes32[] memory store = _values(set._inner);
    uint256[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }
}

File 43 of 48 : Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
  /**
   * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
  struct InitializableStorage {
    /**
     * @dev Indicates that the contract has been initialized.
         */
    uint64 _initialized;
    /**
     * @dev Indicates that the contract is in the process of being initialized.
         */
    bool _initializing;
  }

  // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
  bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

  /**
   * @dev The contract is already initialized.
     */
  error InvalidInitialization();

  /**
   * @dev The contract is not initializing.
     */
  error NotInitializing();

  /**
   * @dev Triggered when the contract has been initialized or reinitialized.
     */
  event Initialized(uint64 version);

  /**
   * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
  modifier initializer() {
    // solhint-disable-next-line var-name-mixedcase
    InitializableStorage storage $ = _getInitializableStorage();

    // Cache values to avoid duplicated sloads
    bool isTopLevelCall = !$._initializing;
    uint64 initialized = $._initialized;

    // Allowed calls:
    // - initialSetup: the contract is not in the initializing state and no previous version was
    //                 initialized
    // - construction: the contract is initialized at version 1 (no reininitialization) and the
    //                 current contract is just being deployed
    bool initialSetup = initialized == 0 && isTopLevelCall;
    bool construction = initialized == 1 && address(this).code.length == 0;

    if (!initialSetup && !construction) {
      revert InvalidInitialization();
    }
    $._initialized = 1;
    if (isTopLevelCall) {
      $._initializing = true;
    }
    _;
    if (isTopLevelCall) {
      $._initializing = false;
      emit Initialized(1);
    }
  }

  /**
   * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
  modifier reinitializer(uint64 version) {
    // solhint-disable-next-line var-name-mixedcase
    InitializableStorage storage $ = _getInitializableStorage();

    if ($._initializing || $._initialized >= version) {
      revert InvalidInitialization();
    }
    $._initialized = version;
    $._initializing = true;
    _;
    $._initializing = false;
    emit Initialized(version);
  }

  /**
   * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
  modifier onlyInitializing() {
    _checkInitializing();
    _;
  }

  /**
   * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
  function _checkInitializing() internal view virtual {
    if (!_isInitializing()) {
      revert NotInitializing();
    }
  }

  /**
   * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
  function _disableInitializers() internal virtual {
    // solhint-disable-next-line var-name-mixedcase
    InitializableStorage storage $ = _getInitializableStorage();

    if ($._initializing) {
      revert InvalidInitialization();
    }
    if ($._initialized != type(uint64).max) {
      $._initialized = type(uint64).max;
      emit Initialized(type(uint64).max);
    }
  }

  /**
   * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
  function _getInitializedVersion() internal view returns (uint64) {
    return _getInitializableStorage()._initialized;
  }

  /**
   * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
  function _isInitializing() internal view returns (bool) {
    return _getInitializableStorage()._initializing;
  }

  /**
   * @dev Returns a pointer to the storage namespace.
     */
  // solhint-disable-next-line var-name-mixedcase
  function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
    assembly {
      $.slot := INITIALIZABLE_STORAGE
    }
  }
}

File 44 of 48 : Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {

  /**
    * @dev Muldiv operation overflow.
   */
  error MathOverflowedMulDiv();

  enum Rounding {
    Floor, // Toward negative infinity
    Ceil, // Toward positive infinity
    Trunc, // Toward zero
    Expand // Away from zero
  }

  /**
   * @dev Returns the addition of two unsigned integers, with an success flag (no overflow).
     */
  function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    unchecked {
      uint256 c = a + b;
      if (c < a) return (false, 0);
      return (true, c);
    }
  }

  /**
   * @dev Returns the subtraction of two unsigned integers, with an success flag (no overflow).
     */
  function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    unchecked {
      if (b > a) return (false, 0);
      return (true, a - b);
    }
  }

  /**
   * @dev Returns the multiplication of two unsigned integers, with an success flag (no overflow).
     */
  function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    unchecked {
    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
    // benefit is lost if 'b' is also tested.
    // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
      if (a == 0) return (true, 0);
      uint256 c = a * b;
      if (c / a != b) return (false, 0);
      return (true, c);
    }
  }

  /**
   * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
     */
  function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    unchecked {
      if (b == 0) return (false, 0);
      return (true, a / b);
    }
  }

  /**
   * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
     */
  function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    unchecked {
      if (b == 0) return (false, 0);
      return (true, a % b);
    }
  }

  /**
   * @dev Returns the largest of two numbers.
     */
  function max(uint256 a, uint256 b) internal pure returns (uint256) {
    return a > b ? a : b;
  }

  /**
   * @dev Returns the smallest of two numbers.
     */
  function min(uint256 a, uint256 b) internal pure returns (uint256) {
    return a < b ? a : b;
  }

  /**
   * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
  function average(uint256 a, uint256 b) internal pure returns (uint256) {
    // (a + b) / 2 can overflow.
    return (a & b) + (a ^ b) / 2;
  }

  /**
   * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
  function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
    if (b == 0) {
      // Guarantee the same behavior as in a regular Solidity division.
      return a / b;
    }

    // The following calculation ensures accurate ceiling division without overflow.
    // Since a is non-zero, (a - 1) / b will not overflow.
    // The largest possible result occurs when (a - 1) / b is type(uint256).max,
    // but the largest value we can obtain is type(uint256).max - 1, which happens
    // when a = type(uint256).max and b = 1.
    unchecked {
      return a == 0 ? 0 : (a - 1) / b + 1;
    }
  }

  /**
   * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
  function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
    unchecked {
    // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
    // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
    // variables such that product = prod1 * 2^256 + prod0.
      uint256 prod0 = x * y; // Least significant 256 bits of the product
      uint256 prod1; // Most significant 256 bits of the product
      assembly {
        let mm := mulmod(x, y, not(0))
        prod1 := sub(sub(mm, prod0), lt(mm, prod0))
      }

    // Handle non-overflow cases, 256 by 256 division.
      if (prod1 == 0) {
        // Solidity will revert if denominator == 0, unlike the div opcode on its own.
        // The surrounding unchecked block does not change this fact.
        // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
        return prod0 / denominator;
      }

    // Make sure the result is less than 2^256. Also prevents denominator == 0.
      if (denominator <= prod1) {
        revert MathOverflowedMulDiv();
      }

    ///////////////////////////////////////////////
    // 512 by 256 division.
    ///////////////////////////////////////////////

    // Make division exact by subtracting the remainder from [prod1 prod0].
      uint256 remainder;
      assembly {
      // Compute remainder using mulmod.
        remainder := mulmod(x, y, denominator)

      // Subtract 256 bit number from 512 bit number.
        prod1 := sub(prod1, gt(remainder, prod0))
        prod0 := sub(prod0, remainder)
      }

    // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
    // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

      uint256 twos = denominator & (0 - denominator);
      assembly {
      // Divide denominator by twos.
        denominator := div(denominator, twos)

      // Divide [prod1 prod0] by twos.
        prod0 := div(prod0, twos)

      // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
        twos := add(div(sub(0, twos), twos), 1)
      }

    // Shift in bits from prod1 into prod0.
      prod0 |= prod1 * twos;

    // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
    // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
    // four bits. That is, denominator * inv = 1 mod 2^4.
      uint256 inverse = (3 * denominator) ^ 2;

    // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
    // works in modular arithmetic, doubling the correct bits in each step.
      inverse *= 2 - denominator * inverse; // inverse mod 2^8
      inverse *= 2 - denominator * inverse; // inverse mod 2^16
      inverse *= 2 - denominator * inverse; // inverse mod 2^32
      inverse *= 2 - denominator * inverse; // inverse mod 2^64
      inverse *= 2 - denominator * inverse; // inverse mod 2^128
      inverse *= 2 - denominator * inverse; // inverse mod 2^256

    // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
    // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
    // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
    // is no longer required.
      result = prod0 * inverse;
      return result;
    }
  }

  /**
   * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
  function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
    uint256 result = mulDiv(x, y, denominator);
    if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
      result += 1;
    }
    return result;
  }

  /**
   * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
  function sqrt(uint256 a) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }

    // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
    //
    // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
    // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
    //
    // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
    // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
    // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
    //
    // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
    uint256 result = 1 << (log2(a) >> 1);

    // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
    // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
    // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
    // into the expected uint128 result.
    unchecked {
      result = (result + a / result) >> 1;
      result = (result + a / result) >> 1;
      result = (result + a / result) >> 1;
      result = (result + a / result) >> 1;
      result = (result + a / result) >> 1;
      result = (result + a / result) >> 1;
      result = (result + a / result) >> 1;
      return min(result, a / result);
    }
  }

  /**
   * @notice Calculates sqrt(a), following the selected rounding direction.
     */
  function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
    unchecked {
      uint256 result = sqrt(a);
      return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
    }
  }

  /**
   * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
  function log2(uint256 value) internal pure returns (uint256) {
    uint256 result = 0;
    unchecked {
      if (value >> 128 > 0) {
        value >>= 128;
        result += 128;
      }
      if (value >> 64 > 0) {
        value >>= 64;
        result += 64;
      }
      if (value >> 32 > 0) {
        value >>= 32;
        result += 32;
      }
      if (value >> 16 > 0) {
        value >>= 16;
        result += 16;
      }
      if (value >> 8 > 0) {
        value >>= 8;
        result += 8;
      }
      if (value >> 4 > 0) {
        value >>= 4;
        result += 4;
      }
      if (value >> 2 > 0) {
        value >>= 2;
        result += 2;
      }
      if (value >> 1 > 0) {
        result += 1;
      }
    }
    return result;
  }

  /**
   * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
  function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
    unchecked {
      uint256 result = log2(value);
      return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
    }
  }

  /**
   * @dev Return the log in base 10 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
  function log10(uint256 value) internal pure returns (uint256) {
    uint256 result = 0;
    unchecked {
      if (value >= 10 ** 64) {
        value /= 10 ** 64;
        result += 64;
      }
      if (value >= 10 ** 32) {
        value /= 10 ** 32;
        result += 32;
      }
      if (value >= 10 ** 16) {
        value /= 10 ** 16;
        result += 16;
      }
      if (value >= 10 ** 8) {
        value /= 10 ** 8;
        result += 8;
      }
      if (value >= 10 ** 4) {
        value /= 10 ** 4;
        result += 4;
      }
      if (value >= 10 ** 2) {
        value /= 10 ** 2;
        result += 2;
      }
      if (value >= 10 ** 1) {
        result += 1;
      }
    }
    return result;
  }

  /**
   * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
  function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
    unchecked {
      uint256 result = log10(value);
      return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
    }
  }

  /**
   * @dev Return the log in base 256 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
  function log256(uint256 value) internal pure returns (uint256) {
    uint256 result = 0;
    unchecked {
      if (value >> 128 > 0) {
        value >>= 128;
        result += 16;
      }
      if (value >> 64 > 0) {
        value >>= 64;
        result += 8;
      }
      if (value >> 32 > 0) {
        value >>= 32;
        result += 4;
      }
      if (value >> 16 > 0) {
        value >>= 16;
        result += 2;
      }
      if (value >> 8 > 0) {
        result += 1;
      }
    }
    return result;
  }

  /**
   * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
  function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
    unchecked {
      uint256 result = log256(value);
      return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
    }
  }

  /**
   * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
  function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
    return uint8(rounding) % 2 == 1;
  }

}

File 45 of 48 : Controllable.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "../openzeppelin/Initializable.sol";
import "../interfaces/IControllable.sol";
import "../interfaces/IController.sol";
import "../lib/SlotsLib.sol";

/// @title Implement basic functionality for any contract that require strict control
/// @dev Can be used with upgradeable pattern.
///      Require call __Controllable_init() in any case.
/// @author belbix
abstract contract Controllable is Initializable, IControllable {
  using SlotsLib for bytes32;

  /// @notice Version of the contract
  /// @dev Should be incremented when contract changed
  string public constant CONTROLLABLE_VERSION = "1.0.0";

  bytes32 internal constant _CONTROLLER_SLOT = bytes32(uint256(keccak256("eip1967.controllable.controller")) - 1);
  bytes32 internal constant _CREATED_SLOT = bytes32(uint256(keccak256("eip1967.controllable.created")) - 1);
  bytes32 internal constant _CREATED_BLOCK_SLOT = bytes32(uint256(keccak256("eip1967.controllable.created_block")) - 1);
  bytes32 internal constant _REVISION_SLOT = bytes32(uint256(keccak256("eip1967.controllable.revision")) - 1);
  bytes32 internal constant _PREVIOUS_LOGIC_SLOT = bytes32(uint256(keccak256("eip1967.controllable.prev_logic")) - 1);

  event ContractInitialized(address controller, uint ts, uint block);
  event RevisionIncreased(uint value, address oldLogic);

  // init implementation contract
  constructor() initializer {}

  /// @notice Initialize contract after setup it as proxy implementation
  ///         Save block.timestamp in the "created" variable
  /// @dev Use it only once after first logic setup
  /// @param controller_ Controller address
  function __Controllable_init(address controller_) internal onlyInitializing {
    _init(controller_);
  }

  function _init(address controller_) private {
    require(controller_ != address(0), "Zero controller");
    _CONTROLLER_SLOT.set(controller_);
    _CREATED_SLOT.set(block.timestamp);
    _CREATED_BLOCK_SLOT.set(block.number);
    emit ContractInitialized(controller_, block.timestamp, block.number);
  }

  /// @dev Return true if given address is controller
  function isController(address value_) public override view returns (bool) {
    return value_ == controller();
  }

  /// @notice Return true if given address is setup as governance in Controller
  function isGovernance(address value_) public override view returns (bool) {
    return IController(controller()).governance() == value_;
  }

  /// @dev Contract upgrade counter
  function revision() external view override returns (uint) {
    return _REVISION_SLOT.getUint();
  }

  /// @dev Previous logic implementation
  function previousImplementation() external view override returns (address) {
    return _PREVIOUS_LOGIC_SLOT.getAddress();
  }

  // ************* SETTERS/GETTERS *******************

  /// @notice Return controller address saved in the contract slot
  function controller() public view override returns (address) {
    return _CONTROLLER_SLOT.getAddress();
  }

  /// @notice Return creation timestamp
  /// @return Creation timestamp
  function created() external view override returns (uint256) {
    return _CREATED_SLOT.getUint();
  }

  /// @notice Return creation block number
  /// @return Creation block number
  function createdBlock() external override view returns (uint256) {
    return _CREATED_BLOCK_SLOT.getUint();
  }

  /// @dev Revision should be increased on each contract upgrade
  function increaseRevision(address oldLogic) external override {
    require(msg.sender == address(this), "Increase revision forbidden");
    uint r = _REVISION_SLOT.getUint() + 1;
    _REVISION_SLOT.set(r);
    _PREVIOUS_LOGIC_SLOT.set(oldLogic);
    emit RevisionIncreased(r, oldLogic);
  }

}

File 46 of 48 : ERC2771Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (metatx/ERC2771Context.sol)

pragma solidity ^0.8.1;

import "../interfaces/IAppErrors.sol";

/**
 * @dev Context variant with ERC2771 support.
 */
// based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/metatx/ERC2771Context.sol
abstract contract ERC2771Context {
  // for whitelist new relayers need to add new constants and update proxies
  address private constant GELATO_RELAY_1_BALANCE_ERC_2771 = 0xd8253782c45a12053594b9deB72d8e8aB2Fca54c;
  address private constant SACRA_RELAY = 0x52CEba41Da235Af367bFC0b0cCd3314cb901bB5F;
  address private constant SACRA_RELAY_2 = 0x102f1f556cD9C3D5f820E6920A8931657c5Da21B;

  function isTrustedForwarder(address forwarder) public view virtual returns (bool){
    return forwarder == GELATO_RELAY_1_BALANCE_ERC_2771 || forwarder == SACRA_RELAY || forwarder == SACRA_RELAY_2;
  }

  function _msgSender() internal view virtual returns (address sender) {
    if (isTrustedForwarder(msg.sender)) {
      // The assembly code is more direct than the Solidity version using `abi.decode`.
      /// @solidity memory-safe-assembly
      assembly {
        sender := shr(96, calldataload(sub(calldatasize(), 20)))
      }
      return sender;
    } else {
      return msg.sender;
    }
  }

  function _msgData() internal view virtual returns (bytes calldata) {
    if (isTrustedForwarder(msg.sender)) {
      return msg.data[: msg.data.length - 20];
    } else {
      return msg.data;
    }
  }

  /// @notice Return true if given address is not a smart contract but a wallet address.
  /// @dev It is not 100% guarantee after EIP-3074 implementation, use it as an additional check.
  /// @return true if the address is a wallet.
  function _isNotSmartContract() internal view returns (bool) {
    return isTrustedForwarder(msg.sender) || msg.sender == tx.origin;
  }

  function onlyEOA() internal view {
    if (!_isNotSmartContract()) {
      revert IAppErrors.NotEOA(msg.sender);
    }
  }
}

File 47 of 48 : LibPRNG.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Library for generating pseudorandom numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibPRNG.sol)
/// @author LazyShuffler based on NextShuffler by aschlosberg (divergencearran)
/// (https://github.com/divergencetech/ethier/blob/main/contracts/random/NextShuffler.sol)
library LibPRNG {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The initial length must be greater than zero and less than `2**32 - 1`.
    error InvalidInitialLazyShufflerLength();

    /// @dev The new length must not be less than the current length.
    error InvalidNewLazyShufflerLength();

    /// @dev The lazy shuffler has not been initialized.
    error LazyShufflerNotInitialized();

    /// @dev Cannot double initialize the lazy shuffler.
    error LazyShufflerAlreadyInitialized();

    /// @dev The lazy shuffle has finished.
    error LazyShuffleFinished();

    /// @dev The queried index is out of bounds.
    error LazyShufflerGetOutOfBounds();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The scalar of ETH and most ERC20s.
    uint256 internal constant WAD = 1e18;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STRUCTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev A pseudorandom number state in memory.
    struct PRNG {
        uint256 state;
    }

    /// @dev A lazy Fisher-Yates shuffler for a range `[0..n)` in storage.
    struct LazyShuffler {
        // Bits Layout:
        // - [0..31]    `numShuffled`
        // - [32..223]  `permutationSlot`
        // - [224..255] `length`
        uint256 _state;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         OPERATIONS                         */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Seeds the `prng` with `state`.
    function seed(PRNG memory prng, uint256 state) internal pure {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(prng, state)
        }
    }

    /// @dev Returns the next pseudorandom uint256.
    /// All bits of the returned uint256 pass the NIST Statistical Test Suite.
    function next(PRNG memory prng) internal pure returns (uint256 result) {
        // We simply use `keccak256` for a great balance between
        // runtime gas costs, bytecode size, and statistical properties.
        //
        // A high-quality LCG with a 32-byte state
        // is only about 30% more gas efficient during runtime,
        // but requires a 32-byte multiplier, which can cause bytecode bloat
        // when this function is inlined.
        //
        // Using this method is about 2x more efficient than
        // `nextRandomness = uint256(keccak256(abi.encode(randomness)))`.
        /// @solidity memory-safe-assembly
        assembly {
            result := keccak256(prng, 0x20)
            mstore(prng, result)
        }
    }

    /// @dev Returns a pseudorandom uint256, uniformly distributed
    /// between 0 (inclusive) and `upper` (exclusive).
    /// If your modulus is big, this method is recommended
    /// for uniform sampling to avoid modulo bias.
    /// For uniform sampling across all uint256 values,
    /// or for small enough moduli such that the bias is neligible,
    /// use {next} instead.
    function uniform(PRNG memory prng, uint256 upper) internal pure returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            for {} 1 {} {
                result := keccak256(prng, 0x20)
                mstore(prng, result)
                if iszero(lt(result, mod(sub(0, upper), upper))) { break }
            }
            result := mod(result, upper)
        }
    }

    /// @dev Shuffles the array in-place with Fisher-Yates shuffle.
    function shuffle(PRNG memory prng, uint256[] memory a) internal pure {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(a)
            let w := not(0)
            let mask := shr(128, w)
            if n {
                for { a := add(a, 0x20) } 1 {} {
                    // We can just directly use `keccak256`, cuz
                    // the other approaches don't save much.
                    let r := keccak256(prng, 0x20)
                    mstore(prng, r)

                    // Note that there will be a very tiny modulo bias
                    // if the length of the array is not a power of 2.
                    // For all practical purposes, it is negligible
                    // and will not be a fairness or security concern.
                    {
                        let j := add(a, shl(5, mod(shr(128, r), n)))
                        n := add(n, w) // `sub(n, 1)`.
                        if iszero(n) { break }

                        let i := add(a, shl(5, n))
                        let t := mload(i)
                        mstore(i, mload(j))
                        mstore(j, t)
                    }

                    {
                        let j := add(a, shl(5, mod(and(r, mask), n)))
                        n := add(n, w) // `sub(n, 1)`.
                        if iszero(n) { break }

                        let i := add(a, shl(5, n))
                        let t := mload(i)
                        mstore(i, mload(j))
                        mstore(j, t)
                    }
                }
            }
        }
    }

    /// @dev Shuffles the bytes in-place with Fisher-Yates shuffle.
    function shuffle(PRNG memory prng, bytes memory a) internal pure {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(a)
            let w := not(0)
            let mask := shr(128, w)
            if n {
                let b := add(a, 0x01)
                for { a := add(a, 0x20) } 1 {} {
                    // We can just directly use `keccak256`, cuz
                    // the other approaches don't save much.
                    let r := keccak256(prng, 0x20)
                    mstore(prng, r)

                    // Note that there will be a very tiny modulo bias
                    // if the length of the array is not a power of 2.
                    // For all practical purposes, it is negligible
                    // and will not be a fairness or security concern.
                    {
                        let o := mod(shr(128, r), n)
                        n := add(n, w) // `sub(n, 1)`.
                        if iszero(n) { break }

                        let t := mload(add(b, n))
                        mstore8(add(a, n), mload(add(b, o)))
                        mstore8(add(a, o), t)
                    }

                    {
                        let o := mod(and(r, mask), n)
                        n := add(n, w) // `sub(n, 1)`.
                        if iszero(n) { break }

                        let t := mload(add(b, n))
                        mstore8(add(a, n), mload(add(b, o)))
                        mstore8(add(a, o), t)
                    }
                }
            }
        }
    }

    /// @dev Returns a sample from the standard normal distribution denominated in `WAD`.
    function standardNormalWad(PRNG memory prng) internal pure returns (int256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            // Technically, this is the Irwin-Hall distribution with 20 samples.
            // The chance of drawing a sample outside 10 σ from the standard normal distribution
            // is ≈ 0.000000000000000000000015, which is insignificant for most practical purposes.
            // Passes the Kolmogorov-Smirnov test for 200k samples. Uses about 322 gas.
            result := keccak256(prng, 0x20)
            mstore(prng, result)
            let n := 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43 // Prime.
            let a := 0x100000000000000000000000000000051 // Prime and a primitive root of `n`.
            let m := 0x1fffffffffffffff1fffffffffffffff1fffffffffffffff1fffffffffffffff
            let s := 0x1000000000000000100000000000000010000000000000001
            let r1 := mulmod(result, a, n)
            let r2 := mulmod(r1, a, n)
            let r3 := mulmod(r2, a, n)
            // forgefmt: disable-next-item
            result := sub(sar(96, mul(26614938895861601847173011183,
                add(add(shr(192, mul(s, add(and(m, result), and(m, r1)))),
                shr(192, mul(s, add(and(m, r2), and(m, r3))))),
                shr(192, mul(s, and(m, mulmod(r3, a, n))))))), 7745966692414833770)
        }
    }

    /// @dev Returns a sample from the unit exponential distribution denominated in `WAD`.
    function exponentialWad(PRNG memory prng) internal pure returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            // Passes the Kolmogorov-Smirnov test for 200k samples.
            // Gas usage varies, starting from about 172+ gas.
            let r := keccak256(prng, 0x20)
            mstore(prng, r)
            let p := shl(129, r)
            let w := shl(1, r)
            if iszero(gt(w, p)) {
                let n := 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43 // Prime.
                let a := 0x100000000000000000000000000000051 // Prime and a primitive root of `n`.
                for {} 1 {} {
                    r := mulmod(r, a, n)
                    if iszero(lt(shl(129, r), w)) {
                        r := mulmod(r, a, n)
                        result := add(1000000000000000000, result)
                        w := shl(1, r)
                        p := shl(129, r)
                        if iszero(lt(w, p)) { break }
                        continue
                    }
                    w := shl(1, r)
                    if iszero(lt(w, shl(129, r))) { break }
                }
            }
            result := add(div(p, shl(129, 170141183460469231732)), result)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*       STORAGE-BASED RANGE LAZY SHUFFLING OPERATIONS        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Initializes the state for lazy-shuffling the range `[0..n)`.
    /// Reverts if `n == 0 || n >= 2**32 - 1`.
    /// Reverts if `$` has already been initialized.
    /// If you need to reduce the length after initialization, just use a fresh new `$`.
    function initialize(LazyShuffler storage $, uint256 n) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(lt(sub(n, 1), 0xfffffffe)) {
                mstore(0x00, 0x83b53941) // `InvalidInitialLazyShufflerLength()`.
                revert(0x1c, 0x04)
            }
            if sload($.slot) {
                mstore(0x00, 0x0c9f11f2) // `LazyShufflerAlreadyInitialized()`.
                revert(0x1c, 0x04)
            }
            mstore(0x00, $.slot)
            sstore($.slot, or(shl(224, n), shl(32, shr(64, keccak256(0x00, 0x20)))))
        }
    }

    /// @dev Increases the length of `$`.
    /// Reverts if `$` has not been initialized.
    function grow(LazyShuffler storage $, uint256 n) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let state := sload($.slot) // The packed value at `$`.
            // If the new length is smaller than the old length, revert.
            if lt(n, shr(224, state)) {
                mstore(0x00, 0xbed37c6e) // `InvalidNewLazyShufflerLength()`.
                revert(0x1c, 0x04)
            }
            if iszero(state) {
                mstore(0x00, 0x1ead2566) // `LazyShufflerNotInitialized()`.
                revert(0x1c, 0x04)
            }
            sstore($.slot, or(shl(224, n), shr(32, shl(32, state))))
        }
    }

    /// @dev Restarts the shuffler by setting `numShuffled` to zero,
    /// such that all elements can be drawn again.
    /// Restarting does NOT clear the internal permutation, nor changes the length.
    /// Even with the same sequence of randomness, reshuffling can yield different results.
    function restart(LazyShuffler storage $) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let state := sload($.slot)
            if iszero(state) {
                mstore(0x00, 0x1ead2566) // `LazyShufflerNotInitialized()`.
                revert(0x1c, 0x04)
            }
            sstore($.slot, shl(32, shr(32, state)))
        }
    }

    /// @dev Returns the number of elements that have been shuffled.
    function numShuffled(LazyShuffler storage $) internal view returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := and(0xffffffff, sload($.slot))
        }
    }

    /// @dev Returns the length of `$`.
    /// Returns zero if `$` is not initialized, else a non-zero value less than `2**32 - 1`.
    function length(LazyShuffler storage $) internal view returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := shr(224, sload($.slot))
        }
    }

    /// @dev Returns if `$` has been initialized.
    function initialized(LazyShuffler storage $) internal view returns (bool result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := iszero(iszero(sload($.slot)))
        }
    }

    /// @dev Returns if there are any more elements left to shuffle.
    /// Reverts if `$` is not initialized.
    function finished(LazyShuffler storage $) internal view returns (bool result) {
        /// @solidity memory-safe-assembly
        assembly {
            let state := sload($.slot) // The packed value at `$`.
            if iszero(state) {
                mstore(0x00, 0x1ead2566) // `LazyShufflerNotInitialized()`.
                revert(0x1c, 0x04)
            }
            result := eq(shr(224, state), and(0xffffffff, state))
        }
    }

    /// @dev Returns the current value stored at `index`, accounting for all historical shuffling.
    /// Reverts if `index` is greater than or equal to the `length` of `$`.
    function get(LazyShuffler storage $, uint256 index) internal view returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            let state := sload($.slot) // The packed value at `$`.
            let n := shr(224, state) // Length of `$`.
            if iszero(lt(index, n)) {
                mstore(0x00, 0x61367cc4) // `LazyShufflerGetOutOfBounds()`.
                revert(0x1c, 0x04)
            }
            let u32 := gt(n, 0xfffe)
            let s := add(shr(sub(4, u32), index), shr(64, shl(32, state))) // Bucket slot.
            let o := shl(add(4, u32), and(index, shr(u32, 15))) // Bucket slot offset (bits).
            let m := sub(shl(shl(u32, 16), 1), 1) // Value mask.
            result := and(m, shr(o, sload(s)))
            result := xor(index, mul(xor(index, sub(result, 1)), iszero(iszero(result))))
        }
    }

    /// @dev Does a single Fisher-Yates shuffle step, increments the `numShuffled` in `$`,
    /// and returns the next value in the shuffled range.
    /// `randomness` can be taken from a good-enough source, or a higher quality source like VRF.
    /// Reverts if there are no more values to shuffle, which includes the case if `$` is not initialized.
    function next(LazyShuffler storage $, uint256 randomness) internal returns (uint256 chosen) {
        /// @solidity memory-safe-assembly
        assembly {
            function _get(u32_, state_, i_) -> _value {
                let s_ := add(shr(sub(4, u32_), i_), shr(64, shl(32, state_))) // Bucket slot.
                let o_ := shl(add(4, u32_), and(i_, shr(u32_, 15))) // Bucket slot offset (bits).
                let m_ := sub(shl(shl(u32_, 16), 1), 1) // Value mask.
                _value := and(m_, shr(o_, sload(s_)))
                _value := xor(i_, mul(xor(i_, sub(_value, 1)), iszero(iszero(_value))))
            }
            function _set(u32_, state_, i_, value_) {
                let s_ := add(shr(sub(4, u32_), i_), shr(64, shl(32, state_))) // Bucket slot.
                let o_ := shl(add(4, u32_), and(i_, shr(u32_, 15))) // Bucket slot offset (bits).
                let m_ := sub(shl(shl(u32_, 16), 1), 1) // Value mask.
                let v_ := sload(s_) // Bucket slot value.
                value_ := mul(iszero(eq(i_, value_)), add(value_, 1))
                sstore(s_, xor(v_, shl(o_, and(m_, xor(shr(o_, v_), value_)))))
            }
            let state := sload($.slot) // The packed value at `$`.
            let shuffled := and(0xffffffff, state) // Number of elements shuffled.
            let n := shr(224, state) // Length of `$`.
            let remainder := sub(n, shuffled) // Number of elements left to shuffle.
            if iszero(remainder) {
                mstore(0x00, 0x51065f79) // `LazyShuffleFinished()`.
                revert(0x1c, 0x04)
            }
            mstore(0x00, randomness) // (Re)hash the randomness so that we don't
            mstore(0x20, shuffled) // need to expect guarantees on its distribution.
            let index := add(mod(keccak256(0x00, 0x40), remainder), shuffled)
            chosen := _get(gt(n, 0xfffe), state, index)
            _set(gt(n, 0xfffe), state, index, _get(gt(n, 0xfffe), state, shuffled))
            _set(gt(n, 0xfffe), state, shuffled, chosen)
            sstore($.slot, add(1, state)) // Increment the `numShuffled` by 1, and store it.
        }
    }
}

File 48 of 48 : GuildBank.sol
// SPDX-License-Identifier: BUSL-1.1
/**
            ▒▓▒  ▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓███▓▓▒     ▒▒▒▒▓▓▓▒▓▓▓▓▓▓▓██▓
             ▒██▒▓▓▓▓█▓██████████████████▓  ▒▒▒▓███████████████▒
              ▒██▒▓█████████████████████▒ ▒▓██████████▓███████
               ▒███████████▓▒                   ▒███▓▓██████▓
                 █████████▒                     ▒▓▒▓███████▒
                  ███████▓      ▒▒▒▒▒▓▓█▓▒     ▓█▓████████
                   ▒▒▒▒▒   ▒▒▒▒▓▓▓█████▒      ▓█████████▓
                         ▒▓▓▓▒▓██████▓      ▒▓▓████████▒
                       ▒██▓▓▓███████▒      ▒▒▓███▓████
                        ▒███▓█████▒       ▒▒█████▓██▓
                          ██████▓   ▒▒▒▓██▓██▓█████▒
                           ▒▒▓▓▒   ▒██▓▒▓▓████████
                                  ▓█████▓███████▓
                                 ██▓▓██████████▒
                                ▒█████████████
                                 ███████████▓
      ▒▓▓▓▓▓▓▒▓                  ▒█████████▒                      ▒▓▓
    ▒▓█▒   ▒▒█▒▒                   ▓██████                       ▒▒▓▓▒
   ▒▒█▒       ▓▒                    ▒████                       ▒▓█▓█▓▒
   ▓▒██▓▒                             ██                       ▒▓█▓▓▓██▒
    ▓█▓▓▓▓▓█▓▓▓▒        ▒▒▒         ▒▒▒▓▓▓▓▒▓▒▒▓▒▓▓▓▓▓▓▓▓▒    ▒▓█▒ ▒▓▒▓█▓
     ▒▓█▓▓▓▓▓▓▓▓▓▓▒    ▒▒▒▓▒     ▒▒▒▓▓     ▓▓  ▓▓█▓   ▒▒▓▓   ▒▒█▒   ▒▓▒▓█▓
            ▒▒▓▓▓▒▓▒  ▒▓▓▓▒█▒   ▒▒▒█▒          ▒▒█▓▒▒▒▓▓▓▒   ▓██▓▓▓▓▓▓▓███▓
 ▒            ▒▓▓█▓  ▒▓▓▓▓█▓█▓  ▒█▓▓▒          ▓▓█▓▒▓█▓▒▒   ▓█▓        ▓███▓
▓▓▒         ▒▒▓▓█▓▒▒▓█▒   ▒▓██▓  ▓██▓▒     ▒█▓ ▓▓██   ▒▓▓▓▒▒▓█▓        ▒▓████▒
 ██▓▓▒▒▒▒▓▓███▓▒ ▒▓▓▓▓▒▒ ▒▓▓▓▓▓▓▓▒▒▒▓█▓▓▓▓█▓▓▒▒▓▓▓▓▓▒    ▒▓████▓▒     ▓▓███████▓▓▒
*/
pragma solidity 0.8.23;

import "../interfaces/IAppErrors.sol";
import "../interfaces/IERC20.sol";
import "../interfaces/IERC721.sol";
import "../interfaces/IGuildBank.sol";
import "../interfaces/IGuildController.sol";
import {IApplicationEvents} from "../interfaces/IApplicationEvents.sol";

contract GuildBank is IGuildBank {

  //region ------------------------ CONSTANTS
  /// @notice Version of the contract
  /// @dev Should be incremented when contract changed
  string public constant VERSION = "1.0.1";
  //endregion ------------------------ CONSTANTS

  //region ------------------------ Members
  IGuildController immutable public guildController;
  uint immutable public guildId;
  //endregion ------------------------ Members

  //region ------------------------ Restrictions and constructor
  function _onlyGuildController(address msgSender) internal view {
    if (msgSender != address(guildController)) revert IAppErrors.GuildControllerOnly();
  }

  constructor (address guildController_, uint guildId_) {
    guildController = IGuildController(guildController_);
    guildId = guildId_;
  }
  //endregion ------------------------ Restrictions and constructor

  //region ------------------------ ERC20
  function transfer(address token, address recipient, uint amount) external {
    _onlyGuildController(msg.sender);

    IERC20(token).transfer(recipient, amount);
    emit IApplicationEvents.GuildBankTransfer(token, recipient, amount);
  }

  function approve(address token, address spender, uint256 amount) external returns (bool) {
    _onlyGuildController(msg.sender);

    return IERC20(token).approve(spender, amount);
  }
  //endregion ------------------------ ERC20

  //region ------------------------ ERC721
  function transferNft(address to, address nft, uint256 tokenId) external {
    _onlyGuildController(msg.sender);

    IERC721(nft).transferFrom(address(this), to, tokenId);
    emit IApplicationEvents.GuildBankTransferNft(to, nft, tokenId);
  }

  function transferNftMulti(address to, address[] memory nfts, uint256[] memory tokenIds) external {
    _onlyGuildController(msg.sender);

    uint len = nfts.length;
    if (len != tokenIds.length) revert IAppErrors.LengthsMismatch();

    for (uint i; i < len; ++i) {
      IERC721(nfts[i]).transferFrom(address(this), to, tokenIds[i]);
    }
    emit IApplicationEvents.GuildBankTransferNftMulti(to, nfts, tokenIds);
  }

  function approveNft(address to, address nft, uint256 tokenId) external {
    _onlyGuildController(msg.sender);

    IERC721(nft).approve(to, tokenId);
  }

  function approveNftMulti(address to, address[] memory nfts, uint256[] memory tokenIds) external {
    _onlyGuildController(msg.sender);

    uint len = nfts.length;
    if (len != tokenIds.length) revert IAppErrors.LengthsMismatch();

    for (uint i; i < len; ++i) {
      IERC721(nfts[i]).approve(to, tokenIds[i]);
    }
  }
  //endregion ------------------------ ERC721
}

Settings
{
  "evmVersion": "istanbul",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs",
    "useLiteralContent": true
  },
  "optimizer": {
    "enabled": true,
    "runs": 50
  },
  "remappings": [],
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ErrorNotDeployer","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ErrorNotOwner","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"LengthsMismatch","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"address","name":"item","type":"address"}],"name":"UnionItemNotFound","type":"error"},{"inputs":[{"internalType":"uint256","name":"unionConfigId","type":"uint256"}],"name":"UnknownUnionConfig","type":"error"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"address","name":"keyPassItem","type":"address"}],"name":"UserHasNoKeyPass","type":"error"},{"inputs":[{"internalType":"address","name":"item","type":"address"},{"internalType":"uint256","name":"countTokens","type":"uint256"},{"internalType":"uint256","name":"requiredCountTokens","type":"uint256"}],"name":"WrongListUnionItemTokens","type":"error"},{"inputs":[],"name":"ZeroValueNotAllowed","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"controller","type":"address"},{"indexed":false,"internalType":"uint256","name":"ts","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"block","type":"uint256"}],"name":"ContractInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"configId","type":"uint256"}],"name":"RemoveUnionConfig","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"address","name":"oldLogic","type":"address"}],"name":"RevisionIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"configId","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"items","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"count","type":"uint256[]"},{"indexed":false,"internalType":"address","name":"itemToMint","type":"address"}],"name":"SetUnionConfig","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"keyPassItem","type":"address"}],"name":"SetUnionKeyPass","type":"event"},{"inputs":[],"name":"CONTROLLABLE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"items","type":"address[]"},{"internalType":"uint256[]","name":"count","type":"uint256[]"},{"internalType":"address","name":"itemToMint","type":"address"}],"name":"addUnionConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"controller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"created","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"createdBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"globalParam","type":"uint8"}],"name":"getGlobalParamValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"oldLogic","type":"address"}],"name":"increaseRevision","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"controller_","type":"address"}],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"value_","type":"address"}],"name":"isController","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"value_","type":"address"}],"name":"isGovernance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"forwarder","type":"address"}],"name":"isTrustedForwarder","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"keyPassItem","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"listUnionConfigsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"configId","type":"uint256"},{"internalType":"address[]","name":"items","type":"address[]"},{"internalType":"uint256[][]","name":"tokens","type":"uint256[][]"}],"name":"prepareToCombine","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"previousImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"configId","type":"uint256"}],"name":"removeUnionConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revision","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"item","type":"address"}],"name":"setUnionKeyPassItem","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"configId","type":"uint256"}],"name":"unionConfig","outputs":[{"components":[{"internalType":"uint96","name":"configId","type":"uint96"},{"internalType":"address","name":"itemToMint","type":"address"},{"internalType":"address[]","name":"items","type":"address[]"},{"internalType":"uint256[]","name":"count","type":"uint256[]"}],"internalType":"struct IItemControllerHelper.UnionConfig","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"unionConfigIdByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"configId","type":"uint256"},{"internalType":"address[]","name":"items","type":"address[]"},{"internalType":"uint256[]","name":"count","type":"uint256[]"},{"internalType":"address","name":"itemToMint","type":"address"}],"name":"updateUnionConfig","outputs":[],"stateMutability":"nonpayable","type":"function"}]

608060405234801561001057600080fd5b507ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff1615906001600160401b031660008115801561005b5750825b90506000826001600160401b031660011480156100775750303b155b905081158015610085575080155b156100a35760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b031916600117855583156100d157845460ff60401b1916680100000000000000001785555b831561011757845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050611d46806200012c6000396000f3fe608060405234801561001057600080fd5b50600436106101225760003560e01c80638b882ab8116100a85780638b882ab8146101f75780639091a5231461020a578063936725ec1461021d5780639d0bcca01461024e578063b429afeb14610256578063dee1f0e414610269578063dfbb45c71461027c578063ef6df1501461029c578063f4c4f338146102af578063f77c4791146102c2578063ffa1ad741461021d57600080fd5b806307bebbf41461012757806319ab453c14610145578063325a19f11461015a57806341427638146101705780634593144c146101785780634fac6ccd14610180578063572b6c05146101935780637128846b146101b6578063772a3261146101c95780637c7898cc146101dc5780637cc96380146101ef575b600080fd5b61012f6102ca565b60405161013c91906116e6565b60405180910390f35b61015861015336600461170f565b6102d9565b005b6101626103d3565b60405190815260200161013c565b610162610407565b610162610411565b61015861018e36600461170f565b610441565b6101a66101a136600461170f565b610553565b604051901515815260200161013c565b6101586101c436600461170f565b6105c9565b6101626101d736600461172c565b6105dd565b6101586101ea36600461188f565b6105e8565b610162610601565b610158610205366004611906565b61061f565b610162610218366004611906565b610630565b610241604051806040016040528060058152602001640312e302e360dc1b81525081565b60405161013c919061191f565b61012f61063b565b6101a661026436600461170f565b61066b565b6101a661027736600461170f565b610690565b61028f61028a366004611906565b610715565b60405161013c91906119e4565b6101586102aa366004611a44565b610726565b61012f6102bd366004611ac3565b61073a565b61012f610751565b60006102d4610781565b905090565b60006102e36107a3565b805490915060ff600160401b82041615906001600160401b031660008115801561030a5750825b90506000826001600160401b031660011480156103265750303b155b905081158015610334575080155b156103525760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561037c57845460ff60401b1916600160401b1785555b610385866107c7565b83156103cb57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b60006102d461040360017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b611bca565b5490565b60006102d46107d8565b60006102d461040360017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f1611bca565b3330146104935760405162461bcd60e51b815260206004820152601b60248201527a24b731b932b0b9b2903932bb34b9b4b7b7103337b93134b23232b760291b60448201526064015b60405180910390fd5b60006104b16104036001600080516020611cf1833981519152611bca565b6104bc906001611bdd565b90506104de816104db6001600080516020611cf1833981519152611bca565b55565b61050d826104db60017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e4611bca565b604080518281526001600160a01b03841660208201527ff27e2ef832a4eb8ed8ec553b875eecd44764cda95b1c24170e281539e0a869c891015b60405180910390a15050565b60006001600160a01b03821673d8253782c45a12053594b9deb72d8e8ab2fca54c148061059c57506001600160a01b0382167352ceba41da235af367bfc0b0ccd3314cb901bb5f145b806105c357506001600160a01b03821673102f1f556cd9c3d5f820e6920a8931657c5da21b145b92915050565b6105da6105d4610751565b826107ed565b50565b60006105c38261084e565b6105fb6105f3610751565b8484846108a7565b50505050565b60006102d46104036001600080516020611cf1833981519152611bca565b6105da61062a610751565b82610a0a565b60006105c382610ae4565b60006102d461040360017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e4611bca565b6000610675610751565b6001600160a01b0316826001600160a01b0316149050919050565b6000816001600160a01b03166106a4610751565b6001600160a01b0316635aa6e6756040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106e1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107059190611bf0565b6001600160a01b03161492915050565b61071d6115f1565b6105c382610afb565b6105fb610731610751565b85858585610c03565b600061074885858585610d59565b95945050505050565b60006102d461040360017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c3618611bca565b600061078b610f41565b60006001815260200190815260200160002054905090565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6107cf610f65565b6105da81610f8c565b60006102d46107e5610f41565b60020161109f565b6107f782336110a9565b806001600160a01b0316610809610f41565b600060018152602001908152602001600020819055507f5a7d7d42a4de0c7d27afaa9f9daf3629731370a9d87c6efe6ac96c576fcc4a2b8160405161054791906116e6565b6000610858610f41565b600060ff8416600281111561086f5761086f611c0d565b600281111561088057610880611c0d565b600281111561089157610891611c0d565b8152602001908152602001600020549050919050565b60006108b385336110a9565b6108bd8484611139565b60006108c7610f41565b6002600090815260209190915260409020546108e4906001611bdd565b9050806108ef610f41565b6002600090815260209190915260409020556109168161090d610f41565b60020190611183565b506040518060800160405280826001600160601b03168152602001846001600160a01b0316815260200186815260200185815250610952610f41565b6000838152600191820160209081526040918290208451858301516001600160a01b0316600160601b026001600160601b0390911617815591840151805192936109a493908501929190910190611617565b50606082015180516109c091600284019160209091019061167c565b509050507f1ddc5371af486bc5c86ff21386d0c534f6ec07252285794836056e05e85b2cfb818686866040516109f99493929190611c23565b60405180910390a195945050505050565b610a1482336110a9565b801580610a44575080610a25610f41565b600083815260019190910160205260409020546001600160601b031614155b15610a65576040516304bdda9960e51b81526004810182905260240161048a565b610a6d610f41565b600082815260019182016020526040812081815591610a8e908301826116b7565b610a9c6002830160006116b7565b5050610ab381610aaa610f41565b60020190611196565b506040518181527f2cc5082e37610df6d3fb5ec870bb586c1826371c3b32850629bc142a28825a0f90602001610547565b60006105c382610af2610f41565b600201906111a2565b610b036115f1565b610b0b610f41565b600083815260019182016020908152604091829020825160808101845281546001600160601b0381168252600160601b90046001600160a01b031681840152938101805484518185028101850186528181529294860193830182828015610b9b57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610b7d575b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015610bf357602002820191906000526020600020905b815481526020019060010190808311610bdf575b5050505050815250509050919050565b610c0d85336110a9565b610c178383611139565b831580610c47575083610c28610f41565b600086815260019190910160205260409020546001600160601b031614155b15610c68576040516304bdda9960e51b81526004810185905260240161048a565b6040518060800160405280856001600160601b03168152602001826001600160a01b0316815260200184815260200183815250610ca3610f41565b6000868152600191820160209081526040918290208451858301516001600160a01b0316600160601b026001600160601b039091161781559184015180519293610cf593908501929190910190611617565b5060608201518051610d1191600284019160209091019061167c565b509050507f1ddc5371af486bc5c86ff21386d0c534f6ec07252285794836056e05e85b2cfb84848484604051610d4a9493929190611c23565b60405180910390a15050505050565b600080610d64610781565b90506001600160a01b03811615610e15576040516370a0823160e01b81526001600160a01b038216906370a0823190610da19089906004016116e6565b602060405180830381865afa158015610dbe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610de29190611c68565b600003610e155760405163b76e8a4f60e01b81526001600160a01b0380881660048301528216602482015260440161048a565b6000610e1f610f41565b6000878152600191909101602052604090209050851580610e4a575080546001600160601b03168614155b15610e6b576040516304bdda9960e51b81526004810187905260240161048a565b610f268782600101805480602002602001604051908101604052809291908181526020018280548015610ec757602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610ea9575b505050505083600201805480602002602001604051908101604052809291908181526020018280548015610f1a57602002820191906000526020600020905b815481526020019060010190808311610f06575b505050505088886111ae565b54600160601b90046001600160a01b03169695505050505050565b7f369019597a17a51d9b72838e37238167004097b8e88fd096bb2d3ab0fa92ac0090565b610f6d61146b565b610f8a57604051631afcd79f60e31b815260040160405180910390fd5b565b6001600160a01b038116610fd45760405162461bcd60e51b815260206004820152600f60248201526e2d32b9379031b7b73a3937b63632b960891b604482015260640161048a565b611003816104db60017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c3618611bca565b611032426104db60017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b611bca565b611061436104db60017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f1611bca565b7f1a2dd071001ebf6e03174e3df5b305795a4ad5d41d8fdb9ba41dbbe23671342681424360405161109493929190611c81565b60405180910390a150565b60006105c3825490565b604051631430d62960e21b81526001600160a01b038316906350c358a4906110d59084906004016116e6565b602060405180830381865afa1580156110f2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111169190611ca2565b611135578060405163451cea1760e11b815260040161048a91906116e6565b5050565b8151600081900361115d5760405163273e150360e21b815260040160405180910390fd5b8151811461117e5760405163586cb9e160e01b815260040160405180910390fd5b505050565b600061118f8383611485565b9392505050565b600061118f83836114d4565b600061118f83836115c7565b83518251811415806111c1575080825114155b156111df5760405163586cb9e160e01b815260040160405180910390fd5b60005b818110156114625760001960005b838110156112535787838151811061120a5761120a611cc4565b60200260200101516001600160a01b031686828151811061122d5761122d611cc4565b60200260200101516001600160a01b03160361124b57809150611253565b6001016111f0565b5060001981036112915786828151811061126f5761126f611cc4565b60200260200101516040516332111a8360e21b815260040161048a91906116e6565b60008482815181106112a5576112a5611cc4565b60200260200101519050600081519050808885815181106112c8576112c8611cc4565b602002602001015114611326578884815181106112e7576112e7611cc4565b60200260200101518189868151811061130257611302611cc4565b6020026020010151604051630e45bea760e31b815260040161048a93929190611c81565b60005b81811015611453578a6001600160a01b03168a868151811061134d5761134d611cc4565b60200260200101516001600160a01b0316636352211e85848151811061137557611375611cc4565b60200260200101516040518263ffffffff1660e01b815260040161139b91815260200190565b602060405180830381865afa1580156113b8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113dc9190611bf0565b6001600160a01b03161461144b578985815181106113fc576113fc611cc4565b602002602001015183828151811061141657611416611cc4565b602090810291909101015160405163547208b960e11b81526001600160a01b039092166004830152602482015260440161048a565b600101611329565b505050508060010190506111e2565b50505050505050565b60006114756107a3565b54600160401b900460ff16919050565b60008181526001830160205260408120546114cc575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556105c3565b5060006105c3565b600081815260018301602052604081205480156115bd5760006114f8600183611bca565b855490915060009061150c90600190611bca565b905080821461157157600086600001828154811061152c5761152c611cc4565b906000526020600020015490508087600001848154811061154f5761154f611cc4565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061158257611582611cda565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506105c3565b60009150506105c3565b60008260000182815481106115de576115de611cc4565b9060005260206000200154905092915050565b604080516080810182526000808252602082015260609181018290528181019190915290565b82805482825590600052602060002090810192821561166c579160200282015b8281111561166c57825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190611637565b506116789291506116d1565b5090565b82805482825590600052602060002090810192821561166c579160200282015b8281111561166c57825182559160200191906001019061169c565b50805460008255906000526020600020908101906105da91905b5b8082111561167857600081556001016116d2565b6001600160a01b0391909116815260200190565b6001600160a01b03811681146105da57600080fd5b60006020828403121561172157600080fd5b813561118f816116fa565b60006020828403121561173e57600080fd5b813560ff8116811461118f57600080fd5b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b038111828210171561178d5761178d61174f565b604052919050565b60006001600160401b038211156117ae576117ae61174f565b5060051b60200190565b600082601f8301126117c957600080fd5b813560206117de6117d983611795565b611765565b8083825260208201915060208460051b87010193508684111561180057600080fd5b602086015b84811015611825578035611818816116fa565b8352918301918301611805565b509695505050505050565b600082601f83011261184157600080fd5b813560206118516117d983611795565b8083825260208201915060208460051b87010193508684111561187357600080fd5b602086015b848110156118255780358352918301918301611878565b6000806000606084860312156118a457600080fd5b83356001600160401b03808211156118bb57600080fd5b6118c7878388016117b8565b945060208601359150808211156118dd57600080fd5b506118ea86828701611830565b92505060408401356118fb816116fa565b809150509250925092565b60006020828403121561191857600080fd5b5035919050565b60006020808352835180602085015260005b8181101561194d57858101830151858201604001528201611931565b506000604082860101526040601f19601f8301168501019250505092915050565b60008151808452602080850194506020840160005b838110156119a85781516001600160a01b031687529582019590820190600101611983565b509495945050505050565b60008151808452602080850194506020840160005b838110156119a8578151875295820195908201906001016119c8565b602080825282516001600160601b0316828201528201516001600160a01b031660408083019190915282015160806060830152600090611a2760a084018261196e565b90506060840151601f1984830301608085015261074882826119b3565b60008060008060808587031215611a5a57600080fd5b8435935060208501356001600160401b0380821115611a7857600080fd5b611a84888389016117b8565b94506040870135915080821115611a9a57600080fd5b50611aa787828801611830565b9250506060850135611ab8816116fa565b939692955090935050565b60008060008060808587031215611ad957600080fd5b8435611ae4816116fa565b9350602085810135935060408601356001600160401b0380821115611b0857600080fd5b611b1489838a016117b8565b94506060880135915080821115611b2a57600080fd5b818801915088601f830112611b3e57600080fd5b8135611b4c6117d982611795565b81815260059190911b8301840190848101908b831115611b6b57600080fd5b8585015b83811015611ba357803585811115611b875760008081fd5b611b958e89838a0101611830565b845250918601918601611b6f565b50989b979a50959850505050505050565b634e487b7160e01b600052601160045260246000fd5b818103818111156105c3576105c3611bb4565b808201808211156105c3576105c3611bb4565b600060208284031215611c0257600080fd5b815161118f816116fa565b634e487b7160e01b600052602160045260246000fd5b848152608060208201526000611c3c608083018661196e565b8281036040840152611c4e81866119b3565b91505060018060a01b038316606083015295945050505050565b600060208284031215611c7a57600080fd5b5051919050565b6001600160a01b039390931683526020830191909152604082015260600190565b600060208284031215611cb457600080fd5b8151801515811461118f57600080fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052603160045260246000fdfe22573091f17911fb166032a3d9e0554aa73d31b7b7ddea4a4dd2995650af84bda26469706673582212205d2ae992e7718b2ad2ec01372f7073b8e5eedd78d8f9e8fe16c719ec4fb7cb6964736f6c63430008170033

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101225760003560e01c80638b882ab8116100a85780638b882ab8146101f75780639091a5231461020a578063936725ec1461021d5780639d0bcca01461024e578063b429afeb14610256578063dee1f0e414610269578063dfbb45c71461027c578063ef6df1501461029c578063f4c4f338146102af578063f77c4791146102c2578063ffa1ad741461021d57600080fd5b806307bebbf41461012757806319ab453c14610145578063325a19f11461015a57806341427638146101705780634593144c146101785780634fac6ccd14610180578063572b6c05146101935780637128846b146101b6578063772a3261146101c95780637c7898cc146101dc5780637cc96380146101ef575b600080fd5b61012f6102ca565b60405161013c91906116e6565b60405180910390f35b61015861015336600461170f565b6102d9565b005b6101626103d3565b60405190815260200161013c565b610162610407565b610162610411565b61015861018e36600461170f565b610441565b6101a66101a136600461170f565b610553565b604051901515815260200161013c565b6101586101c436600461170f565b6105c9565b6101626101d736600461172c565b6105dd565b6101586101ea36600461188f565b6105e8565b610162610601565b610158610205366004611906565b61061f565b610162610218366004611906565b610630565b610241604051806040016040528060058152602001640312e302e360dc1b81525081565b60405161013c919061191f565b61012f61063b565b6101a661026436600461170f565b61066b565b6101a661027736600461170f565b610690565b61028f61028a366004611906565b610715565b60405161013c91906119e4565b6101586102aa366004611a44565b610726565b61012f6102bd366004611ac3565b61073a565b61012f610751565b60006102d4610781565b905090565b60006102e36107a3565b805490915060ff600160401b82041615906001600160401b031660008115801561030a5750825b90506000826001600160401b031660011480156103265750303b155b905081158015610334575080155b156103525760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561037c57845460ff60401b1916600160401b1785555b610385866107c7565b83156103cb57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b60006102d461040360017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b611bca565b5490565b60006102d46107d8565b60006102d461040360017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f1611bca565b3330146104935760405162461bcd60e51b815260206004820152601b60248201527a24b731b932b0b9b2903932bb34b9b4b7b7103337b93134b23232b760291b60448201526064015b60405180910390fd5b60006104b16104036001600080516020611cf1833981519152611bca565b6104bc906001611bdd565b90506104de816104db6001600080516020611cf1833981519152611bca565b55565b61050d826104db60017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e4611bca565b604080518281526001600160a01b03841660208201527ff27e2ef832a4eb8ed8ec553b875eecd44764cda95b1c24170e281539e0a869c891015b60405180910390a15050565b60006001600160a01b03821673d8253782c45a12053594b9deb72d8e8ab2fca54c148061059c57506001600160a01b0382167352ceba41da235af367bfc0b0ccd3314cb901bb5f145b806105c357506001600160a01b03821673102f1f556cd9c3d5f820e6920a8931657c5da21b145b92915050565b6105da6105d4610751565b826107ed565b50565b60006105c38261084e565b6105fb6105f3610751565b8484846108a7565b50505050565b60006102d46104036001600080516020611cf1833981519152611bca565b6105da61062a610751565b82610a0a565b60006105c382610ae4565b60006102d461040360017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e4611bca565b6000610675610751565b6001600160a01b0316826001600160a01b0316149050919050565b6000816001600160a01b03166106a4610751565b6001600160a01b0316635aa6e6756040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106e1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107059190611bf0565b6001600160a01b03161492915050565b61071d6115f1565b6105c382610afb565b6105fb610731610751565b85858585610c03565b600061074885858585610d59565b95945050505050565b60006102d461040360017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c3618611bca565b600061078b610f41565b60006001815260200190815260200160002054905090565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6107cf610f65565b6105da81610f8c565b60006102d46107e5610f41565b60020161109f565b6107f782336110a9565b806001600160a01b0316610809610f41565b600060018152602001908152602001600020819055507f5a7d7d42a4de0c7d27afaa9f9daf3629731370a9d87c6efe6ac96c576fcc4a2b8160405161054791906116e6565b6000610858610f41565b600060ff8416600281111561086f5761086f611c0d565b600281111561088057610880611c0d565b600281111561089157610891611c0d565b8152602001908152602001600020549050919050565b60006108b385336110a9565b6108bd8484611139565b60006108c7610f41565b6002600090815260209190915260409020546108e4906001611bdd565b9050806108ef610f41565b6002600090815260209190915260409020556109168161090d610f41565b60020190611183565b506040518060800160405280826001600160601b03168152602001846001600160a01b0316815260200186815260200185815250610952610f41565b6000838152600191820160209081526040918290208451858301516001600160a01b0316600160601b026001600160601b0390911617815591840151805192936109a493908501929190910190611617565b50606082015180516109c091600284019160209091019061167c565b509050507f1ddc5371af486bc5c86ff21386d0c534f6ec07252285794836056e05e85b2cfb818686866040516109f99493929190611c23565b60405180910390a195945050505050565b610a1482336110a9565b801580610a44575080610a25610f41565b600083815260019190910160205260409020546001600160601b031614155b15610a65576040516304bdda9960e51b81526004810182905260240161048a565b610a6d610f41565b600082815260019182016020526040812081815591610a8e908301826116b7565b610a9c6002830160006116b7565b5050610ab381610aaa610f41565b60020190611196565b506040518181527f2cc5082e37610df6d3fb5ec870bb586c1826371c3b32850629bc142a28825a0f90602001610547565b60006105c382610af2610f41565b600201906111a2565b610b036115f1565b610b0b610f41565b600083815260019182016020908152604091829020825160808101845281546001600160601b0381168252600160601b90046001600160a01b031681840152938101805484518185028101850186528181529294860193830182828015610b9b57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610b7d575b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015610bf357602002820191906000526020600020905b815481526020019060010190808311610bdf575b5050505050815250509050919050565b610c0d85336110a9565b610c178383611139565b831580610c47575083610c28610f41565b600086815260019190910160205260409020546001600160601b031614155b15610c68576040516304bdda9960e51b81526004810185905260240161048a565b6040518060800160405280856001600160601b03168152602001826001600160a01b0316815260200184815260200183815250610ca3610f41565b6000868152600191820160209081526040918290208451858301516001600160a01b0316600160601b026001600160601b039091161781559184015180519293610cf593908501929190910190611617565b5060608201518051610d1191600284019160209091019061167c565b509050507f1ddc5371af486bc5c86ff21386d0c534f6ec07252285794836056e05e85b2cfb84848484604051610d4a9493929190611c23565b60405180910390a15050505050565b600080610d64610781565b90506001600160a01b03811615610e15576040516370a0823160e01b81526001600160a01b038216906370a0823190610da19089906004016116e6565b602060405180830381865afa158015610dbe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610de29190611c68565b600003610e155760405163b76e8a4f60e01b81526001600160a01b0380881660048301528216602482015260440161048a565b6000610e1f610f41565b6000878152600191909101602052604090209050851580610e4a575080546001600160601b03168614155b15610e6b576040516304bdda9960e51b81526004810187905260240161048a565b610f268782600101805480602002602001604051908101604052809291908181526020018280548015610ec757602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610ea9575b505050505083600201805480602002602001604051908101604052809291908181526020018280548015610f1a57602002820191906000526020600020905b815481526020019060010190808311610f06575b505050505088886111ae565b54600160601b90046001600160a01b03169695505050505050565b7f369019597a17a51d9b72838e37238167004097b8e88fd096bb2d3ab0fa92ac0090565b610f6d61146b565b610f8a57604051631afcd79f60e31b815260040160405180910390fd5b565b6001600160a01b038116610fd45760405162461bcd60e51b815260206004820152600f60248201526e2d32b9379031b7b73a3937b63632b960891b604482015260640161048a565b611003816104db60017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c3618611bca565b611032426104db60017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b611bca565b611061436104db60017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f1611bca565b7f1a2dd071001ebf6e03174e3df5b305795a4ad5d41d8fdb9ba41dbbe23671342681424360405161109493929190611c81565b60405180910390a150565b60006105c3825490565b604051631430d62960e21b81526001600160a01b038316906350c358a4906110d59084906004016116e6565b602060405180830381865afa1580156110f2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111169190611ca2565b611135578060405163451cea1760e11b815260040161048a91906116e6565b5050565b8151600081900361115d5760405163273e150360e21b815260040160405180910390fd5b8151811461117e5760405163586cb9e160e01b815260040160405180910390fd5b505050565b600061118f8383611485565b9392505050565b600061118f83836114d4565b600061118f83836115c7565b83518251811415806111c1575080825114155b156111df5760405163586cb9e160e01b815260040160405180910390fd5b60005b818110156114625760001960005b838110156112535787838151811061120a5761120a611cc4565b60200260200101516001600160a01b031686828151811061122d5761122d611cc4565b60200260200101516001600160a01b03160361124b57809150611253565b6001016111f0565b5060001981036112915786828151811061126f5761126f611cc4565b60200260200101516040516332111a8360e21b815260040161048a91906116e6565b60008482815181106112a5576112a5611cc4565b60200260200101519050600081519050808885815181106112c8576112c8611cc4565b602002602001015114611326578884815181106112e7576112e7611cc4565b60200260200101518189868151811061130257611302611cc4565b6020026020010151604051630e45bea760e31b815260040161048a93929190611c81565b60005b81811015611453578a6001600160a01b03168a868151811061134d5761134d611cc4565b60200260200101516001600160a01b0316636352211e85848151811061137557611375611cc4565b60200260200101516040518263ffffffff1660e01b815260040161139b91815260200190565b602060405180830381865afa1580156113b8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113dc9190611bf0565b6001600160a01b03161461144b578985815181106113fc576113fc611cc4565b602002602001015183828151811061141657611416611cc4565b602090810291909101015160405163547208b960e11b81526001600160a01b039092166004830152602482015260440161048a565b600101611329565b505050508060010190506111e2565b50505050505050565b60006114756107a3565b54600160401b900460ff16919050565b60008181526001830160205260408120546114cc575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556105c3565b5060006105c3565b600081815260018301602052604081205480156115bd5760006114f8600183611bca565b855490915060009061150c90600190611bca565b905080821461157157600086600001828154811061152c5761152c611cc4565b906000526020600020015490508087600001848154811061154f5761154f611cc4565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061158257611582611cda565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506105c3565b60009150506105c3565b60008260000182815481106115de576115de611cc4565b9060005260206000200154905092915050565b604080516080810182526000808252602082015260609181018290528181019190915290565b82805482825590600052602060002090810192821561166c579160200282015b8281111561166c57825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190611637565b506116789291506116d1565b5090565b82805482825590600052602060002090810192821561166c579160200282015b8281111561166c57825182559160200191906001019061169c565b50805460008255906000526020600020908101906105da91905b5b8082111561167857600081556001016116d2565b6001600160a01b0391909116815260200190565b6001600160a01b03811681146105da57600080fd5b60006020828403121561172157600080fd5b813561118f816116fa565b60006020828403121561173e57600080fd5b813560ff8116811461118f57600080fd5b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b038111828210171561178d5761178d61174f565b604052919050565b60006001600160401b038211156117ae576117ae61174f565b5060051b60200190565b600082601f8301126117c957600080fd5b813560206117de6117d983611795565b611765565b8083825260208201915060208460051b87010193508684111561180057600080fd5b602086015b84811015611825578035611818816116fa565b8352918301918301611805565b509695505050505050565b600082601f83011261184157600080fd5b813560206118516117d983611795565b8083825260208201915060208460051b87010193508684111561187357600080fd5b602086015b848110156118255780358352918301918301611878565b6000806000606084860312156118a457600080fd5b83356001600160401b03808211156118bb57600080fd5b6118c7878388016117b8565b945060208601359150808211156118dd57600080fd5b506118ea86828701611830565b92505060408401356118fb816116fa565b809150509250925092565b60006020828403121561191857600080fd5b5035919050565b60006020808352835180602085015260005b8181101561194d57858101830151858201604001528201611931565b506000604082860101526040601f19601f8301168501019250505092915050565b60008151808452602080850194506020840160005b838110156119a85781516001600160a01b031687529582019590820190600101611983565b509495945050505050565b60008151808452602080850194506020840160005b838110156119a8578151875295820195908201906001016119c8565b602080825282516001600160601b0316828201528201516001600160a01b031660408083019190915282015160806060830152600090611a2760a084018261196e565b90506060840151601f1984830301608085015261074882826119b3565b60008060008060808587031215611a5a57600080fd5b8435935060208501356001600160401b0380821115611a7857600080fd5b611a84888389016117b8565b94506040870135915080821115611a9a57600080fd5b50611aa787828801611830565b9250506060850135611ab8816116fa565b939692955090935050565b60008060008060808587031215611ad957600080fd5b8435611ae4816116fa565b9350602085810135935060408601356001600160401b0380821115611b0857600080fd5b611b1489838a016117b8565b94506060880135915080821115611b2a57600080fd5b818801915088601f830112611b3e57600080fd5b8135611b4c6117d982611795565b81815260059190911b8301840190848101908b831115611b6b57600080fd5b8585015b83811015611ba357803585811115611b875760008081fd5b611b958e89838a0101611830565b845250918601918601611b6f565b50989b979a50959850505050505050565b634e487b7160e01b600052601160045260246000fd5b818103818111156105c3576105c3611bb4565b808201808211156105c3576105c3611bb4565b600060208284031215611c0257600080fd5b815161118f816116fa565b634e487b7160e01b600052602160045260246000fd5b848152608060208201526000611c3c608083018661196e565b8281036040840152611c4e81866119b3565b91505060018060a01b038316606083015295945050505050565b600060208284031215611c7a57600080fd5b5051919050565b6001600160a01b039390931683526020830191909152604082015260600190565b600060208284031215611cb457600080fd5b8151801515811461118f57600080fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052603160045260246000fdfe22573091f17911fb166032a3d9e0554aa73d31b7b7ddea4a4dd2995650af84bda26469706673582212205d2ae992e7718b2ad2ec01372f7073b8e5eedd78d8f9e8fe16c719ec4fb7cb6964736f6c63430008170033

Block Transaction Gas Used Reward
view all blocks produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.