S Price: $0.54286 (-9.06%)

Contract Diff Checker

Contract Name:
HeroBase

Contract Source Code:

// 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);
  error AlreadyClaimed();

  //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();
  error NotPvpController();
  //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 ErrorNotOwnerOrHero(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();

  error SandboxTierForbidden();
  error SandboxPrepaidOnly();
  error SandboxNgZeroOnly();
  error SandboxModeNotAllowed();
  error SandboxUpgradeModeRequired();
  error SandboxModeRequired();
  error SandboxItemOutside();
  error SandboxItemNotActive();
  error SandboxItemNotRegistered();
  error SandboxItemAlreadyEquipped();
  error SandboxDifferentHeroesNotAllowed();
  error HeroWasTransferredBetweenAccounts();
  //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();
  error LastLifeChance();
  //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();
  error ItemNotFound(address item, uint itemId);
  error NoFirstAugmentationInfo();
  error NotAugmentationProtectiveItem(address item);
  //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);
  error NotSkippableStory();
  error StoryNotPassed();
  error SkippingNotAllowed();
  //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 PvpStaked();
  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);
  //endregion 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 TooLowGuildLevel();

  /// @notice Target biome can be selected only once per epoch
  error BiomeAlreadySelected();
  error NoDominationRequest();
  error PvpFightIsNotPrepared(uint8 biome, uint32 week, address user);
  error PvpFightIsCompleted(uint8 biome, uint32 week, address user);
  error TooLowMaxCountTurns();
  error UserTokensVaultAlreadySet();

  error DifferentBiomeInPvpFight();
  error PvpFightOpponentNotFound();
  error PvpHeroHasInitializedFight();
  error PvpHeroNotRegistered();

  /// @notice User should unregister pvp-hero from prev biome and only then register it in the new biome
  error UserHasRegisteredPvpHeroInBiome(uint8 biome);
  error UserHasRegisteredPvpHero();
  error UserNotAllowedForPvpInCurrentEpoch(uint week);

  error UnknownPvpStrategy();

  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
}

// 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";
import "./IPvpController.sol";

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

  //region ------------------------ Common
  event SetOperator(address operator, bool remove);
  event Salvage(address receiver, address token, uint amount);
  //endregion ------------------------ Common


  //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);
  /// @notice Deprecated, replaced by {HeroCreatedNgpSandbox}. Don't remove - it's required by subgraph
  event HeroCreatedNgp(address hero, uint heroId, string name, address owner, string refCode, uint8 tier, uint8 ngLevel);
  event HeroCreatedNgpSandbox(address hero, uint heroId, string name, address owner, string refCode, uint8 tier, uint8 ngLevel, bool sandbox);
  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);
  event SandboxUpgraded(address hero, uint heroId);
  event SandboxReturnAmountToTreasury(uint64 dungId, address token, uint amount);
  //endregion ------------------------ HeroController

  //region ------------------------ FightLib
  event FightResultProcessed(
    address sender,
    IFightCalculator.FightInfoInternal result,
    IFightCalculator.FightCall callData,
    uint iteration
  );
  /// @param heroA Address of the fighter A.  Address of the fighter B can be detected by fightId
  /// @param heroIdA ID of the figher A. ID of the fighter B can be detected by fightId
  event PvpFightResultProcessed(
    uint48 fightId,
    address sender,
    IFightCalculator.FightInfoInternal result,
    uint turn,
    address heroA,
    uint heroIdA
  );
  //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 PvpControllerChanged(address value);

  event GameTokenPriceChanged(uint value);
  event RewardsPoolChanged(address value);
  event ItemBoxControllerChanged(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 ResetAugmentation(address item, uint itemId, uint consumedItemId, 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 SetAugmentationProtectiveItem(address keyPassItem);
  event CombineItems(address msgSender, uint configId, address[] items, uint[][] itemIds, address mintedItem, uint mintedItemId);
  event RegisterSandboxItem(address hero, uint heroId, address item, uint itemId, uint tsMinting);
  event WithdrawItemsFromSandbox(address hero, uint heroId, address[] items, uint[] itemIds);
  event ItemReturnedToSandbox(address hero, uint heroId, address item, uint itemId);
  event RegisterSandboxUpgrade(address hero, uint heroId, uint tsUpgradng);
  event TransferItemToHeroFromSandbox(address hero, uint heroId, address item, uint itemId);
  event DestroyItemInSandbox(address item, uint itemId);
  event NewItemSentToSandbox(address item, uint itemId);
  event ExitFromDungeon(address hero, uint heroId);
  //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 RegisterPassedDungeon(address user, uint32 epochWeek, uint counterPassedDungeons);
  event RegisterPvp(address user, uint32 epochWeek, uint counterPvp);
  event FameHallHeroRegistered(address hero, uint heroId, address heroOwner, uint8 openedNgLevel);
  event SetMinHeroLevel(uint level);
  event SetGuildStakingAdapter(address adapter);

  event AddGamePoints(address user, uint finalBalanceGamePoints);
  /// @param paramId See IUserController.UserControllerParam
  event SetUserControllerParam(uint8 paramId, uint paramValue);
  event UseGamePointsToSkipStory(address user, uint16 storyId, uint priceInGamePoints, uint finalBalanceGamePoints);
  event SetStoryPassed(address user, uint16 storyId);
  //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 TransferOwnership(address prevOwner, address newOwner);

  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 ------------------------ Pvp
  event AddBiomeRequest(address user, uint8 biome, uint guildId, uint32 week);
  event PvpHeroAdded(address user, uint guildId, address hero, uint heroId, uint week, uint8 biome);
  /// @param manuallyRemoved True - removed manually by the user, false - removed automatically after the fight
  event PvpHeroRemoved(address user, uint guildId, uint week, uint8 biome, address hero, uint heroId, bool manuallyRemoved);
  event PreparePvpFight(uint48 fightId, uint32 week, address hero, uint heroId, uint heroGuildId, address opponentHero, uint opponentHeroId, uint opponentGuildId);

  /// @notice heroId can be detected by {fightId} and {heroes}
  event PvpFightCompleted(
    IPvpController.PvpFightResults fightResult,
    uint48 fightId,
    address[2] heroes,
    uint64[2] guilds,
    bool[2] winners,
    uint[2] prizes,
    bool technicalDefeat
  );
  event UpdatePvpEpoch(uint8 biome, uint32 week, uint guildBiomeOwnerId);
  event FirstPvpEpoch(uint8 biome, uint32 week);
  event BiomeTaxPaid(address msgSender, uint8 biome, uint guildId, uint amount, uint taxPercent, uint taxAmount);
  event BiomeTaxPaidNft(address msgSender, uint8 biome, uint guildId, address item, uint itemId, uint taxPercent);
  event AddPvpFightItems(uint48 fightId, address[] items, uint[] itemIds);

  //endregion ------------------------ Pvp

  //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);
  event ApplyAuctionBid(uint bidId, address msgSender);
  event AuctionSetFee(uint fee);
  //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

  //region ------------------------ Airdrop Distributor
  event AirdropDistributorSetToken(address token);
  event AirdropDistributorAddTree(uint week, bytes32 merkleRoot_);
  event AirdropDistributorRemoveTree(uint week);
  event AirdropDistributorClaim(uint[] _weeks, uint[] amounts, address receiver);

  //endregion ------------------------ Airdrop Distributor

  //region ------------------------ GuildStakingManager
  event SetStakingToken(address token);
  event StakeTokens(address token, uint amount, uint guildId, uint total);
  //endregion ------------------------ GuildStakingManager

}

// 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;

}

// 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 pvpController() external view returns (address);

  function rewardsPool() external view returns (address);

  function itemBoxController() external view returns (address);

  function gameTokenPrice() external view returns (uint);

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

// 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;
}

// 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);
}

// 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);
}

// 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;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)

pragma solidity ^0.8.0;

import "./IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Enumerable is IERC721 {
  /**
   * @dev Returns the total amount of tokens stored by the contract.
   */
  function totalSupply() external view returns (uint256);

  /**
   * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
   * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
   */
  function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);

  /**
   * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
   * Use along with {totalSupply} to enumerate all tokens.
   */
  function tokenByIndex(uint256 index) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;

/**
 * @dev Standard ERC-721 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.
 */
interface IERC721Errors {
  /**
   * @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);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)

pragma solidity ^0.8.0;

import "./IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Metadata is IERC721 {
  /**
   * @dev Returns the token collection name.
   */
  function name() external view returns (string memory);

  /**
   * @dev Returns the token collection symbol.
   */
  function symbol() external view returns (string memory);

  /**
   * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
   */
  function tokenURI(uint256 tokenId) external view returns (string memory);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
  /**
   * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
   * by `operator` from `from`, this function is called.
   *
   * It must return its Solidity selector to confirm the token transfer.
   * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
   *
   * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
   */
  function onERC721Received(
    address operator,
    address from,
    uint256 tokenId,
    bytes calldata data
  ) external returns (bytes4);
}

// 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;
  }

  /// @notice Additional info passed to fight
  struct FightCallAdd {
    address msgSender;

    /// @notice Unique ID of the pvp-fight, 0 for not pvp fights
    uint48 fightId;
  }

  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);
}

// 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;

}

// 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);

}

// 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,
    DOMINATION_REQUEST_13
  }

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

    // max 255 params because enum is uint8 by default
  }

  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 isPeacefulRelation(uint guildId, uint guildId2) external view returns (bool);
  function incPvpCounter(uint guildId, uint64 value) external;

  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;

}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

interface IHero {

  function isHero() external pure returns (bool);

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

  function burn(uint tokenId) external;

}

// 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 Sandbox mode for heroes, see SCR-1153
    mapping(bytes32 packedHero => SandboxMode sandboxMode) sandbox;
  }

  /// @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
    /// Zero for sandbox-heroes
    uint72 paidAmount;

    /// @notice Pay token used to pay {paidAmount}
    /// Zero for sandbox-heroes
    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;

    /// @notice SCR-1153: create not-paid hero with limited rights
    bool sandboxMode;
  }

  enum SandboxMode {
    /// @notice The hero is created in normal (not sandbox) mode
    NORMAL_MODE_0,

    /// @notice The hero was created in sandbox mode and wasn't upgraded.
    SANDBOX_MODE_1,

    /// @notice The hero was created in sandbox mode and was upgraded to the normal mode
    UPGRADED_TO_NORMAL_2
  }

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

  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);

  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);

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

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

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

interface IItemBoxController {
  /// @custom:storage-location erc7201:ItemBox.controller.main
  struct MainState {
    mapping(bytes32 packedHero => HeroData) heroData;

    /// @notice Owners of all items minted in sandbox mode
    mapping(bytes32 packedItem => bytes32 packedHero) heroes;
  }

  struct HeroData {
    /// @notice Moment of upgrading sandbox-hero to normal-hero
    uint tsUpgraded;

    /// @notice List of all items registered for the hero
    EnumerableSet.AddressSet items;

    /// @notice item => (itemId => packedItemBoxItemInfo)
    /// @dev Ids are never deleted from the map, so the order of ids is never changed
    mapping(address item => EnumerableMap.UintToUintMap) states;
  }

  struct ItemBoxItemInfo {
    /// @notice True if the item was withdrawn from balance
    /// It can happens in follow cases:
    /// 1) the hero was upgraded and the item was withdrawn on hero owner balance
    /// 2) the item is used by ItemController:
    /// 2.1) the item is equipped on the hero and so it's transferred to the hero balance
    /// 2.2) the consumable item is used
    /// 3) the item is burnt
    /// @dev Status is required to avoid deletion (and so changing order) of the {items}
    bool withdrawn;

    /// @notice The moment of the initial item minting
    uint64 timestamp;
  }

  enum ItemState {
    /// @notice The item was never registered in the sandbox
    NOT_REGISTERED_0,
    /// @notice The item is not active (outdated) and cannot be used anymore
    NOT_AVAILABLE_1,
    /// @notice The item is active and located inside the sandbox
    INSIDE_2,
    /// @notice The item is either withdrawn or equipped
    OUTSIDE_3
  }

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

  function firstActiveItemOfHeroByIndex(address hero, uint heroId, address item) external view returns (uint itemId);
  function registerItems(address hero, uint heroId, address[] memory items, uint[] memory itemIds, uint countValidItems) external;
  function itemState(address hero, uint heroId, address item, uint itemId) external view returns (IItemBoxController.ItemState);
  function itemHero(address item, uint itemId) external view returns (address hero, uint heroId);
  function registerSandboxUpgrade(bytes32 packedHero) external;
  function transferToHero(address hero, uint heroId, address item, uint itemId) external;
  function destroyItem(address item, uint itemId) external;
}

// 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;

    /// --------------------------------- SCR-1263: Reverse-augmentation
    /// @notice Item attributes values before first augmentation.
    /// @dev SCR-1263: The values are required in augmentation if protective item is used and the augmentation is failed.
    mapping(bytes32 packedItem => ResetAugmentationData) _resetAugmentation;
  }

  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 OTHER_5 Rest in the shelter: restore of hp & mp, clear temporally attributes, clear used consumables (shelter of level 3 is required)
    /// @dev It's OTHER_5 in deploy script, but internally it has subtype 4, see gen_others.ts
    REST_IN_SHELTER_4,

    /// @notice OTHER_4 Stub item that has no logic in contracts, but it has correct (not empty) packedMetaData
    /// @dev It's OTHER_4 in deploy script, but internally it has subtype 5, see gen_others.ts
    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"

  struct AugmentOptParams {
    /// @notice Optional protective item
    /// @dev SCR-1263: If the protective item specified
    /// than failed augmentation doesn't destroy main item but reduces its augmentation level to the zero instead.
    /// Protective item is configured in ItemControllerHelper.
    address protectiveItem;
    uint protectiveItemId;
  }

  struct ResetAugmentationData {
    /// @notice Moment of the first augmentation if any
    uint tsFirstAugmentation;

    /// @notice Values of the item attributes before the first augmentation
    /// @dev Use PackingLib.toInt32ArrayWithIds to decode attribute ids and values
    bytes32[] itemAttributes;

    /// @notice Values of the caster attributes before the first augmentation
    /// @dev Use PackingLib.toInt32ArrayWithIds to decode attribute ids and values
    bytes32[] itemCasterAttributes;

    /// @notice Values of the target attributes before the first augmentation
    /// @dev Use PackingLib.toInt32ArrayWithIds to decode attribute ids and values
    bytes32[] itemTargetAttributes;

    /// @notice packed AttackInfo: attackType8 + min32 + max32 + factors(packed core 128)
    bytes32 itemAttackInfo;
  }

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

  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;
}

// 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);

}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

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

interface IPvpController {
  enum PvpParams {
    NONE_0,
    /// @notice Hero can be pvp-staked if his level is greater of equal to the given min level
    MIN_HERO_LEVEL_1,

    /// @notice Address of IGuildStakingAdapter, can be not initialized
    GUILD_STAKING_ADAPTER_2,

    /// @notice Unique ID of the pvp-fight (each pvp-fight consists from multiple turns)
    FIGHT_COUNTER_3

    // max 255 params because enum is uint8 by default
  }

  /// @custom:storage-location erc7201:pvp.controller.main
  struct MainState {
    /// @notice Mapping to store various params of PvpController
    mapping(PvpParams param => uint value) pvpParam;

    /// @notice Current states of biomes
    mapping(uint8 biome => BiomeData) biomeState;

    /// @notice Biomes owned by the guilds
    mapping(uint guildId => uint8 biome) ownedBiome;

    mapping(uint epochWeek => EpochData) epochData;
  }

  struct EpochData {
    /// @notice Current state of the user in the current epoch
    mapping (address user => PvpUserState) pvpUserState;

    /// @notice biome data for the given epoch
    mapping(uint8 biome => EpochBiomeData) epochBiomeData;

    /// @notice All prepared pvp-fights for the given user
    /// Index of currently active fight is stored in {pvpUserState.activeFightIndex1}
    mapping (address user => PvpFightData[]) fightData;

    /// @notice All currently registered packed-heroes
    EnumerableSet.UintSet stakedHeroes;

    /// @notice Weekly request of the guild to dominate at the given biome starting from the next week
    mapping(uint guildId => uint8 biome) targetBiome;

    /// @notice All guilds pretend for the given biome
    mapping(uint8 biome => EnumerableSet.UintSet guildIds) biomeGuilds;
  }

  /// @notice Current state of the user. Possible states: user has or hasn't staked a hero in pvp.
  /// Each user is able to stake pvp-heroes multiple times per epoch
  /// but the user is able to stake only 1 pvp-hero at any moment.
  /// @dev Implementation assumes that the struct occupies single slot, the struct is read as a whole
  struct PvpUserState {
    /// @notice Domination biome at the moment of staking
    /// @dev not 0 if the user has pvp-staked hero
    uint8 biome;

    /// @notice 1-based index of currently active fight in {fightData} (the fight is either prepared or in-progress).
    /// 0 - there is no active fight
    uint32 activeFightIndex1;

    /// @notice How many times the user has staked heroes for PVP
    /// @dev Max possible value is limited by MAX_NUMBER_STAKES_FOR_USER_PER_EPOCH
    uint32 numHeroesStaked;

    /// @notice User's guild at the moment of staking
    /// 0 if user has no hero staked in pvp currently
    uint64 guildId;

    /// @notice Total number of pvp-fights performed since the last call of addPvpHero.
    /// @dev All pvp-fights are won here because looser is auto removed.
    uint8 countFights;

    /// @notice Max number of pvp-fights allowed by the user per single call of addPvpHero, 0 - no limits
    uint8 maxFights;

    /// @notice Unique id of the current pvp-fight (the fight with activeFightIndex1)
    uint48 fightId;
  }

  struct BiomeData {
    /// @notice Biome owner - the guild that dominates in the biome at the given epoch. He has a right to get a tax
    /// @dev Assume here that uint64 is enough to store any guildId. It allows us to store whole struct in a single slot
    uint64 guildBiomeOwnerId;

    /// @notice Current epoch (last epoch for which pvp-battle was made)
    /// 0 if epoch was never started
    uint32 startedEpochWeek;

    /// @notice Number of consecutive epochs during which {guildBiomeOwnerId} wasn't changed
    uint16 dominationCounter;
  }

  struct EpochBiomeData {
    /// @notice List of guilds asked for domination in the biome => total points scored by the guilds in the given epoch
    /// @dev guildId => count points
    EnumerableMap.UintToUintMap guildPoints;

    /// @notice All users free for pvp-fight
    /// User is added here on registration and removed as soon as the fight for the user is initialized.
    mapping(uint guildId => EnumerableSet.AddressSet) freeUsers;

    /// @notice All users (from the {guilds}) provided heroes for pvp
    /// @dev guildId => (user address => packedHero (hero + heroId))
    mapping(uint guildId => EnumerableMap.AddressToUintMap) registeredHeroes;

    /// @notice The skills and attack type selected in advance
    mapping(bytes32 packedHero => bytes) pvpStrategy;
  }

  enum PvpFightStatus {
    /// @notice No fight, the hero doesn't have selected opponent
    NOT_INITIALIZED_0,

    /// @notice The hero has opponent, the fight is not started
    PREPARED_1,

    /// @notice The fight is started but not completed
    FIGHTING_2,

    /// @notice The fight is completed, the hero is the winner
    WINNER_3,

    /// @notice The fight is completed, the hero is the looser
    LOSER_4
  }

  /// @notice Current state of the fight
  /// @dev Implementation assumes that the struct occupies single slot, the struct is read as a whole
  /// @dev We don't store biome and guildId here. This info is stored in user state and can be lost after fight ending.
  struct PvpFightData {
    /// @notice address of user whose hero is the fight opponent
    address fightOpponent;

    /// @notice Current status of PVP-fight
    PvpFightStatus fightStatus;

    /// @notice Current value of the health (only when fightStatus is FIGHTING_2)
    uint32 health;

    /// @notice Current value of the mana (only when fightStatus is FIGHTING_2)
    uint32 mana;

    /// @notice Number of moves made (only when fightStatus is FIGHTING_2)
    uint8 countTurns;
  }

  /// @dev Implementation assumes that the struct occupies single slot, the struct is read as a whole
  struct PvpFightResults {
    bool completed;
    uint8 totalCountFights;
    uint32 healthHero;
    uint32 healthOpponent;
    uint32 manaConsumedHero;
    uint32 manaConsumedOpponent;
  }

  /// @notice Strategy how to use attack info
  enum PvpBehaviourStrategyKinds {
    /// @notice Use all skills, use magic attack if it's available
    /// @dev {PvpStrategyDefault} is used as data in {addPvpHero}
    DEFAULT_STRATEGY_0

    // new strategies are able to use different structures to store data passed to {addPvpHero}
  }

  /// @notice The data provided by user at the staking with {DEFAULT_STRATEGY_0}
  struct PvpStrategyDefault {
    /// @notice Should be equal to DEFAULT_STRATEGY_0
    uint behaviourStrategyKind;
    IFightCalculator.AttackInfo attackInfo;
  }

  struct HeroData {
    address hero;
    uint heroId;
    bytes pvpStrategy;
  }

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

  /// @notice Update epoch if necessary and return actual biome owner and tax
  /// @return guildId Owner of the biome
  /// @return taxPercent Tax percent , [0...100_000], decimals 3
  function refreshBiomeTax(uint8 biome) external returns (uint guildId, uint taxPercent);

  function isHeroStakedCurrently(address hero, uint heroId) external view returns (bool staked);
  function onGuildDeletion(uint guildId) external;
}

// 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;
}

// 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);
}

// 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;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "../interfaces/IGOC.sol";
import "../interfaces/IItemController.sol";
import "../interfaces/IStatController.sol";
import "../lib/ControllerContextLib.sol";
import "../openzeppelin/EnumerableSet.sol";
import "./IController.sol";
import "./IHeroController.sol";
import "./IOracle.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;

    /// @notice True if the story is allowed to be skipped, see SCR-1248
    EnumerableSet.UintSet skippableStory;
  }

  /// @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;
    IOracle oracle;
    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);

  function skippableStory(uint16 storyId) external view returns (bool);
}

// 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;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

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

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;
  }

  enum UserControllerParam {
    /// @notice Price of story skipping in game points
    PRICE_STORY_SKIPPING_1
  }

  /// @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;

    /// @notice Points earned for passing dungeons
    mapping(address user => uint gamePoints) gamePoints;

    /// @notice List of objects (currently only stories) passed by the given account
    /// @dev hashes of the stories are as encodePacked("STORY_{ID}")
    mapping(address user => EnumerableSet.Bytes32Set hashes) passedObjects;

    /// @notice Values of various params, see {UserControllerParam}
    mapping(UserControllerParam paramId => uint value) userControllerParams;
  }

  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;

  function useGamePointsToSkipStore(address user, uint16 storyId) external;

  function setStoryPassed(address user, uint16 storyId) external;

  function isStoryPassed(address user, uint16 storyId) external view returns (bool);
}

// 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";
import "../interfaces/IPvpController.sol";
import "../interfaces/IItemBoxController.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.statController(cc);
library ControllerContextLib {
  //region ----------------- Data types
  enum CacheIndex {
    STAT_CONTROLLER_0,
    STORY_CONTROLLER_1,
    ORACLE_2,
    TREASURY_3,
    DUNGEON_FACTORY_4,
    GOC_5,
    REINFORCEMENT_CONTROLLER_6,
    ITEM_CONTROLLER_7,
    HERO_CONTROLLER_8,
    GAME_TOKEN_9,
    USER_CONTROLLER_10,
    GUILD_CONTROLLER_11,
    PVP_CONTROLLER_12,
    REWARDS_POOL_13,
    ITEM_BOX_CONTROLLER_14
  }

  uint constant private CACHE_SIZE = 15;

  struct ControllerContext {
    /// @notice Direct access to the controller
    IController controller;

    /// @notice All lazy-initialized addresses in order of {CacheIndex}
    address[CACHE_SIZE] cache;
  }
  //endregion ----------------- Data types

  //region ----------------- Initialization and _lazyInit
  function init(IController controller) internal pure returns (ControllerContext memory cc) {
    cc.controller = controller;
    return cc;
  }

  function _lazyInit(
    ControllerContext memory cc,
    CacheIndex index,
    function () external view returns(address) getter
  ) internal view returns (address) {
    address a = cc.cache[uint(index)];
    if (a != address(0)) return a;

    cc.cache[uint(index)] = getter();
    return cc.cache[uint(index)];
  }
  //endregion ----------------- Initialization and _lazyInit

  //region ----------------- Access with lazy initialization
  function statController(ControllerContext memory cc) internal view returns (IStatController) {
    return IStatController(_lazyInit(cc, CacheIndex.STAT_CONTROLLER_0, cc.controller.statController));
  }

  function storyController(ControllerContext memory cc) internal view returns (IStoryController) {
    return IStoryController(_lazyInit(cc, CacheIndex.STORY_CONTROLLER_1, cc.controller.storyController));
  }

  function oracle(ControllerContext memory cc) internal view returns (IOracle) {
    return IOracle(_lazyInit(cc, CacheIndex.ORACLE_2, cc.controller.oracle));
  }

  function treasury(ControllerContext memory cc) internal view returns (ITreasury) {
    return ITreasury(_lazyInit(cc, CacheIndex.TREASURY_3, cc.controller.treasury));
  }

  function dungeonFactory(ControllerContext memory cc) internal view returns (IDungeonFactory) {
    return IDungeonFactory(_lazyInit(cc, CacheIndex.DUNGEON_FACTORY_4, cc.controller.dungeonFactory));
  }

  function gameObjectController(ControllerContext memory cc) internal view returns (IGOC) {
    return IGOC(_lazyInit(cc, CacheIndex.GOC_5, cc.controller.gameObjectController));
  }

  function reinforcementController(ControllerContext memory cc) internal view returns (IReinforcementController) {
    return IReinforcementController(_lazyInit(cc, CacheIndex.REINFORCEMENT_CONTROLLER_6, cc.controller.reinforcementController));
  }

  function itemController(ControllerContext memory cc) internal view returns (IItemController) {
    return IItemController(_lazyInit(cc, CacheIndex.ITEM_CONTROLLER_7, cc.controller.itemController));
  }

  function heroController(ControllerContext memory cc) internal view returns (IHeroController) {
    return IHeroController(_lazyInit(cc, CacheIndex.HERO_CONTROLLER_8, cc.controller.heroController));
  }

  function gameToken(ControllerContext memory cc) internal view returns (IGameToken) {
    return IGameToken(_lazyInit(cc, CacheIndex.GAME_TOKEN_9, cc.controller.gameToken));
  }

  function userController(ControllerContext memory cc) internal view returns (IUserController) {
    return IUserController(_lazyInit(cc, CacheIndex.USER_CONTROLLER_10, cc.controller.userController));
  }

  function guildController(ControllerContext memory cc) internal view returns (IGuildController) {
    return IGuildController(_lazyInit(cc, CacheIndex.GUILD_CONTROLLER_11, cc.controller.guildController));
  }

  function pvpController(ControllerContext memory cc) internal view returns (IPvpController) {
    return IPvpController(_lazyInit(cc, CacheIndex.PVP_CONTROLLER_12, cc.controller.pvpController));
  }

  function rewardsPool(ControllerContext memory cc) internal view returns (IRewardsPool) {
    return IRewardsPool(_lazyInit(cc, CacheIndex.REWARDS_POOL_13, cc.controller.rewardsPool));
  }

  function itemBoxController(ControllerContext memory cc) internal view returns (IItemBoxController) {
    return IItemBoxController(_lazyInit(cc, CacheIndex.ITEM_BOX_CONTROLLER_14, cc.controller.itemBoxController));
  }
  //endregion ----------------- Access with lazy initialization
}

// 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)
    }
  }

}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../nft/NftBase.sol";
import "../openzeppelin/ERC721Holder.sol";
import "../interfaces/IHero.sol";
import "../interfaces/IHeroController.sol";
import "../interfaces/IStatController.sol";
import "../interfaces/IApplicationEvents.sol";

contract HeroBase is NftBase, IHero, ERC721Holder {

  //region ------------------------ Data types and constants
  /// @custom:storage-location erc7201:hero.base.storage
  struct HeroBaseStorage {
    mapping(uint => string) _heroUriByStatus;
  }

  /// @notice Version of the contract
  /// @dev Should be incremented when contract changed
  string public constant override VERSION = "2.0.1";
  // keccak256(abi.encode(uint256(keccak256("hero.base.storage")) - 1)) & ~bytes32(uint256(0xff))
  bytes32 private constant HeroBaseStorageLocation = 0xc546abf9ed7c8d4a6ded82a0edfd8f66c8300256eba0932933c3d559a091ea00;
  uint public constant KILL_PENALTY = 70;
  //endregion ------------------------ Data types and constants

  //region ------------------------ Initializer

  function init(
    string memory name_,
    string memory symbol_,
    address controller_,
    string memory uri
  ) external initializer {
    __NftBase_init(name_, symbol_, controller_, uri);
  }
  //endregion ------------------------ Initializer

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

  function _beforeTokenTransfer(uint heroId) internal override {
    if (
      ! IHeroController(IController(controller()).heroController()).beforeTokenTransfer(address(this), heroId)
    ) revert IAppErrors.TokenTransferNotAllowed();
  }
  //endregion ------------------------ Restrictions

  //region ------------------------ Views

  function _getHeroBaseStorage() private pure returns (HeroBaseStorage storage $) {
    assembly {
      $.slot := HeroBaseStorageLocation
    }
    return $;
  }

  function isHero() external pure override returns (bool) {
    return true;
  }

  /// @dev Every 10 levels we can show uniq img
  function _specificURI(uint heroId) internal view override returns (string memory) {
    uint level = IStatController(IController(controller()).statController()).heroStats(address(this), heroId).level;
    if (level / 10 == 0) {
      return "";
    }
    return _getHeroBaseStorage()._heroUriByStatus[level / 10];
  }
  //endregion ------------------------ Views

  //region ------------------------ Governance actions

  /// @dev Every 10 levels we can show uniq img
  function setHeroUriByStatus(string memory uri, uint statusLvl) external {
    onlyDeployer();

    _getHeroBaseStorage()._heroUriByStatus[statusLvl] = uri;

    emit IApplicationEvents.HeroUriByStatusChanged(uri, statusLvl);
  }
  //endregion ------------------------ Governance actions

  //region ------------------------ IHero actions

  function mintFor(address recipient) external override returns (uint heroId) {
    if (IController(controller()).heroController() != msg.sender) revert IAppErrors.ErrorNotHeroController(msg.sender);

    heroId = _incrementAndGetId();
    _safeMint(recipient, heroId);

    emit IApplicationEvents.HeroMinted(heroId);
  }

  function burn(uint heroId) external override {
    if (IController(controller()).heroController() != msg.sender) revert IAppErrors.ErrorNotHeroController(msg.sender);

    _burn(heroId);

    emit IApplicationEvents.HeroBurned(heroId);
  }
  //endregion ------------------------ IHero actions

}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../openzeppelin/ERC721EnumerableUpgradeable.sol";
import "../interfaces/IApplicationEvents.sol";
import "../proxy/Controllable.sol";

abstract contract NftBase is ERC721EnumerableUpgradeable, Controllable {

  //region ------------------------ Data types and constants
  /// @custom:storage-location erc7201:nft.base.storage
  struct NftBaseStorage {
    uint idCounter;
    string baseUri;
    mapping(uint => string) uniqueUri;
  }

  // keccak256(abi.encode(uint256(keccak256("nft.base.storage")) - 1)) & ~bytes32(uint256(0xff))
  bytes32 private constant NftBaseStorageLocation = 0xda2932ada77a3c8131d8c171a8679090714572b6a41aff2e2186c297ac0f5500;
  //endregion ------------------------ Data types and constants

  //region ------------------------ Initializer

  function __NftBase_init(
    string memory name_,
    string memory symbol_,
    address controller_,
    string memory uri
  ) internal onlyInitializing {
    _init(name_, symbol_, controller_, uri);
  }

  function _init(
    string memory name_,
    string memory symbol_,
    address controller_,
    string memory uri
  ) private {
    __ERC721_init(name_, symbol_);
    __Controllable_init(controller_);
    _getNftBaseStorage().idCounter = 1;
    _getNftBaseStorage().baseUri = uri;
    emit IApplicationEvents.BaseUriChanged(uri);
  }

  function _incrementAndGetId() internal returns (uint){
    uint id = _getNftBaseStorage().idCounter;
    // we are using uint64 id, so higher value will overflow in the game logic
    if (id + 1 >= uint(type(uint64).max)) revert IAppErrors.IdOverflow(id);
    _getNftBaseStorage().idCounter = id + 1;
    return id;
  }
  //endregion ------------------------ Initializer

  //region ------------------------ Restrictions
  function onlyDeployer() internal view {
    if (! IController(controller()).isDeployer(msg.sender)) revert IAppErrors.ErrorNotDeployer(msg.sender);
  }
  //endregion ------------------------ Restrictions

  //region ------------------------ Views

  function _getNftBaseStorage() private pure returns (NftBaseStorage storage $) {
    assembly {
      $.slot := NftBaseStorageLocation
    }
    return $;
  }

  function _baseURI() internal view override returns (string memory) {
    return _getNftBaseStorage().baseUri;
  }

  function exists(uint tokenId) external view returns (bool) {
    return _ownerOf(tokenId) != address(0);
  }

  function tokenURI(uint256 tokenId) public view override returns (string memory) {
    if (tokenId > _getNftBaseStorage().idCounter) revert IAppErrors.NotExistToken(tokenId);

    // unique uri used for concrete tokenId
    string memory uniqueURI = _getNftBaseStorage().uniqueUri[tokenId];
    if (bytes(uniqueURI).length != 0) {
      return uniqueURI;
    }

    // specific token uri used for group of ids based on nft internal logic (such as item rarity)
    string memory specificURI = _specificURI(tokenId);
    if (bytes(specificURI).length != 0) {
      return specificURI;
    }
    return _baseURI();
  }

  function _specificURI(uint) internal view virtual returns (string memory) {
    return "";
  }

  function baseURI() external view returns (string memory) {
    return _baseURI();
  }
  //endregion ------------------------ Views

  //region ------------------------ Gov actions

  function setUniqueUri(uint tokenId, string memory uri) external {
    onlyDeployer();
    _getNftBaseStorage().uniqueUri[tokenId] = uri;
    emit IApplicationEvents.UniqueUriChanged(tokenId, uri);
  }

  function setBaseUri(string memory value) external {
    onlyDeployer();
    _getNftBaseStorage().baseUri = value;
    emit IApplicationEvents.BaseUriChanged(value);
  }
  //endregion ------------------------ Gov actions

  //region ------------------------ Internal logic

  function _beforeTokenTransfer(uint tokenId) internal virtual {
    // noop
  }

  function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
    _beforeTokenTransfer(tokenId);
    return super._update(to, tokenId, auth);
  }
  //endregion ------------------------ Internal logic

}

// 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;
  }
}

// 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;
  }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)

pragma solidity ^0.8.20;

import "../interfaces/IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 */
abstract contract ERC165 is IERC165 {
  /**
   * @dev See {IERC165-supportsInterface}.
     */
  function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
    return interfaceId == type(IERC165).interfaceId;
  }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Enumerable.sol)

pragma solidity ^0.8.20;

import "./ERC721Upgradeable.sol";
import "../interfaces/IERC721Enumerable.sol";
import "./Initializable.sol";

/**
 * @dev This implements an optional extension of {ERC721} defined in the ERC that adds enumerability
 * of all the token ids in the contract as well as all token ids owned by each account.
 *
 * CAUTION: {ERC721} extensions that implement custom `balanceOf` logic, such as {ERC721Consecutive},
 * interfere with enumerability and should not be used together with {ERC721Enumerable}.
 */
abstract contract ERC721EnumerableUpgradeable is Initializable, ERC721Upgradeable, IERC721Enumerable {
  /// @custom:storage-location erc7201:openzeppelin.storage.ERC721Enumerable
  struct ERC721EnumerableStorage {
    mapping(address owner => mapping(uint256 index => uint256)) _ownedTokens;
    mapping(uint256 tokenId => uint256) _ownedTokensIndex;

    uint256[] _allTokens;
    mapping(uint256 tokenId => uint256) _allTokensIndex;
  }

  // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC721Enumerable")) - 1)) & ~bytes32(uint256(0xff))
  bytes32 private constant ERC721EnumerableStorageLocation = 0x645e039705490088daad89bae25049a34f4a9072d398537b1ab2425f24cbed00;

  function _getERC721EnumerableStorage() private pure returns (ERC721EnumerableStorage storage $) {
    assembly {
      $.slot := ERC721EnumerableStorageLocation
    }
    return $;
  }

  /**
   * @dev An `owner`'s token query was out of bounds for `index`.
     *
     * NOTE: The owner being `address(0)` indicates a global out of bounds index.
     */
  error ERC721OutOfBoundsIndex(address owner, uint256 index);

  /**
   * @dev Batch mint is not allowed.
     */
  error ERC721EnumerableForbiddenBatchMint();

  function __ERC721Enumerable_init() internal onlyInitializing {
  }

  function __ERC721Enumerable_init_unchained() internal onlyInitializing {
  }
  /**
   * @dev See {IERC165-supportsInterface}.
     */
  function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721Upgradeable) returns (bool) {
    return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);
  }

  /**
   * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
     */
  function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual returns (uint256) {
    ERC721EnumerableStorage storage $ = _getERC721EnumerableStorage();
    if (index >= balanceOf(owner)) {
      revert ERC721OutOfBoundsIndex(owner, index);
    }
    return $._ownedTokens[owner][index];
  }

  /**
   * @dev See {IERC721Enumerable-totalSupply}.
     */
  function totalSupply() public view virtual returns (uint256) {
    ERC721EnumerableStorage storage $ = _getERC721EnumerableStorage();
    return $._allTokens.length;
  }

  /**
   * @dev See {IERC721Enumerable-tokenByIndex}.
     */
  function tokenByIndex(uint256 index) public view virtual returns (uint256) {
    ERC721EnumerableStorage storage $ = _getERC721EnumerableStorage();
    if (index >= totalSupply()) {
      revert ERC721OutOfBoundsIndex(address(0), index);
    }
    return $._allTokens[index];
  }

  /**
   * @dev See {ERC721-_update}.
     */
  function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
    address previousOwner = super._update(to, tokenId, auth);

    if (previousOwner == address(0)) {
      _addTokenToAllTokensEnumeration(tokenId);
    } else if (previousOwner != to) {
      _removeTokenFromOwnerEnumeration(previousOwner, tokenId);
    }
    if (to == address(0)) {
      _removeTokenFromAllTokensEnumeration(tokenId);
    } else if (previousOwner != to) {
      _addTokenToOwnerEnumeration(to, tokenId);
    }

    return previousOwner;
  }

  /**
   * @dev Private function to add a token to this extension's ownership-tracking data structures.
     * @param to address representing the new owner of the given token ID
     * @param tokenId uint256 ID of the token to be added to the tokens list of the given address
     */
  function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
    ERC721EnumerableStorage storage $ = _getERC721EnumerableStorage();
    uint256 length = balanceOf(to) - 1;
    $._ownedTokens[to][length] = tokenId;
    $._ownedTokensIndex[tokenId] = length;
  }

  /**
   * @dev Private function to add a token to this extension's token tracking data structures.
     * @param tokenId uint256 ID of the token to be added to the tokens list
     */
  function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
    ERC721EnumerableStorage storage $ = _getERC721EnumerableStorage();
    $._allTokensIndex[tokenId] = $._allTokens.length;
    $._allTokens.push(tokenId);
  }

  /**
   * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
     * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
     * gas optimizations e.g. when performing a transfer operation (avoiding double writes).
     * This has O(1) time complexity, but alters the order of the _ownedTokens array.
     * @param from address representing the previous owner of the given token ID
     * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
     */
  function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
    ERC721EnumerableStorage storage $ = _getERC721EnumerableStorage();
    // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
    // then delete the last slot (swap and pop).

    uint256 lastTokenIndex = balanceOf(from);
    uint256 tokenIndex = $._ownedTokensIndex[tokenId];

    // When the token to delete is the last token, the swap operation is unnecessary
    if (tokenIndex != lastTokenIndex) {
      uint256 lastTokenId = $._ownedTokens[from][lastTokenIndex];

      $._ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
      $._ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
    }

    // This also deletes the contents at the last position of the array
    delete $._ownedTokensIndex[tokenId];
    delete $._ownedTokens[from][lastTokenIndex];
  }

  /**
   * @dev Private function to remove a token from this extension's token tracking data structures.
     * This has O(1) time complexity, but alters the order of the _allTokens array.
     * @param tokenId uint256 ID of the token to be removed from the tokens list
     */
  function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
    ERC721EnumerableStorage storage $ = _getERC721EnumerableStorage();
    // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
    // then delete the last slot (swap and pop).

    uint256 lastTokenIndex = $._allTokens.length - 1;
    uint256 tokenIndex = $._allTokensIndex[tokenId];

    // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
    // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
    // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
    uint256 lastTokenId = $._allTokens[lastTokenIndex];

    $._allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
    $._allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index

    // This also deletes the contents at the last position of the array
    delete $._allTokensIndex[tokenId];
    $._allTokens.pop();
  }

  /**
   * See {ERC721-_increaseBalance}. We need that to account tokens that were minted in batch
   */
  function _increaseBalance(address account, uint128 amount) internal virtual override {
    if (amount > 0) {
      revert ERC721EnumerableForbiddenBatchMint();
    }
    super._increaseBalance(account, amount);
  }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../interfaces/IERC721Receiver.sol";

/**
 * @dev Implementation of the {IERC721Receiver} interface.
 *
 * Accepts all token transfers.
 * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}.
 */
contract ERC721Holder is IERC721Receiver {
  /**
   * @dev See {IERC721Receiver-onERC721Received}.
   *
   * Always returns `IERC721Receiver.onERC721Received.selector`.
   */
  function onERC721Received(
    address,
    address,
    uint256,
    bytes memory
  ) public virtual override returns (bytes4) {
    return this.onERC721Received.selector;
  }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/ERC721.sol)

pragma solidity ^0.8.20;

import "../interfaces/IERC721.sol";
import "../interfaces/IERC721Receiver.sol";
import "../interfaces/IERC721Metadata.sol";
import "../interfaces/IERC721Errors.sol";
import "./Strings.sol";
import "./ERC165.sol";
import "./Initializable.sol";
import "../relay/ERC2771Context.sol";

/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC-721] Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is available separately as
 * {ERC721Enumerable}.
 */
abstract contract ERC721Upgradeable is Initializable, ERC2771Context, ERC165, IERC721, IERC721Metadata, IERC721Errors {
  using Strings for uint256;

  /// @custom:storage-location erc7201:openzeppelin.storage.ERC721
  struct ERC721Storage {
    // Token name
    string _name;

    // Token symbol
    string _symbol;

    mapping(uint256 tokenId => address) _owners;

    mapping(address owner => uint256) _balances;

    mapping(uint256 tokenId => address) _tokenApprovals;

    mapping(address owner => mapping(address operator => bool)) _operatorApprovals;
  }

  // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC721")) - 1)) & ~bytes32(uint256(0xff))
  bytes32 private constant ERC721StorageLocation = 0x80bb2b638cc20bc4d0a60d66940f3ab4a00c1d7b313497ca82fb0b4ab0079300;

  function _getERC721Storage() private pure returns (ERC721Storage storage $) {
    assembly {
      $.slot := ERC721StorageLocation
    }
    return $;
  }

  /**
   * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
     */
  function __ERC721_init(string memory name_, string memory symbol_) internal onlyInitializing {
    __ERC721_init_unchained(name_, symbol_);
  }

  function __ERC721_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
    ERC721Storage storage $ = _getERC721Storage();
    $._name = name_;
    $._symbol = symbol_;
  }

  /**
   * @dev See {IERC165-supportsInterface}.
     */
  function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
    return
      interfaceId == type(IERC721).interfaceId ||
      interfaceId == type(IERC721Metadata).interfaceId ||
      super.supportsInterface(interfaceId);
  }

  /**
   * @dev See {IERC721-balanceOf}.
     */
  function balanceOf(address owner) public view virtual returns (uint256) {
    ERC721Storage storage $ = _getERC721Storage();
    if (owner == address(0)) {
      revert ERC721InvalidOwner(address(0));
    }
    return $._balances[owner];
  }

  /**
   * @dev See {IERC721-ownerOf}.
     */
  function ownerOf(uint256 tokenId) public view virtual returns (address) {
    return _requireOwned(tokenId);
  }

  /**
   * @dev See {IERC721Metadata-name}.
     */
  function name() public view virtual returns (string memory) {
    ERC721Storage storage $ = _getERC721Storage();
    return $._name;
  }

  /**
   * @dev See {IERC721Metadata-symbol}.
     */
  function symbol() public view virtual returns (string memory) {
    ERC721Storage storage $ = _getERC721Storage();
    return $._symbol;
  }

  /**
   * @dev See {IERC721Metadata-tokenURI}.
     */
  function tokenURI(uint256 tokenId) public view virtual returns (string memory) {
    _requireOwned(tokenId);

    string memory baseURI = _baseURI();
    return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : "";
  }

  /**
   * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
     * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
     * by default, can be overridden in child contracts.
     */
  function _baseURI() internal view virtual returns (string memory) {
    return "";
  }

  /**
   * @dev See {IERC721-approve}.
     */
  function approve(address to, uint256 tokenId) public virtual {
    _approve(to, tokenId, _msgSender());
  }

  /**
   * @dev See {IERC721-getApproved}.
     */
  function getApproved(uint256 tokenId) public view virtual returns (address) {
    _requireOwned(tokenId);

    return _getApproved(tokenId);
  }

  /**
   * @dev See {IERC721-setApprovalForAll}.
     */
  function setApprovalForAll(address operator, bool approved) public virtual {
    _setApprovalForAll(_msgSender(), operator, approved);
  }

  /**
   * @dev See {IERC721-isApprovedForAll}.
     */
  function isApprovedForAll(address owner, address operator) public view virtual returns (bool) {
    ERC721Storage storage $ = _getERC721Storage();
    return $._operatorApprovals[owner][operator];
  }

  /**
   * @dev See {IERC721-transferFrom}.
     */
  function transferFrom(address from, address to, uint256 tokenId) public virtual {
    if (to == address(0)) {
      revert ERC721InvalidReceiver(address(0));
    }
    // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists
    // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here.
    address previousOwner = _update(to, tokenId, _msgSender());
    if (previousOwner != from) {
      revert ERC721IncorrectOwner(from, tokenId, previousOwner);
    }
  }

  /**
   * @dev See {IERC721-safeTransferFrom}.
     */
  function safeTransferFrom(address from, address to, uint256 tokenId) public {
    safeTransferFrom(from, to, tokenId, "");
  }

  /**
   * @dev See {IERC721-safeTransferFrom}.
     */
  function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual {
    transferFrom(from, to, tokenId);
    _checkOnERC721Received(from, to, tokenId, data);
  }

  /**
   * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
     *
     * IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the
     * core ERC-721 logic MUST be matched with the use of {_increaseBalance} to keep balances
     * consistent with ownership. The invariant to preserve is that for any address `a` the value returned by
     * `balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`.
     */
  function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
    ERC721Storage storage $ = _getERC721Storage();
    return $._owners[tokenId];
  }

  /**
   * @dev Returns the approved address for `tokenId`. Returns 0 if `tokenId` is not minted.
     */
  function _getApproved(uint256 tokenId) internal view virtual returns (address) {
    ERC721Storage storage $ = _getERC721Storage();
    return $._tokenApprovals[tokenId];
  }

  /**
   * @dev Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in
     * particular (ignoring whether it is owned by `owner`).
     *
     * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
     * assumption.
     */
  function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) {
    return
      spender != address(0) &&
      (owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender);
  }

  /**
   * @dev Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner.
     * Reverts if `spender` does not have approval from the provided `owner` for the given token or for all its assets
     * the `spender` for the specific `tokenId`.
     *
     * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
     * assumption.
     */
  function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual {
    if (!_isAuthorized(owner, spender, tokenId)) {
      if (owner == address(0)) {
        revert ERC721NonexistentToken(tokenId);
      } else {
        revert ERC721InsufficientApproval(spender, tokenId);
      }
    }
  }

  /**
   * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
     *
     * NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that
     * a uint256 would ever overflow from increments when these increments are bounded to uint128 values.
     *
     * WARNING: Increasing an account's balance using this function tends to be paired with an override of the
     * {_ownerOf} function to resolve the ownership of the corresponding tokens so that balances and ownership
     * remain consistent with one another.
     */
  function _increaseBalance(address account, uint128 value) internal virtual {
    ERC721Storage storage $ = _getERC721Storage();
    unchecked {
      $._balances[account] += value;
    }
  }

  /**
   * @dev Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner
     * (or `to`) is the zero address. Returns the owner of the `tokenId` before the update.
     *
     * The `auth` argument is optional. If the value passed is non 0, then this function will check that
     * `auth` is either the owner of the token, or approved to operate on the token (by the owner).
     *
     * Emits a {Transfer} event.
     *
     * NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}.
     */
  function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) {
    ERC721Storage storage $ = _getERC721Storage();
    address from = _ownerOf(tokenId);

    // Perform (optional) operator check
    if (auth != address(0)) {
      _checkAuthorized(from, auth, tokenId);
    }

    // Execute the update
    if (from != address(0)) {
      // Clear approval. No need to re-authorize or emit the Approval event
      _approve(address(0), tokenId, address(0), false);

      unchecked {
        $._balances[from] -= 1;
      }
    }

    if (to != address(0)) {
      unchecked {
        $._balances[to] += 1;
      }
    }

    $._owners[tokenId] = to;

    emit Transfer(from, to, tokenId);

    return from;
  }

  /**
   * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
  function _mint(address to, uint256 tokenId) internal {
    if (to == address(0)) {
      revert ERC721InvalidReceiver(address(0));
    }
    address previousOwner = _update(to, tokenId, address(0));
    if (previousOwner != address(0)) {
      revert ERC721InvalidSender(address(0));
    }
  }

  /**
   * @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance.
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
  function _safeMint(address to, uint256 tokenId) internal {
    _safeMint(to, tokenId, "");
  }

  /**
   * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
  function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
    _mint(to, tokenId);
    _checkOnERC721Received(address(0), to, tokenId, data);
  }

  /**
   * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     * This is an internal function that does not check if the sender is authorized to operate on the token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
  function _burn(uint256 tokenId) internal {
    address previousOwner = _update(address(0), tokenId, address(0));
    if (previousOwner == address(0)) {
      revert ERC721NonexistentToken(tokenId);
    }
  }

  /**
   * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
  function _transfer(address from, address to, uint256 tokenId) internal {
    if (to == address(0)) {
      revert ERC721InvalidReceiver(address(0));
    }
    address previousOwner = _update(to, tokenId, address(0));
    if (previousOwner == address(0)) {
      revert ERC721NonexistentToken(tokenId);
    } else if (previousOwner != from) {
      revert ERC721IncorrectOwner(from, tokenId, previousOwner);
    }
  }

  /**
   * @dev Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients
     * are aware of the ERC-721 standard to prevent tokens from being forever locked.
     *
     * `data` is additional data, it has no specified format and it is sent in call to `to`.
     *
     * This internal function is like {safeTransferFrom} in the sense that it invokes
     * {IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `tokenId` token must exist and be owned by `from`.
     * - `to` cannot be the zero address.
     * - `from` cannot be the zero address.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
  function _safeTransfer(address from, address to, uint256 tokenId) internal {
    _safeTransfer(from, to, tokenId, "");
  }

  /**
   * @dev Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
  function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
    _transfer(from, to, tokenId);
    _checkOnERC721Received(from, to, tokenId, data);
  }

  /**
   * @dev Approve `to` to operate on `tokenId`
     *
     * The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is
     * either the owner of the token, or approved to operate on all tokens held by this owner.
     *
     * Emits an {Approval} event.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
  function _approve(address to, uint256 tokenId, address auth) internal {
    _approve(to, tokenId, auth, true);
  }

  /**
   * @dev Variant of `_approve` with an optional flag to enable or disable the {Approval} event. The event is not
     * emitted in the context of transfers.
     */
  function _approve(address to, uint256 tokenId, address auth, bool emitEvent) internal virtual {
    ERC721Storage storage $ = _getERC721Storage();
    // Avoid reading the owner unless necessary
    if (emitEvent || auth != address(0)) {
      address owner = _requireOwned(tokenId);

      // We do not use _isAuthorized because single-token approvals should not be able to call approve
      if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) {
        revert ERC721InvalidApprover(auth);
      }

      if (emitEvent) {
        emit Approval(owner, to, tokenId);
      }
    }

    $._tokenApprovals[tokenId] = to;
  }

  /**
   * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Requirements:
     * - operator can't be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
  function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
    ERC721Storage storage $ = _getERC721Storage();
    if (operator == address(0)) {
      revert ERC721InvalidOperator(operator);
    }
    $._operatorApprovals[owner][operator] = approved;
    emit ApprovalForAll(owner, operator, approved);
  }

  /**
   * @dev Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned).
     * Returns the owner.
     *
     * Overrides to ownership logic should be done to {_ownerOf}.
     */
  function _requireOwned(uint256 tokenId) internal view returns (address) {
    address owner = _ownerOf(tokenId);
    if (owner == address(0)) {
      revert ERC721NonexistentToken(tokenId);
    }
    return owner;
  }

  /**
   * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target address. This will revert if the
     * recipient doesn't accept the token transfer. The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param data bytes optional data to send along with the call
     */
  function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private {
    if (to.code.length > 0) {
      try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
        if (retval != IERC721Receiver.onERC721Received.selector) {
          revert ERC721InvalidReceiver(to);
        }
      } catch (bytes memory reason) {
        if (reason.length == 0) {
          revert ERC721InvalidReceiver(to);
        } else {
          /// @solidity memory-safe-assembly
          assembly {
            revert(add(32, reason), mload(reason))
          }
        }
      }
    }
  }
}

// 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
    }
  }
}

// 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;
  }

}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
  /**
   * @dev Returns the largest of two signed numbers.
     */
  function max(int256 a, int256 b) internal pure returns (int256) {
    return a > b ? a : b;
  }

  /**
   * @dev Returns the smallest of two signed numbers.
     */
  function min(int256 a, int256 b) internal pure returns (int256) {
    return a < b ? a : b;
  }

  /**
   * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
  function average(int256 a, int256 b) internal pure returns (int256) {
    // Formula from the book "Hacker's Delight"
    int256 x = (a & b) + ((a ^ b) >> 1);
    return x + (int256(uint256(x) >> 255) & (a ^ b));
  }

  /**
   * @dev Returns the absolute unsigned value of a signed value.
     */
  function abs(int256 n) internal pure returns (uint256) {
    unchecked {
    // must be unchecked in order to support `n = type(int256).min`
      return uint256(n >= 0 ? n : -n);
    }
  }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)

pragma solidity ^0.8.20;

import "./Math.sol";
import "./SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
  bytes16 private constant HEX_DIGITS = "0123456789abcdef";
  uint8 private constant ADDRESS_LENGTH = 20;

  /**
   * @dev The `value` string doesn't fit in the specified `length`.
     */
  error StringsInsufficientHexLength(uint256 value, uint256 length);

  /**
   * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
  function toString(uint256 value) internal pure returns (string memory) {
    unchecked {
      uint256 length = Math.log10(value) + 1;
      string memory buffer = new string(length);
      uint256 ptr;
    /// @solidity memory-safe-assembly
      assembly {
        ptr := add(buffer, add(32, length))
      }
      while (true) {
        ptr--;
        /// @solidity memory-safe-assembly
        assembly {
          mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
        }
        value /= 10;
        if (value == 0) break;
      }
      return buffer;
    }
  }

  /**
   * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
  function toStringSigned(int256 value) internal pure returns (string memory) {
    return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
  }

  /**
   * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
  function toHexString(uint256 value) internal pure returns (string memory) {
    unchecked {
      return toHexString(value, Math.log256(value) + 1);
    }
  }

  /**
   * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
  function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
    uint256 localValue = value;
    bytes memory buffer = new bytes(2 * length + 2);
    buffer[0] = "0";
    buffer[1] = "x";
    for (uint256 i = 2 * length + 1; i > 1; --i) {
      buffer[i] = HEX_DIGITS[localValue & 0xf];
      localValue >>= 4;
    }
    if (localValue != 0) {
      revert StringsInsufficientHexLength(value, length);
    }
    return string(buffer);
  }

  /**
   * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
     * representation.
     */
  function toHexString(address addr) internal pure returns (string memory) {
    return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
  }

  /**
   * @dev Returns true if the two strings are equal.
     */
  function equal(string memory a, string memory b) internal pure returns (bool) {
    return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
  }
}

// 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);
  }

}

// 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);
    }
  }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):