Contract

0x439DaF52DBA08BEc47774b910eBA66b944e5eEb0

Overview

S Balance

Sonic LogoSonic LogoSonic Logo0 S

S Value

-

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

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

Contract Source Code Verified (Exact Match)

Contract Name:
StatController

Compiler Version
v0.8.23+commit.f704f362

Optimization Enabled:
Yes with 50 runs

Other Settings:
istanbul EvmVersion
File 1 of 24 : StatController.sol
// SPDX-License-Identifier: BUSL-1.1
/**
            ▒▓▒  ▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓███▓▓▒     ▒▒▒▒▓▓▓▒▓▓▓▓▓▓▓██▓
             ▒██▒▓▓▓▓█▓██████████████████▓  ▒▒▒▓███████████████▒
              ▒██▒▓█████████████████████▒ ▒▓██████████▓███████
               ▒███████████▓▒                   ▒███▓▓██████▓
                 █████████▒                     ▒▓▒▓███████▒
                  ███████▓      ▒▒▒▒▒▓▓█▓▒     ▓█▓████████
                   ▒▒▒▒▒   ▒▒▒▒▓▓▓█████▒      ▓█████████▓
                         ▒▓▓▓▒▓██████▓      ▒▓▓████████▒
                       ▒██▓▓▓███████▒      ▒▒▓███▓████
                        ▒███▓█████▒       ▒▒█████▓██▓
                          ██████▓   ▒▒▒▓██▓██▓█████▒
                           ▒▒▓▓▒   ▒██▓▒▓▓████████
                                  ▓█████▓███████▓
                                 ██▓▓██████████▒
                                ▒█████████████
                                 ███████████▓
      ▒▓▓▓▓▓▓▒▓                  ▒█████████▒                      ▒▓▓
    ▒▓█▒   ▒▒█▒▒                   ▓██████                       ▒▒▓▓▒
   ▒▒█▒       ▓▒                    ▒████                       ▒▓█▓█▓▒
   ▓▒██▓▒                             ██                       ▒▓█▓▓▓██▒
    ▓█▓▓▓▓▓█▓▓▓▒        ▒▒▒         ▒▒▒▓▓▓▓▒▓▒▒▓▒▓▓▓▓▓▓▓▓▒    ▒▓█▒ ▒▓▒▓█▓
     ▒▓█▓▓▓▓▓▓▓▓▓▓▒    ▒▒▒▓▒     ▒▒▒▓▓     ▓▓  ▓▓█▓   ▒▒▓▓   ▒▒█▒   ▒▓▒▓█▓
            ▒▒▓▓▓▒▓▒  ▒▓▓▓▒█▒   ▒▒▒█▒          ▒▒█▓▒▒▒▓▓▓▒   ▓██▓▓▓▓▓▓▓███▓
 ▒            ▒▓▓█▓  ▒▓▓▓▓█▓█▓  ▒█▓▓▒          ▓▓█▓▒▓█▓▒▒   ▓█▓        ▓███▓
▓▓▒         ▒▒▓▓█▓▒▒▓█▒   ▒▓██▓  ▓██▓▒     ▒█▓ ▓▓██   ▒▓▓▓▒▒▓█▓        ▒▓████▒
 ██▓▓▒▒▒▒▓▓███▓▒ ▒▓▓▓▓▒▒ ▒▓▓▓▓▓▓▓▒▒▒▓█▓▓▓▓█▓▓▒▒▓▓▓▓▓▒    ▒▓████▓▒     ▓▓███████▓▓▒
*/
pragma solidity 0.8.23;
import "../proxy/Controllable.sol";
import "../lib/StatControllerLib.sol";

contract StatController is Controllable, IStatController {

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

  /// @notice Version of the contract
  /// @dev Should be incremented when contract changed
  string public constant override VERSION = "2.1.2";
  //endregion ------------------------ CONSTANTS

  //region ------------------------ INITIALIZER

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

  //region ------------------------ VIEWS

  function heroInitialAttributes(uint heroClass) external view returns (StatLib.InitialHero memory) {
    return StatLib.initialHero(heroClass);
  }

  function heroAttributes(address token, uint tokenId) public view override returns (int32[] memory) {
    return StatControllerLib.heroAttributes(StatControllerLib._S(), token, tokenId);
  }

  function heroBonusAttributes(address token, uint tokenId) public view returns (int32[] memory) {
    return StatControllerLib.heroBonusAttributes(StatControllerLib._S(), token, tokenId);
  }

  function heroTemporallyAttributes(address token, uint tokenId) public view returns (int32[] memory) {
    return StatControllerLib.heroTemporallyAttributes(StatControllerLib._S(), token, tokenId);
  }

  function heroAttributesLength(address token, uint tokenId) external pure override returns (uint) {
    return StatControllerLib.heroAttributesLength(token, tokenId);
  }

  function heroAttribute(address token, uint tokenId, uint index) external view override returns (int32) {
    return StatControllerLib.heroAttribute(StatControllerLib._S(), token, tokenId, index);
  }

  function heroBaseAttributes(address token, uint tokenId) public view override returns (CoreAttributes memory core) {
    return StatControllerLib.heroBaseAttributes(StatControllerLib._S(), token, tokenId);
  }

  function heroCustomData(address hero, uint heroId, bytes32 index) external view override returns (uint) {
    return StatControllerLib.heroCustomData(IHeroController(IController(controller()).heroController()), hero, heroId, index);
  }

  function heroCustomDataOnNgLevel(address hero, uint heroId, bytes32 index, uint8 ngLevel) external view returns (uint) {
    return StatControllerLib.heroCustomDataOnNgLevel(IHeroController(IController(controller()).heroController()), hero, heroId, index, ngLevel);
  }

  function getAllHeroCustomData(address token, uint tokenId) external view returns (bytes32[] memory keys, uint[] memory values) {
    return StatControllerLib.getAllHeroCustomData(IHeroController(IController(controller()).heroController()), token, tokenId);
  }

  function globalCustomData(bytes32 index) external view override returns (uint) {
    return StatControllerLib.globalCustomData(StatControllerLib._S(), index);
  }

  function heroStats(address token, uint tokenId) public view override returns (ChangeableStats memory result) {
    return StatControllerLib.heroStats(StatControllerLib._S(), token, tokenId);
  }

  function heroItemSlot(address heroToken, uint64 heroTokenId, uint8 itemSlot) public view override returns (bytes32 nftPacked) {
    return StatControllerLib.heroItemSlot(StatControllerLib._S(), heroToken, heroTokenId, itemSlot);
  }

  function heroItemSlots(address heroToken, uint heroTokenId) external view override returns (uint8[] memory) {
    return StatControllerLib.heroItemSlots(StatControllerLib._S(), heroToken, heroTokenId);
  }

  function isHeroAlive(address heroToken, uint heroTokenId) external view override returns (bool) {
    return StatControllerLib.isHeroAlive(StatControllerLib._S(), heroToken, heroTokenId);
  }

  function isConsumableUsed(address heroToken, uint heroTokenId, address item) external view returns (bool) {
    return StatControllerLib.isConsumableUsed(StatControllerLib._S(), heroToken, heroTokenId, item);
  }

  function buffHero(BuffInfo calldata info) external view override returns (int32[] memory, int32) {
    return StatControllerLib.buffHero(StatControllerLib._S(), IController(controller()), info);
  }
  //endregion ------------------------ VIEWS

  //region ------------------------ PURE

  function levelUpExperienceRequired(uint32 level) external pure returns (uint) {
    return StatControllerLib.levelUpExperienceRequired(level);
  }

  function levelExperience(uint32 level) external pure returns (uint) {
    return StatLib.levelExperience(level);
  }
  //endregion ------------------------ PURE

  //region ------------------------ ACTIONS

  function initNewHero(address heroToken, uint heroTokenId, uint heroClass) external override {
    return StatControllerLib.initNewHero(StatControllerLib._S(), IController(controller()), heroToken, heroTokenId, heroClass);
  }

  function resetHeroCustomData(address heroToken, uint heroTokenId) external {
    return StatControllerLib.resetHeroCustomData(StatControllerLib._S(), IController(controller()), heroToken, heroTokenId);
  }

  function changeHeroItemSlot(
    address heroToken,
    uint64 heroTokenId,
    uint itemType,
    uint8 itemSlot,
    address itemToken,
    uint itemTokenId,
    bool equip
  ) external override {
    return StatControllerLib.changeHeroItemSlot(
      StatControllerLib._S(),
      IController(controller()),
      heroToken,
      heroTokenId,
      itemType,
      itemSlot,
      itemToken,
      itemTokenId,
      equip
    );
  }

  function changeCurrentStats(address heroToken, uint heroTokenId, ChangeableStats memory change, bool increase) external override {
    return StatControllerLib.changeCurrentStats(StatControllerLib._S(), IController(controller()), heroToken, heroTokenId, change, increase);
  }

  function registerConsumableUsage(address heroToken, uint heroTokenId, address item) external override {
    return StatControllerLib.registerConsumableUsage(StatControllerLib._S(), IController(controller()), heroToken, heroTokenId, item);
  }

  function clearUsedConsumables(address heroToken, uint heroTokenId) external override {
    return StatControllerLib.clearUsedConsumables(StatControllerLib._S(), IController(controller()), heroToken, heroTokenId);
  }

  function changeBonusAttributes(ChangeAttributesInfo memory info) external override {
    return StatControllerLib.changeBonusAttributes(StatControllerLib._S(), IController(controller()), info);
  }

  function clearTemporallyAttributes(address heroToken, uint heroTokenId) external override {
    return StatControllerLib.clearTemporallyAttributes(StatControllerLib._S(), IController(controller()), heroToken, heroTokenId);
  }

  function levelUp(address heroToken, uint heroTokenId, uint heroClass, CoreAttributes memory change)
  external override returns (uint newLvl) {
    return StatControllerLib.levelUp(StatControllerLib._S(), IController(controller()), heroToken, heroTokenId, heroClass, change);
  }

  function setHeroCustomData(address token, uint tokenId, bytes32 index, uint value) external override {
    return StatControllerLib.setHeroCustomData(StatControllerLib._S(), IController(controller()), token, tokenId, index, value);
  }

  function setGlobalCustomData(bytes32 index, uint value) external override {
    return StatControllerLib.setGlobalCustomData(StatControllerLib._S(), IController(controller()), index, value);
  }

  /// @notice Restore life and mana during reinforcement
  /// @param prevAttributes Hero attributes before reinforcement
  function restoreLifeAndMana(address heroToken, uint heroTokenId, int32[] memory prevAttributes) external override {
    return StatControllerLib.restoreLifeAndMana(StatControllerLib._S(), IController(controller()), heroToken, heroTokenId, prevAttributes);
  }

  function reborn(address heroToken, uint heroTokenId, uint heroClass) external override {
    StatControllerLib.reborn(IController(controller()), heroToken, heroTokenId, heroClass);
  }
  //endregion ------------------------ ACTIONS

}

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

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

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

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

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

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

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

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

  //endregion ERC20Errors

  //region ERC721Errors

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

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

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

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

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

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

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

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

  //endregion ERC721Errors

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

pragma solidity 0.8.23;

interface IControllable {

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

  function revision() external view returns (uint);

  function previousImplementation() external view returns (address);

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

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

  function created() external view returns (uint256);

  function createdBlock() external view returns (uint256);

  function controller() external view returns (address);

  function increaseRevision(address oldLogic) external;

}

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

pragma solidity 0.8.23;

interface IController {

  function governance() external view returns (address);

  function statController() external view returns (address);

  function storyController() external view returns (address);

  function gameObjectController() external view returns (address);

  function reinforcementController() external view returns (address);

  function oracle() external view returns (address);

  function treasury() external view returns (address);

  function itemController() external view returns (address);

  function heroController() external view returns (address);

  function dungeonFactory() external view returns (address);

  function gameToken() external view returns (address);

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

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

  function onPause() external view returns (bool);

  function userController() external view returns (address);

  function guildController() external view returns (address);

  function rewardsPool() external view returns (address);

  function gameTokenPrice() external view returns (uint);

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

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

pragma solidity 0.8.23;

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

interface IDungeonFactory {

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

    // ---

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

    // ---

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

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

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

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

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

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

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

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

    uint32[] uniqObjects;

    uint8 minLevel;
    uint8 maxLevel;

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

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

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

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

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

    ObjectGenerateInfo info;
  }

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

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

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

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

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

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

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

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

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

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

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

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

  function maxAvailableBiome() external view returns (uint8);

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

File 7 of 24 : IFightCalculator.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

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

interface IFightCalculator {

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

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

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

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

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

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

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

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

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

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

File 8 of 24 : IGOC.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

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

interface IGOC {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    uint8[] attributeIds;
    int32[] attributeValues;

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

    address attackToken;
    uint64 attackTokenId;
    uint8 attackType;

    address[] mintItems;
    uint32[] mintItemsChances;
  }

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

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

    uint32 goodChance;

    AttributeGenerateInfo goodAttributes;
    AttributeGenerateInfo badAttributes;

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

    address[] mintItems;
    uint32[] mintItemsChances;
  }

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

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

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

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

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

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

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

}

File 9 of 24 : IHeroController.sol
// SPDX-License-Identifier: BUSL-1.1

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

interface IHeroController {

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

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

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

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

    // ---

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

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

    // ---

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

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

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

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


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

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

    mapping(bytes32 packedHero => HeroInfo) heroInfo;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    /// @notice Desired hero name
    string heroName;

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

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


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

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

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

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

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

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

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

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

  // ---

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

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

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

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

  function resetLifeAndMana(address hero, uint heroId) external;

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

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

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

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

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

File 10 of 24 : IItemController.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

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

interface IItemController {

  enum GlobalParam {
    UNKNOWN_0,

    /// @notice Address of ItemControllerHelper
    ITEM_CONTROLLER_HELPER_ADDRESS_1
  }

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

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

    EnumerableSet.AddressSet items;

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

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

    // --- common attr ---

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

    // --- consumable ---

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

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

    // --- buff ---

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

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

    // --- attack ---

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

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

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

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

    // --- common attr ---

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

    // --- consumable ---

    // consumable stats unchangeable, get them by address

    // --- buff ---

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

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

    // --- attack ---

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

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

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


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

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

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

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

    IGOC.AttributeGenerateInfo consumableAttributes;
    IStatController.ChangeableStats consumableStats;

    ItemGenerateInfo casterAttributes;
    ItemGenerateInfo targetAttributes;

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

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

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

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

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

    IStatController.CoreAttributes requirements;
  }

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

    END_SLOT
  }

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

    END_SLOT
  }

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

    END_SLOT
  }

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

    END_SLOT
  }

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

    END_SLOT
  }

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

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

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

    IItemController.AttackInfo attackInfo;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  // ---

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

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

  function destroy(address item, uint tokenId) external;

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

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

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

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

File 11 of 24 : IOracle.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

interface IOracle {

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

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

}

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

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

interface IStatController {

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

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


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

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

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

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

    END_SLOT // 43
  }

  // possible
  // HEAL_FACTOR

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

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

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

  struct NftItem {
    address token;
    uint tokenId;
  }

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

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

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

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

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

    address[] mintedItems;
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  function changeBonusAttributes(ChangeAttributesInfo memory info) external;

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

  function clearUsedConsumables(address heroToken, uint heroTokenId) external;

  function clearTemporallyAttributes(address heroToken, uint heroTokenId) external;

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

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

  function setGlobalCustomData(bytes32 index, uint value) external;

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

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

File 13 of 24 : IStoryController.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

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

interface IStoryController {

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

    END_SLOT
  }

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

    END_SLOT
  }

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

    // --- STORY REG INFO ---

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

    // --- ANSWER MAPPING ---

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

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

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

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

    // --- ANSWER REQUIREMENTS ---

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

    // --- ANSWER RESULTS ---

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

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

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

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

    // --- GENERAL STORY REQUIREMENTS ---

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

    // --- HERO STATES ---

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

    // --- OTHER ---

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

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

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

    // --- story reqs

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

    // --- answer reqs

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

    // --- answer results

    AnswerBurnRandomItemMeta answerBurnRandomItemMeta;
    NextObjRewriteMeta nextObjRewriteMeta;

    // --- story results

    AnswerResultMeta successInfo;
    AnswerResultMeta failInfo;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  // --- WRITE ---

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

  // --- READ ---

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

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

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

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

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

}

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

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

library CalcLib {

  uint32 public constant MAX_CHANCE = 1e9;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}

File 15 of 24 : PackingLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

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

library PackingLib {

  //////////////////////////
  // ---- PACKING LOGIC ----
  //////////////////////////

  //region ------------------------------------ COMMON

  function packNftId(address token, uint id) internal pure returns (bytes32 serialized) {
    if (id > uint(type(uint64).max)) revert IAppErrors.TooHighValue(id);
    serialized = bytes32(uint(uint160(token)));
    serialized |= bytes32(uint(uint64(id))) << 160;
  }

  function unpackNftId(bytes32 data) internal pure returns (address token, uint id) {
    token = address(uint160(uint(data)));
    id = uint(data) >> 160;
  }

  function packAddressWithAmount(address token, uint amount) internal pure returns (bytes32 data) {
    if (amount > uint(type(uint96).max)) revert IAppErrors.TooHighValue(amount);
    data = bytes32(uint(uint160(token)));
    data |= bytes32(uint(uint96(amount))) << 160;
  }

  function unpackAddressWithAmount(bytes32 data) internal pure returns (address token, uint amount) {
    token = address(uint160(uint(data)));
    amount = uint(data) >> 160;
  }

  function packItemMintInfo(address item, uint32 chance) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(item)));
    data |= bytes32(uint(chance)) << 160;
  }

  function unpackItemMintInfo(bytes32 data) internal pure returns (address item, uint32 chance) {
    item = address(uint160(uint(data)));
    chance = uint32(uint(data) >> 160);
  }

  /// @param customDataIndex We assume, that two lowest bytes of this string are always zero
  /// So, the string looks like following: 0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0000
  /// Last 2 bytes will be used to encode {value}
  function packCustomDataChange(bytes32 customDataIndex, int16 value) internal pure returns (bytes32 data) {
    if (uint(customDataIndex) != (uint(customDataIndex) >> 16) << 16) revert IAppErrors.IncompatibleInputString();
    data = bytes32(uint(customDataIndex));
    data |= bytes32(uint(uint16(value)));
  }

  function unpackCustomDataChange(bytes32 data) internal pure returns (bytes32 customDataIndex, int16 value) {
    customDataIndex = bytes32((uint(data) >> 16) << 16);
    value = int16(int(uint(uint16(uint(data)))));
  }

  /// @dev min(uint64) + max(uint64) + isHeroData/isMandatory(uint8)
  function packCustomDataRequirements(uint64 min, uint64 max, bool key) internal pure returns (bytes32 data) {
    data = bytes32(uint(min));
    data |= bytes32(uint(max)) << 64;
    data |= bytes32(uint(key ? uint8(1) : uint8(0))) << (64 + 64);
  }

  function unpackCustomDataRequirements(bytes32 data) internal pure returns (uint64 min, uint64 max, bool key) {
    min = uint64(uint(data));
    max = uint64(uint(data) >> 64);
    key = uint8(uint(data) >> (64 + 64)) == uint8(1);
  }

  function packStatsChange(
    uint32 experience,
    int32 heal,
    int32 manaRegen,
    int32 lifeChancesRecovered,
    int32 damage,
    int32 manaConsumed
  ) internal pure returns (bytes32 data) {
    data = bytes32(uint(experience));
    data |= bytes32(uint(uint32(heal))) << 32;
    data |= bytes32(uint(uint32(manaRegen))) << (32 + 32);
    data |= bytes32(uint(uint32(lifeChancesRecovered))) << (32 + 32 + 32);
    data |= bytes32(uint(uint32(damage))) << (32 + 32 + 32 + 32);
    data |= bytes32(uint(uint32(manaConsumed))) << (32 + 32 + 32 + 32 + 32);
  }

  function unpackStatsChange(bytes32 data) internal pure returns (
    uint32 experience,
    int32 heal,
    int32 manaRegen,
    int32 lifeChancesRecovered,
    int32 damage,
    int32 manaConsumed
  ) {
    experience = uint32(uint(data));
    heal = int32(int(uint(data) >> 32));
    manaRegen = int32(int(uint(data) >> (32 + 32)));
    lifeChancesRecovered = int32(int(uint(data) >> (32 + 32 + 32)));
    damage = int32(int(uint(data) >> (32 + 32 + 32 + 32)));
    manaConsumed = int32(int(uint(data) >> (32 + 32 + 32 + 32 + 32)));
  }

  function packNftIdWithValue(address token, uint id, uint32 value) internal pure returns (bytes32 serialized) {
    if (id > uint(type(uint64).max)) revert IAppErrors.TooHighValue(id);
    serialized = bytes32(uint(uint160(token)));
    serialized |= bytes32(uint(uint64(id))) << 160;
    serialized |= bytes32(uint(value)) << 160 + 64;
  }

  function unpackNftIdWithValue(bytes32 data) internal pure returns (address token, uint id, uint32 value) {
    token = address(uint160(uint(data)));
    id = uint64(uint(data) >> 160);
    value = uint32(uint(data) >> 160 + 64);
  }
  //endregion ------------------------------------ COMMON

  //region ------------------------------------ WORLD/BATTLEFIELD MAP

  function packMapObject(address objectAddress, uint64 objectId, uint8 objectType) internal pure returns (bytes32 packedData) {
    packedData = bytes32(bytes20(objectAddress));
    packedData |= bytes32(uint(objectId) << 32);
    packedData |= bytes32(uint(objectType) << 24);
  }

  function unpackMapObject(bytes32 packedData) internal pure returns (address objectAddress, uint64 objectId, uint8 objectType) {
    objectAddress = address(bytes20(packedData));
    objectId = uint64(uint(packedData) >> 32);
    objectType = uint8(uint(packedData) >> 24);
  }

  function packCoordinate(uint128 x, uint128 y) internal pure returns (bytes32 packedData) {
    packedData = bytes32(uint(x));
    packedData |= bytes32(uint(y) << 128);
  }

  function unpackCoordinate(bytes32 packedData) internal pure returns (uint128 x, uint128 y) {
    x = uint128(uint(packedData));
    y = uint128(uint(packedData) >> 128);
  }

  /// @param x Assume x <= max uint64
  /// @param y Assume y <= max uint64
  function packBattlefieldId(uint8 biomeMapFieldId, uint8 territoryNumber, uint128 x, uint128 y) internal pure returns (bytes32 packedData) {
    // 256 => 128 + 128;
    // 1) 128 is used for biomeMapFieldId, territoryNumber and probably other fields in the future
    // 2) 128 is used to store x, y as uint64, uint64

    // we will use uint64 for coordinates assuming it is more than enough for biome map
    packedData = bytes32(uint(biomeMapFieldId));
    packedData |= bytes32(uint(territoryNumber) << (8));
    packedData |= bytes32(uint(uint64(x)) << 128);
    packedData |= bytes32(uint(uint64(y)) << (64 + 128));
  }

  function unpackBattlefieldId(bytes32 packedData) internal pure returns (uint8 biomeMapFieldId, uint8 territoryNumber, uint128 x, uint128 y) {
    biomeMapFieldId = uint8(uint(packedData));
    territoryNumber = uint8(uint(packedData) >> (8));
    x = uint128(uint64(uint(packedData) >> (128)));
    y = uint128(uint64(uint(packedData) >> (64 + 128)));
  }
  //endregion ------------------------------------ WORLD/BATTLEFIELD MAP

  //region ------------------------------------ REINFORCEMENT

  function packReinforcementHeroInfo(uint8 biome, uint128 score, uint8 fee, uint64 stakeTs) internal pure returns (bytes32 packedData) {
    packedData = bytes32(uint(biome));
    packedData |= bytes32(uint(score) << 8);
    packedData |= bytes32(uint(fee) << (8 + 128));
    packedData |= bytes32(uint(stakeTs) << (8 + 128 + 8));
  }

  function unpackReinforcementHeroInfo(bytes32 packedData) internal pure returns (uint8 biome, uint128 score, uint8 fee, uint64 stakeTs) {
    biome = uint8(uint(packedData));
    score = uint128(uint(packedData) >> 8);
    fee = uint8(uint(packedData) >> (8 + 128));
    stakeTs = uint64(uint(packedData) >> (8 + 128 + 8));
  }

  function packConfigReinforcementV2(uint32 min, uint32 max, uint32 lowDivider, uint32 highDivider, uint8 levelLimit) internal pure returns (bytes32 packedData) {
    packedData = bytes32(uint(min));
    packedData |= bytes32(uint(max) << 32);
    packedData |= bytes32(uint(lowDivider) << 64);
    packedData |= bytes32(uint(highDivider) << 96);
    packedData |= bytes32(uint(levelLimit) << 128);
  }

  function unpackConfigReinforcementV2(bytes32 packedData) internal pure returns (uint32 min, uint32 max, uint32 lowDivider, uint32 highDivider, uint8 levelLimit) {
    min = uint32(uint(packedData));
    max = uint32(uint(packedData) >> 32);
    lowDivider = uint32(uint(packedData) >> 64);
    highDivider = uint32(uint(packedData) >> 96);
    levelLimit = uint8(uint(packedData) >> 128);
  }
  //endregion ------------------------------------ REINFORCEMENT

  //region ------------------------------------ DUNGEON

  function packDungeonKey(address heroAdr, uint80 heroId, uint16 dungLogicNum) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(heroAdr)));
    data |= bytes32(uint(heroId)) << 160;
    data |= bytes32(uint(dungLogicNum)) << (160 + 80);
  }

  function unpackDungeonKey(bytes32 data) internal pure returns (address heroAdr, uint80 heroId, uint16 dungLogicNum) {
    heroAdr = address(uint160(uint(data)));
    heroId = uint80(uint(data) >> 160);
    dungLogicNum = uint16(uint(data) >> (160 + 80));
  }

  // --- GAME OBJECTS ---

  function packIterationKey(address heroAdr, uint64 heroId, uint32 objId) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(heroAdr)));
    data |= bytes32(uint(heroId)) << 160;
    data |= bytes32(uint(objId)) << (160 + 64);
  }

  function unpackIterationKey(bytes32 data) internal pure returns (address heroAdr, uint64 heroId, uint32 objId) {
    heroAdr = address(uint160(uint(data)));
    heroId = uint64(uint(data) >> 160);
    objId = uint32(uint(data) >> (160 + 64));
  }

  function packMonsterStats(
    uint8 level,
    uint8 race,
    uint32 experience,
    uint8 maxDropItems
  ) internal pure returns (bytes32 data) {
    data = bytes32(uint(level));
    data |= bytes32(uint(race)) << 8;
    data |= bytes32(uint(experience)) << (8 + 8);
    data |= bytes32(uint(maxDropItems)) << (8 + 8 + 32);
  }

  function unpackMonsterStats(bytes32 data) internal pure returns (
    uint8 level,
    uint8 race,
    uint32 experience,
    uint8 maxDropItems
  ) {
    level = uint8(uint(data));
    race = uint8(uint(data) >> 8);
    experience = uint32(uint(data) >> (8 + 8));
    maxDropItems = uint8(uint(data) >> (8 + 8 + 32));
  }

  function packAttackInfo(
    address attackToken,
    uint64 attackTokenId,
    uint8 attackType
  ) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(attackToken)));
    data |= bytes32(uint(attackTokenId)) << 160;
    data |= bytes32(uint(attackType)) << (160 + 64);
  }

  function unpackAttackInfo(bytes32 data) internal pure returns (
    address attackToken,
    uint64 attackTokenId,
    uint8 attackType
  ) {
    attackToken = address(uint160(uint(data)));
    attackTokenId = uint64(uint(data) >> 160);
    attackType = uint8(uint(data) >> (160 + 64));
  }

  function packPlayedObjKey(address heroAdr, uint64 heroId, uint8 oType, uint8 biome) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(heroAdr)));
    data |= bytes32(uint(heroId)) << 160;
    data |= bytes32(uint(oType)) << (160 + 64);
    data |= bytes32(uint(biome)) << (160 + 64 + 8);
  }

  function unpackPlayedObjKey(bytes32 data) internal pure returns (address heroAdr, uint64 heroId, uint8 oType, uint8 biome) {
    heroAdr = address(uint160(uint(data)));
    heroId = uint64(uint(data) >> 160);
    oType = uint8(uint(data) >> (160 + 64));
    biome = uint8(uint(data) >> (160 + 64 + 8));
  }

  function packGeneratedMonster(bool generated, uint32 amplifier, int32 hp, uint8 turnCounter) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint8(generated ? 1 : 0)));
    data |= bytes32(uint(amplifier)) << 8;
    data |= bytes32(uint(uint32(hp))) << (8 + 32);
    data |= bytes32(uint(turnCounter)) << (8 + 32 + 32);
  }

  function unpackGeneratedMonster(bytes32 data) internal pure returns (bool generated, uint32 amplifier, int32 hp, uint8 turnCounter) {
    generated = uint8(uint(data)) == uint8(1);
    amplifier = uint32(uint(data) >> 8);
    hp = int32(int(uint(data) >> (8 + 32)));
    turnCounter = uint8(uint(data) >> (8 + 32 + 32));
  }
  //endregion ------------------------------------ DUNGEON

  //region ------------------------------------ ITEMS

  /// @notice itemMetaType8 + itemLvl8 + itemType8 + baseDurability16 + defaultRarity8 + minAttr8 + maxAttr8 + manaCost32 + req(packed core 128)
  /// @param itemType This is ItemType enum
  function packItemMeta(
    uint8 itemMetaType,
    uint8 itemLvl,
    uint8 itemType,
    uint16 baseDurability,
    uint8 defaultRarity,
    uint8 minAttr,
    uint8 maxAttr,
    uint32 manaCost,
    IStatController.CoreAttributes memory req
  ) internal pure returns (bytes32 data) {
    data = bytes32(uint(itemMetaType));
    data |= bytes32(uint(itemLvl)) << 8;
    data |= bytes32(uint(itemType)) << (8 + 8);
    data |= bytes32(uint(baseDurability)) << (8 + 8 + 8);
    data |= bytes32(uint(defaultRarity)) << (8 + 8 + 8 + 16);
    data |= bytes32(uint(minAttr)) << (8 + 8 + 8 + 16 + 8);
    data |= bytes32(uint(maxAttr)) << (8 + 8 + 8 + 16 + 8 + 8);
    data |= bytes32(uint(manaCost)) << (8 + 8 + 8 + 16 + 8 + 8 + 8);
    data |= bytes32(uint(int(req.strength))) << (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32);
    data |= bytes32(uint(int(req.dexterity))) << (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32 + 32);
    data |= bytes32(uint(int(req.vitality))) << (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32 + 32 + 32);
    data |= bytes32(uint(int(req.energy))) << (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32 + 32 + 32 + 32);
  }

  function unpackItemMeta(bytes32 data) internal pure returns (IItemController.ItemMeta memory) {
    IItemController.ItemMeta memory result;

    result.itemMetaType = uint8(uint(data));
    result.itemLevel = uint8(uint(data) >> 8);
    result.itemType = IItemController.ItemType(uint8(uint(data) >> (8 + 8)));
    result.baseDurability = uint16(uint(data) >> (8 + 8 + 8));
    result.defaultRarity = uint8(uint(data) >> (8 + 8 + 8 + 16));
    result.minRandomAttributes = uint8(uint(data) >> (8 + 8 + 8 + 16 + 8));
    result.maxRandomAttributes = uint8(uint(data) >> (8 + 8 + 8 + 16 + 8 + 8));
    result.manaCost = uint32(uint(data) >> (8 + 8 + 8 + 16 + 8 + 8 + 8));
    result.requirements.strength = int32(int(uint(data) >> (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32)));
    result.requirements.dexterity = int32(int(uint(data) >> (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32 + 32)));
    result.requirements.vitality = int32(int(uint(data) >> (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32 + 32 + 32)));
    result.requirements.energy = int32(int(uint(data) >> (8 + 8 + 8 + 16 + 8 + 8 + 8 + 32 + 32 + 32 + 32)));

    return result;
  }

  function packItemGenerateInfo(uint8 id, int32 min, int32 max, uint32 chance) internal pure returns (bytes32 data) {
    data = bytes32(uint(id));
    data |= bytes32(uint(uint32(min))) << 8;
    data |= bytes32(uint(uint32(max))) << (8 + 32);
    data |= bytes32(uint(chance)) << (8 + 32 + 32);
  }

  function unpackItemGenerateInfo(bytes32 data) internal pure returns (uint8 id, int32 min, int32 max, uint32 chance) {
    id = uint8(uint(data));
    min = int32(int(uint(data) >> 8));
    max = int32(int(uint(data) >> (8 + 32)));
    chance = uint32(uint(data) >> (8 + 32 + 32));
  }

  function packItemAttackInfo(
    uint8 attackType,
    int32 min,
    int32 max,
    int32 factorStr,
    int32 factorDex,
    int32 factorVit,
    int32 factorEng
  ) internal pure returns (bytes32 data) {
    data = bytes32(uint(attackType));
    data |= bytes32(uint(uint32(min))) << 8;
    data |= bytes32(uint(uint32(max))) << (8 + 32);
    data |= bytes32(uint(int(factorStr))) << (8 + 32 + 32);
    data |= bytes32(uint(int(factorDex))) << (8 + 32 + 32 + 32);
    data |= bytes32(uint(int(factorVit))) << (8 + 32 + 32 + 32 + 32);
    data |= bytes32(uint(int(factorEng))) << (8 + 32 + 32 + 32 + 32 + 32);
  }

  function unpackItemAttackInfo(bytes32 data) internal pure returns (
    uint8 attackType,
    int32 min,
    int32 max,
    int32 factorStr,
    int32 factorDex,
    int32 factorVit,
    int32 factorEng
  ) {
    attackType = uint8(uint(data));
    min = int32(int(uint(data) >> 8));
    max = int32(int(uint(data) >> (8 + 32)));
    factorStr = int32(int(uint(data) >> (8 + 32 + 32)));
    factorDex = int32(int(uint(data) >> (8 + 32 + 32 + 32)));
    factorVit = int32(int(uint(data) >> (8 + 32 + 32 + 32 + 32)));
    factorEng = int32(int(uint(data) >> (8 + 32 + 32 + 32 + 32 + 32)));
  }

  function packItemInfo(uint8 rarity, uint8 augmentationLevel, uint16 durability) internal pure returns (bytes32 data) {
    data = bytes32(uint(rarity));
    data |= bytes32(uint(augmentationLevel)) << 8;
    data |= bytes32(uint(durability)) << (8 + 8);
  }

  function unpackItemInfo(bytes32 data) internal pure returns (uint8 rarity, uint8 augmentationLevel, uint16 durability) {
    rarity = uint8(uint(data));
    augmentationLevel = uint8(uint(data) >> 8);
    durability = uint16(uint(data) >> (8 + 8));
  }
  //endregion ------------------------------------ ITEMS

  //region ------------------------------------ STORIES

  function packStoryPageId(uint16 storyId, uint16 pageId, uint8 heroClass) internal pure returns (bytes32 data) {
    data = bytes32(uint(storyId));
    data |= bytes32(uint(pageId)) << 16;
    data |= bytes32(uint(heroClass)) << (16 + 16);
  }

  function unpackStoryPageId(bytes32 data) internal pure returns (uint16 storyId, uint16 pageId, uint8 heroClass) {
    storyId = uint16(uint(data));
    pageId = uint16(uint(data) >> 16);
    heroClass = uint8(uint(data) >> (16 + 16));
  }

  function packStoryAnswerId(uint16 storyId, uint16 pageId, uint8 heroClass, uint16 answerId) internal pure returns (bytes32 data) {
    data = bytes32(uint(storyId));
    data |= bytes32(uint(pageId)) << 16;
    data |= bytes32(uint(heroClass)) << (16 + 16);
    data |= bytes32(uint(answerId)) << (16 + 16 + 8);
  }

  function unpackStoryAnswerId(bytes32 data) internal pure returns (uint16 storyId, uint16 pageId, uint8 heroClass, uint16 answerId) {
    storyId = uint16(uint(data));
    pageId = uint16(uint(data) >> 16);
    heroClass = uint8(uint(data) >> (16 + 16));
    answerId = uint16(uint(data) >> (16 + 16 + 8));
  }

  function packStoryNextPagesId(uint16 storyId, uint16 pageId, uint8 heroClass, uint16 answerId, uint8 resultId) internal pure returns (bytes32 data) {
    data = bytes32(uint(storyId));
    data |= bytes32(uint(pageId)) << 16;
    data |= bytes32(uint(heroClass)) << (16 + 16);
    data |= bytes32(uint(answerId)) << (16 + 16 + 8);
    data |= bytes32(uint(resultId)) << (16 + 16 + 8 + 16);
  }

  function unpackStoryNextPagesId(bytes32 data) internal pure returns (uint16 storyId, uint16 pageId, uint8 heroClass, uint16 answerId, uint8 resultId) {
    storyId = uint16(uint(data));
    pageId = uint16(uint(data) >> 16);
    heroClass = uint8(uint(data) >> (16 + 16));
    answerId = uint16(uint(data) >> (16 + 16 + 8));
    resultId = uint8(uint(data) >> (16 + 16 + 8 + 16));
  }

  function packStoryAttributeRequirement(uint8 attributeIndex, int32 value, bool isCore) internal pure returns (bytes32 data) {
    data = bytes32(uint(attributeIndex));
    data |= bytes32(uint(uint32(value))) << 8;
    data |= bytes32(uint(isCore ? uint8(1) : uint8(0))) << (8 + 32);
  }

  function unpackStoryAttributeRequirement(bytes32 data) internal pure returns (uint8 attributeIndex, int32 value, bool isCore) {
    attributeIndex = uint8(uint(data));
    value = int32(int(uint(data) >> 8));
    isCore = uint8(uint(data) >> (8 + 32)) == uint8(1);
  }

  function packStoryItemRequirement(address item, bool requireItemBurn, bool requireItemEquipped) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(item)));
    data |= bytes32(uint(requireItemBurn ? uint8(1) : uint8(0))) << 160;
    data |= bytes32(uint(requireItemEquipped ? uint8(1) : uint8(0))) << (160 + 8);
  }

  function unpackStoryItemRequirement(bytes32 data) internal pure returns (address item, bool requireItemBurn, bool requireItemEquipped) {
    item = address(uint160(uint(data)));
    requireItemBurn = uint8(uint(data) >> 160) == uint8(1);
    requireItemEquipped = uint8(uint(data) >> (160 + 8)) == uint8(1);
  }

  /// @dev max amount is 309,485,009 for token with 18 decimals
  function packStoryTokenRequirement(address token, uint88 amount, bool requireTransfer) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(token)));
    data |= bytes32(uint(amount)) << 160;
    data |= bytes32(uint(requireTransfer ? uint8(1) : uint8(0))) << (160 + 88);
  }

  function unpackStoryTokenRequirement(bytes32 data) internal pure returns (address token, uint88 amount, bool requireTransfer) {
    token = address(uint160(uint(data)));
    amount = uint88(uint(data) >> 160);
    requireTransfer = uint8(uint(data) >> (160 + 88)) == uint8(1);
  }

  function packStoryCustomDataResult(uint16 storyId, uint16 pageId, uint8 heroClass, uint16 answerId, uint8 customDataResultId) internal pure returns (bytes32 data) {
    data = bytes32(uint(storyId));
    data |= bytes32(uint(pageId)) << 16;
    data |= bytes32(uint(heroClass)) << (16 + 16);
    data |= bytes32(uint(answerId)) << (16 + 16 + 8);
    data |= bytes32(uint(customDataResultId)) << (16 + 16 + 8 + 16);
  }

  function unpackStoryCustomDataResult(bytes32 data) internal pure returns (uint16 storyId, uint16 pageId, uint8 heroClass, uint16 answerId, uint8 customDataResultId) {
    storyId = uint16(uint(data));
    pageId = uint16(uint(data) >> 16);
    heroClass = uint8(uint(data) >> (16 + 16));
    answerId = uint16(uint(data) >> (16 + 16 + 8));
    customDataResultId = uint8(uint(data) >> (16 + 16 + 8 + 16));
  }

  function packStoryHeroState(uint16 pageId, uint40 heroLastActionTS) internal pure returns (bytes32 data) {
    data = bytes32(uint(pageId));
    data |= bytes32(uint(heroLastActionTS)) << 16;
  }

  function unpackStoryHeroState(bytes32 data) internal pure returns (uint16 pageId, uint40 heroLastActionTS) {
    pageId = uint16(uint(data));
    heroLastActionTS = uint40(uint(data) >> 16);
  }

  function packStoryHeroStateId(address heroAdr, uint80 heroId, uint16 storyId) internal pure returns (bytes32 data) {
    data = bytes32(uint(uint160(heroAdr)));
    data |= bytes32(uint(heroId)) << 160;
    data |= bytes32(uint(storyId)) << (160 + 80);
  }

  function unpackStoryHeroStateId(bytes32 data) internal pure returns (address heroAdr, uint80 heroId, uint16 storyId) {
    heroAdr = address(uint160(uint(data)));
    heroId = uint80(uint(data) >> 160);
    storyId = uint16(uint(data) >> (160 + 80));
  }

  function packStorySimpleRequirement(uint32 randomRequirement, uint32 delayRequirement, bool isFinalAnswer) internal pure returns (bytes32 data) {
    data = bytes32(uint(randomRequirement));
    data |= bytes32(uint(delayRequirement)) << 32;
    data |= bytes32(uint(isFinalAnswer ? uint8(1) : uint8(0))) << (32 + 32);
  }

  function unpackStorySimpleRequirement(bytes32 data) internal pure returns (uint32 randomRequirement, uint32 delayRequirement, bool isFinalAnswer) {
    randomRequirement = uint32(uint(data));
    delayRequirement = uint32(uint(data) >> 32);
    isFinalAnswer = uint8(uint(data) >> (32 + 32)) == uint8(1);
  }

  function packBreakInfo(uint8 slot, uint64 chance, bool stopIfBroken) internal pure returns (bytes32 data) {
    data = bytes32(uint(slot));
    data |= bytes32(uint(chance)) << 8;
    data |= bytes32(uint(stopIfBroken ? uint8(1) : uint8(0))) << (8 + 64);
  }

  function unpackBreakInfo(bytes32 data) internal pure returns (uint8 slot, uint64 chance, bool stopIfBurned) {
    slot = uint8(uint(data));
    chance = uint64(uint(data) >> 8);
    stopIfBurned = uint8(uint(data) >> (8 + 64)) == uint8(1);
  }
  //endregion ------------------------------------ STORIES

  //region ------------------------------------ Hero controller
  function packTierHero(uint8 tier, address hero) internal pure returns (bytes32 packedTierHero) {
    packedTierHero = bytes32(uint(tier));
    packedTierHero |= bytes32(uint(uint160(hero)) << 8);
  }

  function unpackTierHero(bytes32 packedTierHero) internal pure returns (uint8 tier, address hero) {
    tier = uint8(uint(packedTierHero));
    hero = address(uint160(uint(packedTierHero) >> 8));
  }

  //endregion ------------------------------------ Hero controller

  ////////////////////////////////////////////////////////////////////////////////////
  // ---- ARRAYS LOGIC ----
  ////////////////////////////////////////////////////////////////////////////////////

  //region ------------------------------------ SIMPLE ARRAYS


  function packUint8Array(uint8[] memory data) internal pure returns (bytes32) {
    uint len = data.length;
    if (len > 32) revert IAppErrors.OutOfBounds(len, 32);
    bytes32 result;
    for (uint i = 0; i < len; i++) {
      result |= bytes32(uint(data[i])) << (i * 8);
    }
    return result;
  }

  /// @notice Simple faster version of {packUint8Array} for small number of items
  ///         It allows to exclude dynamic array creation.
  function packUint8Array3(uint8 a, uint8 b, uint8 c) internal pure returns (bytes32) {
    bytes32 result = bytes32(uint(a));
    result |= bytes32(uint(b)) << (1 * 8);
    result |= bytes32(uint(c)) << (2 * 8);
    return result;
  }


  function unpackUint8Array(bytes32 data) internal pure returns (uint8[] memory) {
    uint8[] memory result = new uint8[](32);
    for (uint i = 0; i < 32; i++) {
      result[i] = uint8(uint(data) >> (i * 8));
    }
    return result;
  }

  /// @notice Simple faster version of {unpackUint8Array} for small number of items
  ///         It allows to exclude only first 3 values
  function unpackUint8Array3(bytes32 data) internal pure returns (uint8 a, uint8 b, uint8 c) {
    a = uint8(uint(data));
    b = uint8(uint(data) >> (1 * 8));
    c = uint8(uint(data) >> (2 * 8));
  }

  function changeUnit8ArrayWithCheck(bytes32 data, uint index, uint8 value, uint8 expectedPrevValue) internal pure returns (bytes32 newData) {
    uint8[] memory arr = unpackUint8Array(data);
    if (arr[index] != expectedPrevValue) revert IAppErrors.UnexpectedValue(uint(expectedPrevValue), uint(arr[index]));
    arr[index] = value;
    return packUint8Array(arr);
  }

  function packInt32Array(int32[] memory data) internal pure returns (bytes32) {
    uint len = data.length;
    if (len > 8) revert IAppErrors.OutOfBounds(len, 8);
    bytes32 result;
    for (uint i; i < len; i++) {
      result |= bytes32(uint(uint32(data[i]))) << (i * 32);
    }
    return result;
  }

  function unpackInt32Array(bytes32 data) internal pure returns (int32[] memory) {
    int32[] memory result = new int32[](8);
    for (uint i = 0; i < 8; i++) {
      result[i] = int32(int(uint(data) >> (i * 32)));
    }
    return result;
  }

  function packUint32Array(uint32[] memory data) internal pure returns (bytes32) {
    uint len = data.length;
    if (len > 8) revert IAppErrors.OutOfBounds(len, 8);
    bytes32 result;
    for (uint i = 0; i < len; i++) {
      result |= bytes32(uint(data[i])) << (i * 32);
    }
    return result;
  }

  function unpackUint32Array(bytes32 data) internal pure returns (uint32[] memory) {
    uint32[] memory result = new uint32[](8);
    for (uint i = 0; i < 8; i++) {
      result[i] = uint32(uint(data) >> (i * 32));
    }
    return result;
  }
  //endregion ------------------------------------ SIMPLE ARRAYS

  //region ------------------------------------ COMPLEX ARRAYS

  // We should represent arrays without concrete size.
  // For this reason we must not revert IAppErrors.on out of bounds but return zero value instead.

  // we need it for properly unpack packed arrays with ids
//  function getInt32AsInt24(bytes32[] memory arr, uint idx) internal pure returns (int32) {
//    if (idx / 8 >= arr.length) {
//      return int32(0);
//    }
//    return int32(int24(int(uint(arr[idx / 8]) >> ((idx % 8) * 32))));
//  }

  // we need it for properly unpack packed arrays with ids
//  function getUnit8From32Step(bytes32[] memory arr, uint idx) internal pure returns (uint8) {
//    if (idx / 8 >= arr.length) {
//      return uint8(0);
//    }
//    return uint8(uint(arr[idx / 8]) >> ((idx % 8) * 32 + 24));
//  }

  function getInt32Memory(bytes32[] memory arr, uint idx) internal pure returns (int32) {
    if (idx / 8 >= arr.length) {
      return int32(0);
    }
    return int32(int(uint(arr[idx / 8]) >> ((idx % 8) * 32)));
  }

  function getInt32(bytes32[] storage arr, uint idx) internal view returns (int32) {
    // additional gas usage, but we should not revert IAppErrors.on out of bounds
    if (idx / 8 >= arr.length) {
      return int32(0);
    }
    return int32(int(uint(arr[idx / 8]) >> ((idx % 8) * 32)));
  }

  function setInt32(bytes32[] storage arr, uint idx, int32 value) internal {
    uint pos = idx / 8;
    uint shift = (idx % 8) * 32;

    uint curLength = arr.length;
    if (pos >= curLength) {
      arr.push(0);
      for (uint i = curLength; i < pos; ++i) {
        arr.push(0);
      }
    }

    arr[pos] = bytes32(uint(arr[pos]) & ~(uint(0xffffffff) << shift) | (uint(uint32(value)) & 0xffffffff) << shift);
  }

  /// @notice Increment {idx}-th item on {value}
  function changeInt32(bytes32[] storage arr, uint idx, int32 value) internal returns (int32 newValue, int32 change) {
    int32 cur = int32(int(getInt32(arr, idx)));
    int newValueI = int(cur) + int(value);
    newValue = int32(newValueI);
    change = int32(newValueI - int(cur));

    setInt32(arr, idx, newValue);
  }

  function toInt32Array(bytes32[] memory arr, uint size) internal pure returns (int32[] memory) {
    int32[] memory result = new int32[](size);
    for (uint i = 0; i < arr.length; i++) {
      for (uint j; j < 8; ++j) {
        uint idx = i * 8 + j;
        if (idx >= size) break;
        result[idx] = getInt32Memory(arr, idx);
      }
    }
    return result;
  }

  /// @dev pack int32 array into bytes32 array
  function toBytes32Array(int32[] memory arr) internal pure returns (bytes32[] memory) {
    uint size = arr.length / 8 + 1;
    bytes32[] memory result = new bytes32[](size);
    for (uint i; i < size; ++i) {
      for (uint j; j < 8; ++j) {
        uint idx = i * 8 + j;
        if (idx >= arr.length) break;
        result[i] |= bytes32(uint(uint32(arr[idx]))) << (j * 32);
      }
    }
    return result;
  }

  /// @dev pack int32 array into bytes32 array using last 8bytes for ids
  ///      we can not use zero values coz will not able to properly unpack it later
  function toBytes32ArrayWithIds(int32[] memory arr, uint8[] memory ids) internal pure returns (bytes32[] memory) {
    if (arr.length != ids.length) revert IAppErrors.LengthsMismatch();

    uint size = arr.length / 8 + 1;
    bytes32[] memory result = new bytes32[](size);
    for (uint i; i < size; ++i) {
      for (uint j; j < 8; ++j) {
        uint idx = i * 8 + j;
        if (idx >= arr.length) break;

        if (arr[idx] > type(int24).max || arr[idx] < type(int24).min) revert IAppErrors.IntOutOfRange(int(arr[idx]));
        if (arr[idx] == 0) revert IAppErrors.ZeroValue();
        result[i] |= bytes32(uint(uint24(int24(arr[idx])))) << (j * 32);
        result[i] |= bytes32(uint(ids[idx])) << (j * 32 + 24);
      }
    }
    return result;
  }

  /// @dev we do not know exact size of array, assume zero values is not acceptable for this array
  function toInt32ArrayWithIds(bytes32[] memory arr) internal pure returns (int32[] memory values, uint8[] memory ids) {
    uint len = arr.length;
    uint size = len * 8;
    int32[] memory valuesTmp = new int32[](size);
    uint8[] memory idsTmp = new uint8[](size);
    uint counter;
    for (uint i = 0; i < len; i++) {
      for (uint j; j < 8; ++j) {
        uint idx = i * 8 + j;
        // if (idx >= size) break;  // it looks like a useless check
        valuesTmp[idx] = int32(int24(int(uint(arr[i]) >> (j * 32)))); // getInt32AsInt24(arr, idx);
        idsTmp[idx] = uint8(uint(arr[i]) >> (j * 32 + 24)); // getUnit8From32Step(arr, idx);
        if (valuesTmp[idx] == 0) {
          break;
        }
        counter++;
      }
    }

    values = new int32[](counter);
    ids = new uint8[](counter);
    for (uint i; i < counter; ++i) {
      values[i] = valuesTmp[i];
      ids[i] = idsTmp[i];
    }
  }
  //endregion ------------------------------------ COMPLEX ARRAYS

  //region ------------------------------------ Guilds
  /// @dev ShelterID is uint. But in the code we assume that this ID can be stored as uint64 (see auctions)
  /// @param biome 1, 2, 3...
  /// @param shelterLevel 1, 2 or 3.
  /// @param shelterIndex 0, 1, 2 ...
  function packShelterId(uint8 biome, uint8 shelterLevel, uint8 shelterIndex) internal pure returns (uint) {
    return uint(biome) | (uint(shelterLevel) << 8) | (uint(shelterIndex) << 16);
  }

  function unpackShelterId(uint shelterId) internal pure returns (uint8 biome, uint8 shelterLevel, uint8 shelterIndex) {
    return (uint8(shelterId), uint8(shelterId >> 8), uint8(shelterId >> 16));
  }
  //endregion ------------------------------------ Guilds

  //region ------------------------------------ Metadata of IItemController.OtherSubtypeKind

  function getOtherItemTypeKind(bytes memory packedData) internal pure returns (IItemController.OtherSubtypeKind) {
    bytes32 serialized;
    assembly {
      serialized := mload(add(packedData, 32))
    }
    uint8 kind = uint8(uint(serialized));
    if (kind == 0 || kind >= uint8(IItemController.OtherSubtypeKind.END_SLOT)) revert IAppErrors.IncorrectOtherItemTypeKind(kind);
    return IItemController.OtherSubtypeKind(kind);
  }

  function packOtherItemReduceFragility(uint value) internal pure returns (bytes memory packedData) {
    bytes32 serialized = bytes32(uint(uint8(IItemController.OtherSubtypeKind.REDUCE_FRAGILITY_1)));
    serialized |= bytes32(uint(uint248(value))) << 8;
    return bytes.concat(serialized);
  }

  function unpackOtherItemReduceFragility(bytes memory packedData) internal pure returns (uint) {
    bytes32 serialized;
    assembly {
      serialized := mload(add(packedData, 32))
    }
    uint8 kind = uint8(uint(serialized));
    if (kind != uint8(IItemController.OtherSubtypeKind.REDUCE_FRAGILITY_1)) revert IAppErrors.IncorrectOtherItemTypeKind(kind);
    uint value = uint248(uint(serialized) >> 8);
    return value;
  }
  //endregion ------------------------------------ Metadata of IItemController.OtherSubtypeKind
}

File 16 of 24 : SlotsLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

/// @title Library for setting / getting slot variables (used in upgradable proxy contracts)
/// @author bogdoslav
library SlotsLib {

  /// @notice Version of the contract
  /// @dev Should be incremented when contract changed
  string public constant SLOT_LIB_VERSION = "1.0.0";

  // ************* GETTERS *******************

  /// @dev Gets a slot as bytes32
  function getBytes32(bytes32 slot) internal view returns (bytes32 result) {
    assembly {
      result := sload(slot)
    }
  }

  /// @dev Gets a slot as an address
  function getAddress(bytes32 slot) internal view returns (address result) {
    assembly {
      result := sload(slot)
    }
  }

  /// @dev Gets a slot as uint256
  function getUint(bytes32 slot) internal view returns (uint result) {
    assembly {
      result := sload(slot)
    }
  }

  // ************* ARRAY GETTERS *******************

  /// @dev Gets an array length
  function arrayLength(bytes32 slot) internal view returns (uint result) {
    assembly {
      result := sload(slot)
    }
  }

  /// @dev Gets a slot array by index as address
  /// @notice First slot is array length, elements ordered backward in memory
  /// @notice This is unsafe, without checking array length.
  function addressAt(bytes32 slot, uint index) internal view returns (address result) {
    bytes32 pointer = bytes32(uint(slot) - 1 - index);
    assembly {
      result := sload(pointer)
    }
  }

  // ************* SETTERS *******************

  /// @dev Sets a slot with bytes32
  /// @notice Check address for 0 at the setter
  function set(bytes32 slot, bytes32 value) internal {
    assembly {
      sstore(slot, value)
    }
  }

  /// @dev Sets a slot with address
  /// @notice Check address for 0 at the setter
  function set(bytes32 slot, address value) internal {
    assembly {
      sstore(slot, value)
    }
  }

  /// @dev Sets a slot with uint
  function set(bytes32 slot, uint value) internal {
    assembly {
      sstore(slot, value)
    }
  }

}

File 17 of 24 : StatControllerLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../openzeppelin/Math.sol";
import "../openzeppelin/EnumerableSet.sol";
import "../interfaces/IItemController.sol";
import "../interfaces/IStatController.sol";
import "../interfaces/IAppErrors.sol";
import "../interfaces/IApplicationEvents.sol";
import "../lib/StatLib.sol";

/// @notice Implementation of StatController
library StatControllerLib {
  using StatLib for uint;
  using StatLib for uint[];
  using StatLib for uint32;
  using StatLib for int32;
  using StatLib for int32;
  using CalcLib for uint;
  using CalcLib for int;
  using CalcLib for int32;
  using EnumerableSet for EnumerableSet.AddressSet;
  using EnumerableMap for EnumerableMap.Bytes32ToUintMap;
  using PackingLib for bytes32[];
  using PackingLib for bytes32;
  using PackingLib for int32;
  using PackingLib for uint32;

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

  int32 public constant LEVEL_UP_SUM = 5;
  bytes32 public constant KARMA_HASH = bytes32("KARMA");
  uint internal constant DEFAULT_KARMA_VALUE = 1000;

  /// @notice Virtual data, value is not stored to hero custom data, heroClass is taken from heroController by the index
  bytes32 public constant HERO_CLASS_HASH = bytes32("HERO_CLASS");

  /// @notice Custom data of the hero. Value is incremented on every life-chance lost
  bytes32 public constant DEATH_COUNT_HASH = bytes32("DEATH_COUNT");
  //endregion ------------------------ Constants

  //region ------------------------ RESTRICTIONS

  function onlyRegisteredContract(IController controller_) internal view returns (IHeroController) {
    // using of ControllerContextLib.ControllerContext increases size of the contract on 0.5 kb
    address sender = msg.sender;
    address heroController = controller_.heroController();
    if (
      heroController != sender
      && controller_.itemController() != sender
      && controller_.dungeonFactory() != sender
      && controller_.storyController() != sender
      && controller_.gameObjectController() != sender
      // todo after pvp release: && controller_.pvpController() != sender
    ) revert IAppErrors.ErrorForbidden(sender);

    return IHeroController(heroController);
  }

  function onlyItemController(IController controller_) internal view {
    if (controller_.itemController() != msg.sender) revert IAppErrors.ErrorNotItemController(msg.sender);
  }

  function onlyHeroController(IController controller_) internal view returns (IHeroController) {
    address heroController = controller_.heroController();
    if (heroController != msg.sender) revert IAppErrors.ErrorNotHeroController(msg.sender);
    return IHeroController(heroController);
  }

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

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

  function heroAttributes(IStatController.MainState storage s, address token, uint tokenId) internal view returns (int32[] memory) {
    return PackingLib.toInt32Array(s.heroTotalAttributes[PackingLib.packNftId(token, tokenId)], uint(IStatController.ATTRIBUTES.END_SLOT));
  }

  function heroBonusAttributes(IStatController.MainState storage s, address token, uint tokenId) internal view returns (int32[] memory) {
    return PackingLib.toInt32Array(s.heroBonusAttributes[PackingLib.packNftId(token, tokenId)], uint(IStatController.ATTRIBUTES.END_SLOT));
  }

  function heroTemporallyAttributes(IStatController.MainState storage s, address token, uint tokenId) internal view returns (int32[] memory) {
    return PackingLib.toInt32Array(s.heroTemporallyAttributes[PackingLib.packNftId(token, tokenId)], uint(IStatController.ATTRIBUTES.END_SLOT));
  }


  function heroAttributesLength(address /*token*/, uint /*tokenId*/) internal pure returns (uint) {
    return uint(IStatController.ATTRIBUTES.END_SLOT);
  }

  function heroAttribute(IStatController.MainState storage s, address token, uint tokenId, uint index) internal view returns (int32) {
    return PackingLib.getInt32(s.heroTotalAttributes[PackingLib.packNftId(token, tokenId)], index);
  }

  function heroBaseAttributes(IStatController.MainState storage s, address token, uint tokenId) internal view returns (
    IStatController.CoreAttributes memory core
  ) {
    int32[] memory data = PackingLib.unpackInt32Array(s._heroCore[PackingLib.packNftId(token, tokenId)]);
    core = IStatController.CoreAttributes({
      strength: int32(data[0]),
      dexterity: int32(data[1]),
      vitality: int32(data[2]),
      energy: int32(data[3])
    });
  }

  function heroCustomData(IHeroController hc, address hero, uint heroId, bytes32 index) internal view returns (uint) {
    return heroCustomDataOnNgLevel(hc, hero, heroId, index, hc.getHeroInfo(hero, heroId).ngLevel);
  }

  function heroCustomDataOnNgLevel(IHeroController hc, address hero, uint heroId, bytes32 index, uint8 ngLevel) internal view returns (uint) {
    if (index == HERO_CLASS_HASH) {
      return hc.heroClass(hero);
    } else {
      (, uint value) = _S().heroCustomDataV2[PackingLib.packNftIdWithValue(hero, heroId, ngLevel)].tryGet(index);

      if (index == KARMA_HASH && value == 0) {
        return DEFAULT_KARMA_VALUE;
      }

      return value;
    }
  }


  function getAllHeroCustomData(IHeroController hc, address hero, uint heroId) internal view returns (bytes32[] memory keys, uint[] memory values) {
    // Result doesn't include HERO_CLASS_HASH
    EnumerableMap.Bytes32ToUintMap storage map = _S().heroCustomDataV2[PackingLib.packNftIdWithValue(hero, heroId, hc.getHeroInfo(hero, heroId).ngLevel)];
    uint length = map.length();
    keys = new bytes32[](length);
    values = new uint[](length);
    for (uint i; i < length; ++i) {
      (keys[i], values[i]) = map.at(i);
    }
  }

  function globalCustomData(IStatController.MainState storage s, bytes32 index) internal view returns (uint) {
    return s.globalCustomData[index];
  }

  function heroStats(IStatController.MainState storage s, address token, uint tokenId) internal view returns (
    IStatController.ChangeableStats memory result
  ) {
    uint32[] memory data = PackingLib.unpackUint32Array(s.heroStats[PackingLib.packNftId(token, tokenId)]);
    result = IStatController.ChangeableStats({
      level: uint32(data[0]),
      experience: uint32(data[1]),
      life: uint32(data[2]),
      mana: uint32(data[3]),
      lifeChances: uint32(data[4])
    });
  }

  function heroItemSlot(IStatController.MainState storage s, address heroToken, uint64 heroTokenId, uint8 itemSlot) internal view returns (
    bytes32 nftPacked
  ) {
    return s.heroSlots[PackingLib.packMapObject(heroToken, heroTokenId, itemSlot)];
  }

  /// @return Return list of indices of the busy item slots for the given hero
  function heroItemSlots(IStatController.MainState storage s, address heroToken, uint heroTokenId) internal view returns (
    uint8[] memory
  ) {
    uint8[] memory slots = PackingLib.unpackUint8Array(s.heroBusySlots[PackingLib.packNftId(heroToken, heroTokenId)]);

    uint8[] memory busySlotsNumbers = new uint8[](slots.length);
    uint counter;

    for (uint8 i; i < uint8(slots.length); ++i) {
      if (slots[i] != 0) {
        busySlotsNumbers[counter] = i;
        counter++;
      }
    }

    uint8[] memory result = new uint8[](counter);

    for (uint i; i < counter; ++i) {
      result[i] = busySlotsNumbers[i];
    }

    return result;
  }

  function isHeroAlive(IStatController.MainState storage s, address heroToken, uint heroTokenId) internal view returns (bool) {
    return heroStats(s, heroToken, heroTokenId).lifeChances != 0;
  }

  function isConsumableUsed(IStatController.MainState storage s, address heroToken, uint heroTokenId, address item) internal view returns (bool) {
    return s.usedConsumables[PackingLib.packNftId(heroToken, heroTokenId)].contains(item);
  }

  /// @notice Calculate totalAttributes + all attributes of the items specified in {info}
  function buffHero(
    IStatController.MainState storage s,
    IController c,
    IStatController.BuffInfo calldata info
  ) external view returns (
    int32[] memory dest,
    int32 manaSum
  ) {
    uint length = info.buffTokens.length;
    if (length == 0) {
      return (heroAttributes(s, info.heroToken, info.heroTokenId), 0);
    }

    IItemController ic = IItemController(c.itemController());

    int32[] memory buffAttributes = new int32[](uint(IStatController.ATTRIBUTES.END_SLOT));
    address[] memory usedTokens = new address[](length);

    for (uint i; i < length; ++i) {

      // we should ignore the same skills
      bool used;
      for (uint j; j < i; ++j) {
        if (usedTokens[j] == info.buffTokens[i]) {
          used = true;
          break;
        }
      }
      if (used) {
        continue;
      }


      manaSum += int32(ic.itemMeta(info.buffTokens[i]).manaCost);
      (int32[] memory values, uint8[] memory ids) = ic.casterAttributes(info.buffTokens[i], info.buffTokenIds[i]);
      StatLib.attributesAdd(buffAttributes, StatLib.valuesToFullAttributesArray(values, ids));
      usedTokens[i] = info.buffTokens[i];
    }

    int32[] memory totalAttributes = StatLib.attributesAdd(heroAttributes(s, info.heroToken, info.heroTokenId), buffAttributes);

    StatLib.attributesAdd(buffAttributes, heroBonusAttributes(s, info.heroToken, info.heroTokenId));
    StatLib.attributesAdd(buffAttributes, heroTemporallyAttributes(s, info.heroToken, info.heroTokenId));

    return (
      StatLib.updateCoreDependAttributesInMemory(
      totalAttributes,
      buffAttributes,
      IHeroController(c.heroController()).heroClass(info.heroToken),
      info.heroLevel
    ),
      manaSum
    );
  }

  //endregion ------------------------ VIEWS

  //region ------------------------ PURE

  function isItemTypeEligibleToItemSlot(uint itemType, uint itemSlot) internal pure returns (bool) {
    // Consumable items not eligible
    if (itemType == 0 || itemSlot == 0) {
      return false;
    }
    // items with type before 5 mapped 1 to 1
    if (itemType <= uint(IItemController.ItemType.AMULET)) {
      return itemSlot == itemType;
    }
    if (itemType == uint(IItemController.ItemType.RING)) {
      return itemSlot == uint(IStatController.ItemSlots.RIGHT_RING)
        || itemSlot == uint(IStatController.ItemSlots.LEFT_RING);
    }
    if (itemType == uint(IItemController.ItemType.BOOTS)) {
      return itemSlot == uint(IStatController.ItemSlots.BOOTS);
    }
    if (itemType == uint(IItemController.ItemType.ONE_HAND)) {
      return itemSlot == uint(IStatController.ItemSlots.RIGHT_HAND);
    }
    if (itemType == uint(IItemController.ItemType.OFF_HAND)) {
      return itemSlot == uint(IStatController.ItemSlots.LEFT_HAND);
    }
    if (itemType == uint(IItemController.ItemType.TWO_HAND)) {
      return itemSlot == uint(IStatController.ItemSlots.TWO_HAND);
    }
    if (itemType == uint(IItemController.ItemType.SKILL)) {
      return itemSlot == uint(IStatController.ItemSlots.SKILL_1)
      || itemSlot == uint(IStatController.ItemSlots.SKILL_2)
        || itemSlot == uint(IStatController.ItemSlots.SKILL_3);
    }
    // unknown types
    return false;
  }

  /// @notice How much experience is required to go from the {level} to the next level
  function levelUpExperienceRequired(uint32 level) internal pure returns (uint) {
    if (level == 0 || level >= StatLib.MAX_LEVEL) return 0;
    return level == uint32(1)
      ? StatLib.levelExperience(level)
      : StatLib.levelExperience(level) - StatLib.levelExperience(level - uint32(1));
  }

  //endregion ------------------------ PURE

  //region ------------------------ ACTIONS

  /// @param heroClass Assume that heroController passes correct value of the heroClass for the given hero
  /// Also assume that the hero exists and alive
  function reborn(IController controller, address hero, uint heroId, uint heroClass) external {
    IStatController.MainState storage s = _S();
    bytes32 heroPackedId = PackingLib.packNftId(hero, heroId);

    IHeroController heroController = StatControllerLib.onlyHeroController(controller);
    if (_S().heroBusySlots[heroPackedId] != 0) revert IAppErrors.EquippedItemsExist();

    uint32 lifeChances = heroStats(s, hero, heroId).lifeChances;

    // -------------------------- clear
    delete s.heroTotalAttributes[heroPackedId];
    delete s.heroTemporallyAttributes[heroPackedId];
    delete s.heroBonusAttributes[heroPackedId];

    // -------------------------- init from zero
    uint32[] memory baseStats = _initCoreAndAttributes(s, heroPackedId, heroClass);
    _changeChangeableStats(
      s,
      heroPackedId,
      1, // level is set to 1
      0, // experience is set to 0
      baseStats[0], // life is restored
      baseStats[1], // mana is restored
      lifeChances// life chances are not changed
    );

    // custom data is NOT cleared on reborn, new custom data map is used on each new NG_LVL
    _prepareHeroCustomDataForNextNgLevel(heroController, hero, heroId);
  }

  function _prepareHeroCustomDataForNextNgLevel(IHeroController heroController, address hero, uint heroId) internal {
    // assume here, that statController.reborn is called AFTER incrementing of NG_LVL, current NG_LVL has "new" value
    uint8 newNgLevel = heroController.getHeroInfo(hero, heroId).ngLevel;
    if (newNgLevel == 0) revert IAppErrors.ZeroValueNotAllowed(); // edge case
    uint8 prevNgLevel = newNgLevel - 1;

    // copy value of DEATH_COUNT from current ng-level to next ng-level
    (bool exist, uint value) = _S().heroCustomDataV2[PackingLib.packNftIdWithValue(hero, heroId, prevNgLevel)].tryGet(DEATH_COUNT_HASH);
    if (exist && value != 0) {
      _S().heroCustomDataV2[PackingLib.packNftIdWithValue(hero, heroId, newNgLevel)].set(DEATH_COUNT_HASH, value);
      emit IApplicationEvents.HeroCustomDataChangedNg(hero, heroId, DEATH_COUNT_HASH, value, newNgLevel);
    }

    // leave KARMA equal to 0 on next ng-level, getter returns default karma in this case
    emit IApplicationEvents.HeroCustomDataChangedNg(hero, heroId, KARMA_HASH, DEFAULT_KARMA_VALUE, newNgLevel);
  }

  /// @notice Keep stories, monsters, DEATH_COUNT_HASH and HERO_CLASS_HASH; remove all other custom data
  function _removeAllHeroCustomData(IHeroController hc, address hero, uint heroId) internal {
    EnumerableMap.Bytes32ToUintMap storage data = _S().heroCustomDataV2[PackingLib.packNftIdWithValue(hero, heroId, hc.getHeroInfo(hero, heroId).ngLevel)];
    uint length = data.length();
    bytes32[] memory keysToRemove = new bytes32[](length);
    bytes32 monsterPrefix = bytes32(abi.encodePacked("MONSTER_")); // 8 bytes
    bytes32 storyPrefix = bytes32(abi.encodePacked("STORY_")); // 6 bytes

    for (uint i; i < length; ++i) {
      (bytes32 key,) = data.at(i);
      if (key == DEATH_COUNT_HASH || key == HERO_CLASS_HASH) continue;

      bool isNotMonster;
      bool isNotStory;
      for (uint j; j < 8; j++) {
        if (!isNotMonster && key[j] != monsterPrefix[j]) {
          isNotMonster = true;
        }
        if (!isNotStory && j < 6 && key[j] != storyPrefix[j]) {
          isNotStory = true;
        }
        if (isNotMonster && isNotStory) break;
      }

      if (isNotMonster && isNotStory) {
        keysToRemove[i] = key;
      }
    }

    for (uint i; i < length; ++i) {
      if (keysToRemove[i] != bytes32(0)) {
        data.remove(keysToRemove[i]);
      }
    }

    emit IApplicationEvents.HeroCustomDataCleared(hero, heroId);
  }

  /// @notice Initialize new hero, set up custom data, core data, changeable stats by default value
  /// @param heroClass [1..6], see StatLib.initHeroXXX
  function initNewHero(
    IStatController.MainState storage s,
    IController c,
    address heroToken,
    uint heroTokenId,
    uint heroClass
  ) external {
    IHeroController heroController = StatControllerLib.onlyHeroController(c);

    bytes32 heroPackedId = PackingLib.packNftId(heroToken, heroTokenId);
    uint32[] memory baseStats = _initCoreAndAttributes(s, heroPackedId, heroClass);

    _changeChangeableStats(s, heroPackedId, 1, 0, baseStats[0], baseStats[1], baseStats[2]);

    emit IApplicationEvents.NewHeroInited(heroToken, heroTokenId, IStatController.ChangeableStats({
      level: 1,
      experience: 0,
      life: baseStats[0],
      mana: baseStats[1],
      lifeChances: baseStats[2]
    }));

    // --- init predefined custom hero data
    _initNewHeroCustomData(s, heroController, heroToken, heroTokenId);
  }

  /// @dev Reset custom hero data if something went wrong
  function resetHeroCustomData(
    IStatController.MainState storage s,
    IController c,
    address heroToken,
    uint heroTokenId
  ) external {
    StatControllerLib.onlyDeployer(c);
    _removeAllHeroCustomData(IHeroController(c.heroController()), heroToken, heroTokenId);
    _initNewHeroCustomData(s, IHeroController(c.heroController()), heroToken, heroTokenId);
  }

  function _initNewHeroCustomData(IStatController.MainState storage s, IHeroController heroController, address hero, uint heroId) internal {
    uint8 ngLevel = heroController.getHeroInfo(hero, heroId).ngLevel;
    bytes32 heroPackedIdValue = PackingLib.packNftIdWithValue(hero, heroId, ngLevel);

    EnumerableMap.Bytes32ToUintMap storage customData = s.heroCustomDataV2[heroPackedIdValue];

    // set initial karma
    customData.set(KARMA_HASH, DEFAULT_KARMA_VALUE);
    emit IApplicationEvents.HeroCustomDataChangedNg(hero, heroId, KARMA_HASH, DEFAULT_KARMA_VALUE, ngLevel);

    // HERO_CLASS_HASH is not used as custom data anymore, getter takes value directly from heroController

    // set death count value
    // customData[DEATH_COUNT_HASH] is initialized by 0 by default
    emit IApplicationEvents.HeroCustomDataChangedNg(hero, heroId, DEATH_COUNT_HASH, 0, ngLevel);
  }

  function _initCoreAndAttributes(IStatController.MainState storage s, bytes32 heroPackedId, uint heroClass) internal returns (
    uint32[] memory baseStats
  ){
    _initNewHeroCore(s, heroPackedId, heroClass);
    bytes32[] storage totalAttributes = s.heroTotalAttributes[heroPackedId];
    return StatLib.initAttributes(totalAttributes, heroClass, 1, heroClass.initialHero().core);
  }

  function _initNewHeroCore(IStatController.MainState storage s, bytes32 heroPackedId, uint heroClass) internal {
    IStatController.CoreAttributes memory initialCore = heroClass.initialHero().core;
    int32[] memory arr = new int32[](4);

    arr[0] = int32(initialCore.strength);
    arr[1] = int32(initialCore.dexterity);
    arr[2] = int32(initialCore.vitality);
    arr[3] = int32(initialCore.energy);

    s._heroCore[heroPackedId] = PackingLib.packInt32Array(arr);
  }

  function _changeChangeableStats(
    IStatController.MainState storage s,
    bytes32 heroPackedId,
    uint32 level,
    uint32 experience,
    uint32 life,
    uint32 mana,
    uint32 lifeChances
  ) internal {
    if (lifeChances != 0 && life == 0) {
      life = 1;
    }
    uint32[] memory data = new uint32[](5);
    data[0] = level;
    data[1] = experience;
    data[2] = life;
    data[3] = mana;
    data[4] = lifeChances;

    s.heroStats[heroPackedId] = PackingLib.packUint32Array(data);
  }

  /// @notice Add/remove the item to/from the hero
  function changeHeroItemSlot(
    IStatController.MainState storage s,
    IController controller,
    address heroToken,
    uint64 heroTokenId,
    uint itemType,
    uint8 itemSlot,
    address itemToken,
    uint itemTokenId,
    bool equip
  ) internal {
    StatControllerLib.onlyItemController(controller);
    if (!StatControllerLib.isItemTypeEligibleToItemSlot(itemType, itemSlot)) revert IAppErrors.ErrorItemNotEligibleForTheSlot(itemType, itemSlot);

    // if we are going to take an item by two hands, we need both hands free.
    // if we are going to use only one hand, we shouldn't keep anything by two hands
    if (itemSlot == uint(IStatController.ItemSlots.TWO_HAND)) {
      if (heroItemSlot(s, heroToken, heroTokenId, uint8(IStatController.ItemSlots.RIGHT_HAND)) != bytes32(0)
        || heroItemSlot(s, heroToken, heroTokenId, uint8(IStatController.ItemSlots.LEFT_HAND)) != bytes32(0)) {
        revert IAppErrors.ErrorItemSlotBusyHand(itemSlot);
      }
    }
    if (itemSlot == uint(IStatController.ItemSlots.RIGHT_HAND) || itemSlot == uint(IStatController.ItemSlots.LEFT_HAND)) {
      if (heroItemSlot(s, heroToken, heroTokenId, uint8(IStatController.ItemSlots.TWO_HAND)) != bytes32(0)) {
        revert IAppErrors.ErrorItemSlotBusyHand(itemSlot);
      }
    }

    bytes32 heroPackedId = PackingLib.packNftId(heroToken, heroTokenId);
    (address equippedItem, uint equippedItemId) = PackingLib.unpackNftId(heroItemSlot(s, heroToken, heroTokenId, itemSlot));
    if (equip) {
      if (equippedItem != address(0)) revert IAppErrors.ErrorItemSlotBusy();

      s.heroSlots[PackingLib.packMapObject(heroToken, uint64(heroTokenId), itemSlot)] = PackingLib.packNftId(itemToken, itemTokenId);
      s.heroBusySlots[heroPackedId] = PackingLib.changeUnit8ArrayWithCheck(s.heroBusySlots[heroPackedId], itemSlot, 1, 0);
    } else {
      if (equippedItem != itemToken || equippedItemId != itemTokenId) revert IAppErrors.ErrorItemNotInSlot();

      delete s.heroSlots[PackingLib.packMapObject(heroToken, uint64(heroTokenId), itemSlot)];
      s.heroBusySlots[heroPackedId] = PackingLib.changeUnit8ArrayWithCheck(s.heroBusySlots[heroPackedId], itemSlot, 0, 1);
    }

    emit IApplicationEvents.HeroItemSlotChanged(heroToken, heroTokenId, itemType, itemSlot, itemToken, itemTokenId, equip, msg.sender);
  }

  /// @notice Increase or decrease stats (life, mana, lifeChances). Experience can be increased only.
  function changeCurrentStats(
    IStatController.MainState storage s,
    IController c,
    address heroToken,
    uint heroTokenId,
    IStatController.ChangeableStats memory change,
    bool increase
  ) internal {
    StatControllerLib.onlyRegisteredContract(c);

    bytes32 heroPackedId = PackingLib.packNftId(heroToken, heroTokenId);
    IStatController.ChangeableStats memory currentStats = heroStats(s, heroToken, heroTokenId);

    uint32 life = currentStats.life;
    uint32 mana = currentStats.mana;
    uint32 lifeChances = currentStats.lifeChances;

    if (increase) {
      bytes32[] storage attrs = s.heroTotalAttributes[heroPackedId];
      int32 maxLife = attrs.getInt32(uint(IStatController.ATTRIBUTES.LIFE));
      int32 maxMana = attrs.getInt32(uint(IStatController.ATTRIBUTES.MANA));
      int32 maxLC = attrs.getInt32(uint(IStatController.ATTRIBUTES.LIFE_CHANCES));

      currentStats.experience += change.experience;
      life = uint32(Math.min(maxLife.toUint(), uint(life + change.life)));
      mana = uint32(Math.min(maxMana.toUint(), uint(mana + change.mana)));

      // Assume that Life Chances can be increased only by 1 per use.
      // Some stories and events can allow users to increase life chance above max...
      // Such attempts should be forbidden on UI side, we just silently ignore them here, no revert
      lifeChances = uint32(Math.min(maxLC.toUint(), uint(lifeChances + change.lifeChances)));
    } else {
      if (change.experience != 0) revert IAppErrors.ErrorExperienceMustNotDecrease();
      life = life > change.life ? life - change.life : 0;
      lifeChances = lifeChances > change.lifeChances ? lifeChances - change.lifeChances : 0;
      mana = mana > change.mana ? mana - change.mana : 0;
    }

    _changeChangeableStats(s, heroPackedId, currentStats.level, currentStats.experience, life, mana, lifeChances);
    emit IApplicationEvents.CurrentStatsChanged(heroToken, heroTokenId, change, increase, msg.sender);
  }

  /// @notice Mark consumable {item} as used
  function registerConsumableUsage(
    IStatController.MainState storage s,
    IController c,
    address heroToken,
    uint heroTokenId,
    address item
  ) internal {
    StatControllerLib.onlyRegisteredContract(c);

    if (!s.usedConsumables[PackingLib.packNftId(heroToken, heroTokenId)].add(item)) revert IAppErrors.ErrorConsumableItemIsUsed(item);
    emit IApplicationEvents.ConsumableUsed(heroToken, heroTokenId, item);
  }

  /// @notice Clear all consumable items of the given hero
  function clearUsedConsumables(
    IStatController.MainState storage s,
    IController c,
    address heroToken,
    uint heroTokenId
  ) internal {
    StatControllerLib.onlyRegisteredContract(c);

    EnumerableSet.AddressSet storage items = s.usedConsumables[PackingLib.packNftId(heroToken, heroTokenId)];

    uint length = items.length();

    for (uint i; i < length; ++i) {
      // we are removing the first element, so it's safe to use in cycle
      address item = items.at(0);
      if (!items.remove(item)) revert IAppErrors.ErrorCannotRemoveItemFromMap();
      emit IApplicationEvents.RemoveConsumableUsage(heroToken, heroTokenId, item);
    }
  }

  /// @notice Increase or decrease values of the given attributes, any attributes are allowed.
  /// @dev If a core attribute is changed than depended attributes are recalculated
  function changeBonusAttributes(
    IStatController.MainState storage s,
    IController c,
    IStatController.ChangeAttributesInfo memory info
  ) internal {
    StatControllerLib.onlyRegisteredContract(c);
    bytes32 heroPackedId = PackingLib.packNftId(info.heroToken, info.heroTokenId);

    IStatController.ChangeableStats memory stats = heroStats(s, info.heroToken, info.heroTokenId);
    bytes32[] storage totalAttributes = s.heroTotalAttributes[heroPackedId];
    (bytes32[] storage bonusMain, bytes32[] storage bonusExtra) = info.temporally
      ? (s.heroTemporallyAttributes[heroPackedId], s.heroBonusAttributes[heroPackedId])
      : (s.heroBonusAttributes[heroPackedId], s.heroTemporallyAttributes[heroPackedId]);

    int32[] memory cachedTotalAttrChanged = new int32[](info.changeAttributes.length);
    for (uint i; i < info.changeAttributes.length; ++i) {
      int32 change = info.changeAttributes[i];
      if (change != 0) {
        int32 newTotalValue;

        if (info.add) {
          bonusMain.changeInt32(i, change);
          newTotalValue = totalAttributes.getInt32(i) + change;
        } else {
          bonusMain.changeInt32(i, - change);
          newTotalValue = totalAttributes.getInt32(i) - change;
        }

        // todo in some cases value stored here to totalAttributes will be overwritten below by updateCoreDependAttributes
        // it happens if core attribute is changed AND it's depend attribute is change too
        // values of the depend attribute will be overwritten by updateCoreDependAttributes
        // fix it together with PACKED WRITING
        totalAttributes.setInt32(i, newTotalValue);
        cachedTotalAttrChanged[i] = newTotalValue;
      }
    }

    _updateCoreDependAttributes(c, totalAttributes, bonusMain, bonusExtra, stats, info.heroToken, cachedTotalAttrChanged, info.changeAttributes);
    _compareStatsWithAttributes(s, heroPackedId, totalAttributes, stats);

    emit IApplicationEvents.BonusAttributesChanged(info.heroToken, info.heroTokenId, info.add, info.temporally, msg.sender);
  }

  /// @dev Make sure we don't have life/mana more than total attributes after decreasing
  function _compareStatsWithAttributes(
    IStatController.MainState storage s,
    bytes32 heroPackedId,
    bytes32[] storage totalAttributes,
    IStatController.ChangeableStats memory curStats
  ) internal {
    uint life = totalAttributes.getInt32(uint(IStatController.ATTRIBUTES.LIFE)).toUint();
    uint mana = totalAttributes.getInt32(uint(IStatController.ATTRIBUTES.MANA)).toUint();
    bool changed;
    if (life < curStats.life) {
      curStats.life = uint32(Math.min(life, curStats.life));
      changed = true;
    }
    if (mana < curStats.mana) {
      curStats.mana = uint32(Math.min(mana, curStats.mana));
      changed = true;
    }
    if (changed) {
      _changeChangeableStats(s,
        heroPackedId,
        curStats.level,
        curStats.experience,
        curStats.life,
        curStats.mana,
        curStats.lifeChances
      );
    }
  }

  function clearTemporallyAttributes(
    IStatController.MainState storage s,
    IController c,
    address heroToken,
    uint heroTokenId
  ) internal {
    StatControllerLib.onlyRegisteredContract(c);
    bytes32 heroPackedId = PackingLib.packNftId(heroToken, heroTokenId);

    bytes32[] memory tmpBonuses = s.heroTemporallyAttributes[heroPackedId];

    IStatController.ChangeableStats memory stats = heroStats(s, heroToken, heroTokenId);
    bytes32[] storage bonus = s.heroBonusAttributes[heroPackedId];
    bytes32[] storage totalAttributes = s.heroTotalAttributes[heroPackedId];

    int32[] memory baseValues = new int32[](uint(IStatController.ATTRIBUTES.END_SLOT));
    int32[] memory tmpBonusesUnpacked = new int32[](uint(IStatController.ATTRIBUTES.END_SLOT));
    for (uint i; i < uint(IStatController.ATTRIBUTES.END_SLOT); ++i) {
      int32 value = tmpBonuses.getInt32Memory(i);
      if (value != int32(0)) {
        (baseValues[i],) = totalAttributes.changeInt32(i, - int32(uint32(value)));
        tmpBonusesUnpacked[i] = value;
      }
    }

    delete s.heroTemporallyAttributes[heroPackedId];

    bytes32[] storage tmpBonusesStorage = s.heroTemporallyAttributes[heroPackedId];

    _updateCoreDependAttributes(c, totalAttributes, bonus, tmpBonusesStorage, stats, heroToken, baseValues, tmpBonusesUnpacked);
    _compareStatsWithAttributes(s, heroPackedId, totalAttributes, stats);

    emit IApplicationEvents.TemporallyAttributesCleared(heroToken, heroTokenId, msg.sender);
  }

  /// @dev Update depend-values for all changed attributes
  function _updateCoreDependAttributes(
    IController c,
    bytes32[] storage totalAttributes,
    bytes32[] storage bonusMain,
    bytes32[] storage bonusExtra,
    IStatController.ChangeableStats memory stats,
    address heroToken,
    int32[] memory baseValues,
    int32[] memory changed
  ) internal {
    // handle core depend attributes in the second loop, totalAttributes should be updated together
    uint len = changed.length;
    for (uint i; i < len; ++i) {
      // depend-values should be recalculated if corresponded core value is changed (even if it's equal to 0 now)
      if (changed[i] != 0) {
        StatLib.updateCoreDependAttributes(c, totalAttributes, bonusMain, bonusExtra, stats, i, heroToken, baseValues[i]);
      }
    }
  }

  function levelUp(
    IStatController.MainState storage s,
    IController c,
    address heroToken,
    uint heroTokenId,
    uint heroClass,
    IStatController.CoreAttributes memory change
  ) internal returns (uint newLvl) {
    StatControllerLib.onlyHeroController(c);

    bytes32 heroPackedId = PackingLib.packNftId(heroToken, heroTokenId);
    if (change.strength + change.dexterity + change.vitality + change.energy != LEVEL_UP_SUM) revert IAppErrors.ErrorWrongLevelUpSum();

    IStatController.ChangeableStats memory currentStats = heroStats(s, heroToken, heroTokenId);

    if (currentStats.level >= StatLib.MAX_LEVEL) revert IAppErrors.ErrorMaxLevel();
    if (currentStats.level.levelExperience() > currentStats.experience) revert IAppErrors.ErrorNotEnoughExperience();
    currentStats.level++;

    {
      int32[] memory data = PackingLib.unpackInt32Array(s._heroCore[heroPackedId]);

      data[0] += change.strength;
      data[1] += change.dexterity;
      data[2] += change.vitality;
      data[3] += change.energy;

      s._heroCore[heroPackedId] = PackingLib.packInt32Array(data);
    }

    bytes32[] storage totalAttributes = s.heroTotalAttributes[heroPackedId];
    {
      bytes32[] storage bonus = s.heroBonusAttributes[heroPackedId];
      bytes32[] storage bonusTmp = s.heroTemporallyAttributes[heroPackedId];

      // update
      _addCoreToTotal(
        c,
        totalAttributes,
        bonus,
        bonusTmp,
        currentStats,
        heroToken,
        change.strength,
        uint(IStatController.ATTRIBUTES.STRENGTH)
      );
      _addCoreToTotal(
        c,
        totalAttributes,
        bonus,
        bonusTmp,
        currentStats,
        heroToken,
        change.dexterity,
        uint(IStatController.ATTRIBUTES.DEXTERITY)
      );
      _addCoreToTotal(
        c,
        totalAttributes,
        bonus,
        bonusTmp,
        currentStats,
        heroToken,
        change.vitality,
        uint(IStatController.ATTRIBUTES.VITALITY)
      );
      _addCoreToTotal(
        c,
        totalAttributes,
        bonus,
        bonusTmp,
        currentStats,
        heroToken,
        change.energy,
        uint(IStatController.ATTRIBUTES.ENERGY)
      );
    }

    // setup new level and restore life/mana
    currentStats.life = uint32(totalAttributes.getInt32(uint(IStatController.ATTRIBUTES.LIFE)).toUint());
    currentStats.mana = uint32(totalAttributes.getInt32(uint(IStatController.ATTRIBUTES.MANA)).toUint());

    _changeChangeableStats(
      s,
      heroPackedId,
      currentStats.level,
      currentStats.experience,
      currentStats.life,
      currentStats.mana,
      currentStats.lifeChances
    );

    emit IApplicationEvents.LevelUp(heroToken, heroTokenId, heroClass, change);

    return currentStats.level;
  }

  /// @notice scb-1009: Update current values of Life and mana during reinforcement as following:
  /// Reinforcement increases max value of life/mana on DELTA, current value of life/mana is increased on DELTA too
  /// @param prevAttributes Hero attributes before reinforcement
  function restoreLifeAndMana(
    IStatController.MainState storage s,
    IController c,
    address heroToken,
    uint heroTokenId,
    int32[] memory prevAttributes
  ) internal {
    onlyRegisteredContract(c);

    IStatController.ChangeableStats memory currentStats = heroStats(s, heroToken, heroTokenId);
    bytes32 heroPackedId = PackingLib.packNftId(heroToken, heroTokenId);

    // assume here that totalAttributes were already updated during reinforcement
    // and so max values of life and mana were increased on delta1 and delta2
    bytes32[] storage totalAttributes = s.heroTotalAttributes[heroPackedId];

    // now increase current values of life and mana on delta1 and delta2 too
    currentStats.life += _getPositiveDelta(totalAttributes.getInt32(uint(IStatController.ATTRIBUTES.LIFE)), prevAttributes[uint(IStatController.ATTRIBUTES.LIFE)]);
    currentStats.mana += _getPositiveDelta(totalAttributes.getInt32(uint(IStatController.ATTRIBUTES.MANA)), prevAttributes[uint(IStatController.ATTRIBUTES.MANA)]);

    _changeChangeableStats(
      s,
      heroPackedId,
      currentStats.level,
      currentStats.experience,
      currentStats.life,
      currentStats.mana,
      currentStats.lifeChances
    );
  }

  function _getPositiveDelta(int32 a, int32 b) internal pure returns (uint32) {
    return a < b
      ? 0
      : uint32(uint(int(a - b)));
  }

  function _addCoreToTotal(
    IController c,
    bytes32[] storage totalAttributes,
    bytes32[] storage bonus,
    bytes32[] storage bonusTmp,
    IStatController.ChangeableStats memory stats,
    address heroToken,
    int32 changeValue,
    uint attrIndex
  ) internal {
    if (changeValue != 0) {
      (int32 newValue,) = totalAttributes.changeInt32(attrIndex, int32(uint32(changeValue)));
      StatLib.updateCoreDependAttributes(c, totalAttributes, bonus, bonusTmp, stats, attrIndex, heroToken, newValue);
    }
  }

  function setHeroCustomData(
    IStatController.MainState storage s,
    IController c,
    address token,
    uint tokenId,
    bytes32 index,
    uint value
  ) internal {
    IHeroController heroController = StatControllerLib.onlyRegisteredContract(c);
    uint8 ngLevel = heroController.getHeroInfo(token, tokenId).ngLevel;

    if (index == KARMA_HASH && value == 0) {
      revert IAppErrors.ErrorZeroKarmaNotAllowed();
    }

    s.heroCustomDataV2[PackingLib.packNftIdWithValue(token, tokenId, ngLevel)].set(index, value);
    emit IApplicationEvents.HeroCustomDataChangedNg(token, tokenId, index, value, ngLevel);
  }

  function setGlobalCustomData(
    IStatController.MainState storage s,
    IController c,
    bytes32 index,
    uint value
  ) internal {
    StatControllerLib.onlyRegisteredContract(c);

    s.globalCustomData[index] = value;

    emit IApplicationEvents.GlobalCustomDataChanged(index, value);
  }
  //endregion ------------------------ ACTIONS
}

File 18 of 24 : StatLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../interfaces/IStatController.sol";
import "../interfaces/IHeroController.sol";
import "../interfaces/IAppErrors.sol";
import "../openzeppelin/Math.sol";
import "./CalcLib.sol";
import "./PackingLib.sol";

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

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

  /// @notice Version of the contract
  /// @dev Should be incremented when contract changed
  string public constant STAT_LIB_VERSION = "1.0.0";
  uint32 public constant MAX_LEVEL = 99;

  uint public constant BASE_EXPERIENCE = 100_000;
  uint public constant BIOME_LEVEL_STEP = 5;
  uint internal constant _MAX_AMPLIFIER = 1e18;
  uint private constant _PRECISION = 1e18;
  uint private constant VIRTUAL_LEVEL_GAP = 2;

  /// @dev Assume MAX_BIOME * BIOME_LEVEL_STEP < MAX_LEVEL + 1, see dungeonTreasuryReward
  uint public constant MAX_POSSIBLE_BIOME = 19;
  //endregion --------------------------- Constants

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

  struct BaseMultiplier {
    uint minDamage;
    uint maxDamage;
    uint attackRating;
    uint defense;
    uint blockRating;
    uint life;
    uint mana;
  }

  struct LevelUp {
    uint life;
    uint mana;
  }

  struct InitialHero {
    IStatController.CoreAttributes core;
    BaseMultiplier multiplier;
    LevelUp levelUp;
    int32 baseLifeChances;
  }

  enum HeroClasses {
    UNKNOWN,
    THRALL,
    SAVAGE,
    MAGE,
    ASSASSIN,
    GHOST,
    HAMMERGINA,
    END_SLOT
  }
  //endregion --------------------------- Data types

  //region --------------------------- BASE

  function isNetworkWithOldSavage() public view returns (bool) {
    return block.chainid == uint(111188) || block.chainid == uint(250);
  }

  // --- HERO 1 (Slave) ---

  function initialHero1() internal pure returns (InitialHero memory) {
    return InitialHero({
      core: IStatController.CoreAttributes({
      strength: 15,
      dexterity: 15,
      vitality: 30,
      energy: 10
    }),

      multiplier: BaseMultiplier({
      minDamage: 0.1e18,
      maxDamage: 0.2e18,
      attackRating: 2e18,
      defense: 2e18,
      blockRating: 0.1e18,
      life: 1.5e18,
      mana: 0.5e18
    }),

      levelUp: LevelUp({
      life: 2e18,
      mana: 1e18
    }),

      baseLifeChances: 5
    });
  }

  // --- HERO 2 (Spata) ---

  function initialHero2() internal view returns (InitialHero memory) {

    bool old = isNetworkWithOldSavage();

    return InitialHero({
      core: IStatController.CoreAttributes({
      strength: 30,
      dexterity: 5,
      vitality: 25,
      energy: 10
    }),

      multiplier: BaseMultiplier({
      minDamage: 0.15e18,
      maxDamage: old ? 0.25e18 : 0.5e18,
      attackRating: old ? 2e18 : 3e18,
      defense: 1e18,
      blockRating: 0.08e18,
      life: 1.3e18,
      mana: 0.5e18
    }),

      levelUp: LevelUp({
      life: 1.8e18,
      mana: 1e18
    }),

      baseLifeChances: 5
    });
  }

  // --- HERO 3 (Decidia) ---

  function initialHero3() internal pure returns (InitialHero memory) {
    return InitialHero({
      core: IStatController.CoreAttributes({
      strength: 10,
      dexterity: 15,
      vitality: 20,
      energy: 25
    }),

      multiplier: BaseMultiplier({
      minDamage: 0.1e18,
      maxDamage: 0.2e18,
      attackRating: 2e18,
      defense: 1e18,
      blockRating: 0.1e18,
      life: 1e18,
      mana: 2e18
    }),

      levelUp: LevelUp({
      life: 1.3e18,
      mana: 2e18
    }),

      baseLifeChances: 5
    });
  }

  // --- HERO 4 (Innatus) ---

  function initialHero4() internal pure returns (InitialHero memory) {
    return InitialHero({
      core: IStatController.CoreAttributes({
      strength: 15,
      dexterity: 25,
      vitality: 15,
      energy: 15
    }),

      multiplier: BaseMultiplier({
      minDamage: 0.1e18,
      maxDamage: 0.2e18,
      attackRating: 4e18,
      defense: 3e18,
      blockRating: 0.2e18,
      life: 1.2e18,
      mana: 1e18
    }),

      levelUp: LevelUp({
      life: 1.7e18,
      mana: 1.5e18
    }),

      baseLifeChances: 5
    });
  }

  // --- HERO 5 (F2P) ---

  function initialHero5() internal pure returns (InitialHero memory) {
    return InitialHero({
      core: IStatController.CoreAttributes({
      strength: 20,
      dexterity: 20,
      vitality: 20,
      energy: 10
    }),

      multiplier: BaseMultiplier({
      minDamage: 0.15e18,
      maxDamage: 0.25e18,
      attackRating: 3e18,
      defense: 2.5e18,
      blockRating: 0.15e18,
      life: 1.5e18,
      mana: 1.5e18
    }),

      levelUp: LevelUp({
      life: 1.5e18,
      mana: 1.5e18
    }),

      baseLifeChances: 1
    });
  }

  // --- HERO 6 (F2P) HAMMERGINA ---

  function initialHero6() internal pure returns (InitialHero memory) {
    return InitialHero({
      core: IStatController.CoreAttributes({
      strength: 50,
      dexterity: 30,
      vitality: 50,
      energy: 15
    }),

      multiplier: BaseMultiplier({
      minDamage: 0.2e18,
      maxDamage: 0.3e18,
      attackRating: 5e18,
      defense: 3e18,
      blockRating: 0.15e18,
      life: 2e18,
      mana: 2e18
    }),

      levelUp: LevelUp({
      life: 1.7e18,
      mana: 1.5e18
    }),

      baseLifeChances: 1
    });
  }

  // ------

  function initialHero(uint heroClass) internal view returns (InitialHero memory) {
    if (heroClass == 1) {
      return initialHero1();
    } else if (heroClass == 2) {
      return initialHero2();
    } else if (heroClass == 3) {
      return initialHero3();
    } else if (heroClass == 4) {
      return initialHero4();
    } else if (heroClass == 5) {
      return initialHero5();
    } else if (heroClass == 6) {
      return initialHero6();
    } else {
      revert IAppErrors.UnknownHeroClass(heroClass);
    }
  }
  //endregion --------------------------- BASE

  //region --------------------------- CALCULATIONS

  function minDamage(int32 strength, uint heroClass) internal view returns (int32) {
    return int32(int(strength.toUint() * initialHero(heroClass).multiplier.minDamage / _PRECISION));
  }

  function maxDamage(int32 strength, uint heroClass) internal view returns (int32){
    return int32(int(strength.toUint() * initialHero(heroClass).multiplier.maxDamage / _PRECISION));
  }

  function attackRating(int32 dexterity, uint heroClass) internal view returns (int32){
    return int32(int(dexterity.toUint() * initialHero(heroClass).multiplier.attackRating / _PRECISION));
  }

  function defense(int32 dexterity, uint heroClass) internal view returns (int32){
    return int32(int(dexterity.toUint() * initialHero(heroClass).multiplier.defense / _PRECISION));
  }

  function blockRating(int32 dexterity, uint heroClass) internal view returns (int32){
    return int32(int(Math.min((dexterity.toUint() * initialHero(heroClass).multiplier.blockRating / _PRECISION), 75)));
  }

  function life(int32 vitality, uint heroClass, uint32 level) internal view returns (int32){
    return int32(int(
      (vitality.toUint() * initialHero(heroClass).multiplier.life / _PRECISION)
      + (uint(level) * initialHero(heroClass).levelUp.life / _PRECISION)
    ));
  }

  function mana(int32 energy, uint heroClass, uint32 level) internal view returns (int32){
    return int32(int(
      (energy.toUint() * initialHero(heroClass).multiplier.mana / _PRECISION)
      + (uint(level) * initialHero(heroClass).levelUp.mana / _PRECISION)
    ));
  }

  function lifeChances(uint heroClass, uint32 /*level*/) internal view returns (int32){
    return initialHero(heroClass).baseLifeChances;
  }

  function levelExperience(uint32 level) internal pure returns (uint32) {
    if (level == 0 || level >= MAX_LEVEL) {
      return 0;
    }
    return uint32(uint(level) * BASE_EXPERIENCE * (67e17 - CalcLib.log2((uint(MAX_LEVEL - level + 2)) * 1e18)) / 1e18);
  }

  function chanceToHit(
    uint attackersAttackRating,
    uint defendersDefenceRating,
    uint attackersLevel,
    uint defendersLevel,
    uint arFactor
  ) internal pure returns (uint) {
    attackersAttackRating += attackersAttackRating * arFactor / 100;
    uint x = Math.max(attackersAttackRating, 1);
    uint y = Math.max(attackersAttackRating + defendersDefenceRating, 1);
    uint z = attackersLevel;
    uint k = defendersLevel / 2;
    uint xy = x * 1e18 / y;
    uint zk = z * 1e18 / (attackersLevel + k);
    uint base = 2 * xy * zk / 1e18;
    return Math.max(Math.min(base, 0.95e18), 0.2e18);
  }

  function experienceToVirtualLevel(uint experience, uint startFromLevel) internal pure returns (uint level) {
    level = startFromLevel;
    for (; level < MAX_LEVEL;) {
      if (levelExperience(uint32(level)) >= (experience + 1)) {
        break;
      }
      unchecked{++level;}
    }
  }

  function expPerMonster(uint32 monsterExp, uint monsterRarity, uint32 /*heroExp*/, uint32 /*heroCurrentLvl*/, uint /*monsterBiome*/) internal pure returns (uint32) {
    // do not reduce exp per level, it is no economical sense
    return uint32(uint(monsterExp) + uint(monsterExp) * monsterRarity / _MAX_AMPLIFIER);
  }

  /// @notice Allow to calculate delta param for {mintDropChance}
  function mintDropChanceDelta(uint heroCurrentExp, uint heroCurrentLevel, uint monsterBiome) internal pure returns (uint) {
    uint heroBiome = getVirtualLevel(heroCurrentExp, heroCurrentLevel, true) / StatLib.BIOME_LEVEL_STEP + 1;
    return heroBiome > monsterBiome ? 2 ** (heroBiome - monsterBiome + 10) : 0;
  }

  function getVirtualLevel(uint heroCurrentExp, uint heroCurrentLevel, bool withGap) internal pure returns (uint) {
    uint virtualLevel = StatLib.experienceToVirtualLevel(heroCurrentExp, heroCurrentLevel);
    if (withGap && (virtualLevel + 1) > VIRTUAL_LEVEL_GAP) {
      virtualLevel -= VIRTUAL_LEVEL_GAP;
    }
    return virtualLevel;
  }

  function initAttributes(
    bytes32[] storage attributes,
    uint heroClass,
    uint32 level,
    IStatController.CoreAttributes memory base
  ) internal returns (uint32[] memory result) {

    attributes.setInt32(uint(IStatController.ATTRIBUTES.STRENGTH), base.strength);
    attributes.setInt32(uint(IStatController.ATTRIBUTES.DEXTERITY), base.dexterity);
    attributes.setInt32(uint(IStatController.ATTRIBUTES.VITALITY), base.vitality);
    attributes.setInt32(uint(IStatController.ATTRIBUTES.ENERGY), base.energy);

    attributes.setInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MIN), minDamage(base.strength, heroClass));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MAX), maxDamage(base.strength, heroClass));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.ATTACK_RATING), attackRating(base.dexterity, heroClass));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.DEFENSE), defense(base.dexterity, heroClass));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.BLOCK_RATING), blockRating(base.dexterity, heroClass));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.LIFE), life(base.vitality, heroClass, level));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.MANA), mana(base.energy, heroClass, level));
    attributes.setInt32(uint(IStatController.ATTRIBUTES.LIFE_CHANCES), lifeChances(heroClass, level));

    result = new uint32[](3);
    result[0] = uint32(life(base.vitality, heroClass, level).toUint());
    result[1] = uint32(mana(base.energy, heroClass, level).toUint());
    result[2] = uint32(lifeChances(heroClass, uint32(level)).toUint());
  }

  function updateCoreDependAttributesInMemory(
    int32[] memory attributes,
    int32[] memory bonus,
    uint heroClass,
    uint32 level
  ) internal view returns (int32[] memory) {
    int32 strength = attributes[uint(IStatController.ATTRIBUTES.STRENGTH)];
    int32 dexterity = attributes[uint(IStatController.ATTRIBUTES.DEXTERITY)];
    int32 vitality = attributes[uint(IStatController.ATTRIBUTES.VITALITY)];
    int32 energy = attributes[uint(IStatController.ATTRIBUTES.ENERGY)];

    attributes[uint(IStatController.ATTRIBUTES.DAMAGE_MIN)] = minDamage(strength, heroClass) + bonus[uint(IStatController.ATTRIBUTES.DAMAGE_MIN)];
    attributes[uint(IStatController.ATTRIBUTES.DAMAGE_MAX)] = maxDamage(strength, heroClass) + bonus[uint(IStatController.ATTRIBUTES.DAMAGE_MAX)];
    attributes[uint(IStatController.ATTRIBUTES.ATTACK_RATING)] = attackRating(dexterity, heroClass) + bonus[uint(IStatController.ATTRIBUTES.ATTACK_RATING)];
    attributes[uint(IStatController.ATTRIBUTES.DEFENSE)] = defense(dexterity, heroClass) + bonus[uint(IStatController.ATTRIBUTES.DEFENSE)];
    attributes[uint(IStatController.ATTRIBUTES.BLOCK_RATING)] = blockRating(dexterity, heroClass) + bonus[uint(IStatController.ATTRIBUTES.BLOCK_RATING)];
    attributes[uint(IStatController.ATTRIBUTES.LIFE)] = life(vitality, heroClass, level) + bonus[uint(IStatController.ATTRIBUTES.LIFE)];
    attributes[uint(IStatController.ATTRIBUTES.MANA)] = mana(energy, heroClass, level) + bonus[uint(IStatController.ATTRIBUTES.MANA)];
    return attributes;
  }

  function updateCoreDependAttributes(
    IController controller,
    bytes32[] storage attributes,
    bytes32[] storage bonusMain,
    bytes32[] storage bonusExtra,
    IStatController.ChangeableStats memory _heroStats,
    uint index,
    address heroToken,
    int32 base
  ) internal {
    uint heroClass = IHeroController(controller.heroController()).heroClass(heroToken);
    if (index == uint(IStatController.ATTRIBUTES.STRENGTH)) {

      attributes.setInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MIN),
        StatLib.minDamage(base, heroClass)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MIN))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MIN))
      );
      attributes.setInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MAX),
        StatLib.maxDamage(base, heroClass)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MAX))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MAX))
      );
    } else if (index == uint(IStatController.ATTRIBUTES.DEXTERITY)) {

      attributes.setInt32(uint(IStatController.ATTRIBUTES.ATTACK_RATING),
        StatLib.attackRating(base, heroClass)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.ATTACK_RATING))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.ATTACK_RATING))
      );

      attributes.setInt32(uint(IStatController.ATTRIBUTES.DEFENSE),
        StatLib.defense(base, heroClass)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.DEFENSE))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.DEFENSE))
      );

      attributes.setInt32(uint(IStatController.ATTRIBUTES.BLOCK_RATING),
        StatLib.blockRating(base, heroClass)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.BLOCK_RATING))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.BLOCK_RATING))
      );
    } else if (index == uint(IStatController.ATTRIBUTES.VITALITY)) {

      attributes.setInt32(uint(IStatController.ATTRIBUTES.LIFE),
        StatLib.life(base, heroClass, _heroStats.level)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.LIFE))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.LIFE))
      );
    } else if (index == uint(IStatController.ATTRIBUTES.ENERGY)) {

      attributes.setInt32(uint(IStatController.ATTRIBUTES.MANA),
        StatLib.mana(base, heroClass, _heroStats.level)
        + bonusMain.getInt32(uint(IStatController.ATTRIBUTES.MANA))
        + bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.MANA))
      );
    }
  }

  function attributesAdd(int32[] memory base, int32[] memory add) internal pure returns (int32[] memory) {
    unchecked{
      for (uint i; i < base.length; ++i) {
        base[i] += add[i];
      }
    }
    return base;
  }

// Currently this function is not used
//  function attributesRemove(int32[] memory base, int32[] memory remove) internal pure returns (int32[] memory) {
//    unchecked{
//      for (uint i; i < base.length; ++i) {
//        base[i] = CalcLib.minusWithMinFloorI32(base[i], remove[i]);
//      }
//    }
//    return base;
//  }

  function packChangeableStats(IStatController.ChangeableStats memory stats) internal pure returns (bytes32) {
    uint32[] memory cData = new uint32[](5);
    cData[0] = stats.level;
    cData[1] = stats.experience;
    cData[2] = stats.life;
    cData[3] = stats.mana;
    cData[4] = stats.lifeChances;

    return cData.packUint32Array();
  }

  function unpackChangeableStats(bytes32 data) internal pure returns (IStatController.ChangeableStats memory result) {
    uint32[] memory cData = data.unpackUint32Array();
    return IStatController.ChangeableStats({
      level: cData[0],
      experience: cData[1],
      life: cData[2],
      mana: cData[3],
      lifeChances: cData[4]
    });
  }

  function bytesToFullAttributesArray(bytes32[] memory attributes) internal pure returns (int32[] memory result) {
    (int32[] memory values, uint8[] memory ids) = attributes.toInt32ArrayWithIds();
    return valuesToFullAttributesArray(values, ids);
  }

  function valuesToFullAttributesArray(int32[] memory values, uint8[] memory ids) internal pure returns (int32[] memory result) {
    result = new int32[](uint(IStatController.ATTRIBUTES.END_SLOT));
    for (uint i; i < values.length; ++i) {
      int32 value = values[i];
      if (value != 0) {
        result[ids[i]] = value;
      }
    }
  }
  //endregion --------------------------- CALCULATIONS

}

File 19 of 24 : EnumerableMap.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js.

pragma solidity ^0.8.20;

import {EnumerableSet} from "./EnumerableSet.sol";

/**
 * @dev Library for managing an enumerable variant of Solidity's
 * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
 * type.
 *
 * Maps have the following properties:
 *
 * - Entries are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Entries are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableMap for EnumerableMap.UintToAddressMap;
 *
 *     // Declare a set state variable
 *     EnumerableMap.UintToAddressMap private myMap;
 * }
 * ```
 *
 * The following map types are supported:
 *
 * - `uint256 -> address` (`UintToAddressMap`) since v3.0.0
 * - `address -> uint256` (`AddressToUintMap`) since v4.6.0
 * - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0
 * - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0
 * - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableMap.
 * ====
 */
library EnumerableMap {
  using EnumerableSet for EnumerableSet.Bytes32Set;

  // To implement this library for multiple types with as little code repetition as possible, we write it in
  // terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions,
  // and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map.
  // This means that we can only create new EnumerableMaps for types that fit in bytes32.

  /**
   * @dev Query for a nonexistent map key.
     */
  error EnumerableMapNonexistentKey(bytes32 key);

  struct Bytes32ToBytes32Map {
    // Storage of keys
    EnumerableSet.Bytes32Set _keys;
    mapping(bytes32 key => bytes32) _values;
  }

  /**
   * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
  function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) {
    map._values[key] = value;
    return map._keys.add(key);
  }

  /**
   * @dev Removes a key-value pair from a map. O(1).
     *
     * Returns true if the key was removed from the map, that is if it was present.
     */
  function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) {
    delete map._values[key];
    return map._keys.remove(key);
  }

  /**
   * @dev Returns true if the key is in the map. O(1).
     */
  function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) {
    return map._keys.contains(key);
  }

  /**
   * @dev Returns the number of key-value pairs in the map. O(1).
     */
  function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) {
    return map._keys.length();
  }

  /**
   * @dev Returns the key-value pair stored at position `index` in the map. O(1).
     *
     * Note that there are no guarantees on the ordering of entries inside the
     * array, and it may change when more entries are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) {
    bytes32 key = map._keys.at(index);
    return (key, map._values[key]);
  }

  /**
   * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
  function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) {
    bytes32 value = map._values[key];
    if (value == bytes32(0)) {
      return (contains(map, key), bytes32(0));
    } else {
      return (true, value);
    }
  }

  /**
   * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
  function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) {
    bytes32 value = map._values[key];
    if (value == 0 && !contains(map, key)) {
      revert EnumerableMapNonexistentKey(key);
    }
    return value;
  }

  /**
   * @dev Return the an array containing all the keys
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) {
    return map._keys.values();
  }

  // UintToUintMap

  struct UintToUintMap {
    Bytes32ToBytes32Map _inner;
  }

  /**
   * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
  function set(UintToUintMap storage map, uint256 key, uint256 value) internal returns (bool) {
    return set(map._inner, bytes32(key), bytes32(value));
  }

  /**
   * @dev Removes a value from a map. O(1).
     *
     * Returns true if the key was removed from the map, that is if it was present.
     */
  function remove(UintToUintMap storage map, uint256 key) internal returns (bool) {
    return remove(map._inner, bytes32(key));
  }

  /**
   * @dev Returns true if the key is in the map. O(1).
     */
  function contains(UintToUintMap storage map, uint256 key) internal view returns (bool) {
    return contains(map._inner, bytes32(key));
  }

  /**
   * @dev Returns the number of elements in the map. O(1).
     */
  function length(UintToUintMap storage map) internal view returns (uint256) {
    return length(map._inner);
  }

  /**
   * @dev Returns the element stored at position `index` in the map. O(1).
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) {
    (bytes32 key, bytes32 value) = at(map._inner, index);
    return (uint256(key), uint256(value));
  }

  /**
   * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
  function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) {
    (bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
    return (success, uint256(value));
  }

  /**
   * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
  function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) {
    return uint256(get(map._inner, bytes32(key)));
  }

  /**
   * @dev Return the an array containing all the keys
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function keys(UintToUintMap storage map) internal view returns (uint256[] memory) {
    bytes32[] memory store = keys(map._inner);
    uint256[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }

  // UintToAddressMap

  struct UintToAddressMap {
    Bytes32ToBytes32Map _inner;
  }

  /**
   * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
  function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) {
    return set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
  }

  /**
   * @dev Removes a value from a map. O(1).
     *
     * Returns true if the key was removed from the map, that is if it was present.
     */
  function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) {
    return remove(map._inner, bytes32(key));
  }

  /**
   * @dev Returns true if the key is in the map. O(1).
     */
  function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) {
    return contains(map._inner, bytes32(key));
  }

  /**
   * @dev Returns the number of elements in the map. O(1).
     */
  function length(UintToAddressMap storage map) internal view returns (uint256) {
    return length(map._inner);
  }

  /**
   * @dev Returns the element stored at position `index` in the map. O(1).
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) {
    (bytes32 key, bytes32 value) = at(map._inner, index);
    return (uint256(key), address(uint160(uint256(value))));
  }

  /**
   * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
  function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) {
    (bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
    return (success, address(uint160(uint256(value))));
  }

  /**
   * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
  function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
    return address(uint160(uint256(get(map._inner, bytes32(key)))));
  }

  /**
   * @dev Return the an array containing all the keys
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function keys(UintToAddressMap storage map) internal view returns (uint256[] memory) {
    bytes32[] memory store = keys(map._inner);
    uint256[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }

  // AddressToUintMap

  struct AddressToUintMap {
    Bytes32ToBytes32Map _inner;
  }

  /**
   * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
  function set(AddressToUintMap storage map, address key, uint256 value) internal returns (bool) {
    return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value));
  }

  /**
   * @dev Removes a value from a map. O(1).
     *
     * Returns true if the key was removed from the map, that is if it was present.
     */
  function remove(AddressToUintMap storage map, address key) internal returns (bool) {
    return remove(map._inner, bytes32(uint256(uint160(key))));
  }

  /**
   * @dev Returns true if the key is in the map. O(1).
     */
  function contains(AddressToUintMap storage map, address key) internal view returns (bool) {
    return contains(map._inner, bytes32(uint256(uint160(key))));
  }

  /**
   * @dev Returns the number of elements in the map. O(1).
     */
  function length(AddressToUintMap storage map) internal view returns (uint256) {
    return length(map._inner);
  }

  /**
   * @dev Returns the element stored at position `index` in the map. O(1).
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) {
    (bytes32 key, bytes32 value) = at(map._inner, index);
    return (address(uint160(uint256(key))), uint256(value));
  }

  /**
   * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
  function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) {
    (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key))));
    return (success, uint256(value));
  }

  /**
   * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
  function get(AddressToUintMap storage map, address key) internal view returns (uint256) {
    return uint256(get(map._inner, bytes32(uint256(uint160(key)))));
  }

  /**
   * @dev Return the an array containing all the keys
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function keys(AddressToUintMap storage map) internal view returns (address[] memory) {
    bytes32[] memory store = keys(map._inner);
    address[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }

  // Bytes32ToUintMap

  struct Bytes32ToUintMap {
    Bytes32ToBytes32Map _inner;
  }

  /**
   * @dev Adds a key-value pair to a map, or updates the value for an existing
     * key. O(1).
     *
     * Returns true if the key was added to the map, that is if it was not
     * already present.
     */
  function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) {
    return set(map._inner, key, bytes32(value));
  }

  /**
   * @dev Removes a value from a map. O(1).
     *
     * Returns true if the key was removed from the map, that is if it was present.
     */
  function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) {
    return remove(map._inner, key);
  }

  /**
   * @dev Returns true if the key is in the map. O(1).
     */
  function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) {
    return contains(map._inner, key);
  }

  /**
   * @dev Returns the number of elements in the map. O(1).
     */
  function length(Bytes32ToUintMap storage map) internal view returns (uint256) {
    return length(map._inner);
  }

  /**
   * @dev Returns the element stored at position `index` in the map. O(1).
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) {
    (bytes32 key, bytes32 value) = at(map._inner, index);
    return (key, uint256(value));
  }

  /**
   * @dev Tries to returns the value associated with `key`. O(1).
     * Does not revert if `key` is not in the map.
     */
  function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) {
    (bool success, bytes32 value) = tryGet(map._inner, key);
    return (success, uint256(value));
  }

  /**
   * @dev Returns the value associated with `key`. O(1).
     *
     * Requirements:
     *
     * - `key` must be in the map.
     */
  function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) {
    return uint256(get(map._inner, key));
  }

  /**
   * @dev Return the an array containing all the keys
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) {
    bytes32[] memory store = keys(map._inner);
    bytes32[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }
}

File 20 of 24 : EnumerableSet.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
  // To implement this library for multiple types with as little code
  // repetition as possible, we write it in terms of a generic Set type with
  // bytes32 values.
  // The Set implementation uses private functions, and user-facing
  // implementations (such as AddressSet) are just wrappers around the
  // underlying Set.
  // This means that we can only create new EnumerableSets for types that fit
  // in bytes32.

  struct Set {
    // Storage of set values
    bytes32[] _values;
    // Position is the index of the value in the `values` array plus 1.
    // Position 0 is used to mean a value is not in the set.
    mapping(bytes32 value => uint256) _positions;
  }

  /**
   * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
  function _add(Set storage set, bytes32 value) private returns (bool) {
    if (!_contains(set, value)) {
      set._values.push(value);
      // The value is stored at length-1, but we add 1 to all indexes
      // and use 0 as a sentinel value
      set._positions[value] = set._values.length;
      return true;
    } else {
      return false;
    }
  }

  /**
   * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
  function _remove(Set storage set, bytes32 value) private returns (bool) {
    // We cache the value's position to prevent multiple reads from the same storage slot
    uint256 position = set._positions[value];

    if (position != 0) {
      // Equivalent to contains(set, value)
      // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
      // the array, and then remove the last element (sometimes called as 'swap and pop').
      // This modifies the order of the array, as noted in {at}.

      uint256 valueIndex = position - 1;
      uint256 lastIndex = set._values.length - 1;

      if (valueIndex != lastIndex) {
        bytes32 lastValue = set._values[lastIndex];

        // Move the lastValue to the index where the value to delete is
        set._values[valueIndex] = lastValue;
        // Update the tracked position of the lastValue (that was just moved)
        set._positions[lastValue] = position;
      }

      // Delete the slot where the moved value was stored
      set._values.pop();

      // Delete the tracked position for the deleted slot
      delete set._positions[value];

      return true;
    } else {
      return false;
    }
  }

  /**
   * @dev Returns true if the value is in the set. O(1).
     */
  function _contains(Set storage set, bytes32 value) private view returns (bool) {
    return set._positions[value] != 0;
  }

  /**
   * @dev Returns the number of values on the set. O(1).
     */
  function _length(Set storage set) private view returns (uint256) {
    return set._values.length;
  }

  /**
   * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function _at(Set storage set, uint256 index) private view returns (bytes32) {
    return set._values[index];
  }

  /**
   * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function _values(Set storage set) private view returns (bytes32[] memory) {
    return set._values;
  }

  // Bytes32Set

  struct Bytes32Set {
    Set _inner;
  }

  /**
   * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
  function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
    return _add(set._inner, value);
  }

  /**
   * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
  function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
    return _remove(set._inner, value);
  }

  /**
   * @dev Returns true if the value is in the set. O(1).
     */
  function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
    return _contains(set._inner, value);
  }

  /**
   * @dev Returns the number of values in the set. O(1).
     */
  function length(Bytes32Set storage set) internal view returns (uint256) {
    return _length(set._inner);
  }

  /**
   * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
    return _at(set._inner, index);
  }

  /**
   * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
    bytes32[] memory store = _values(set._inner);
    bytes32[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }

  // AddressSet

  struct AddressSet {
    Set _inner;
  }

  /**
   * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
  function add(AddressSet storage set, address value) internal returns (bool) {
    return _add(set._inner, bytes32(uint256(uint160(value))));
  }

  /**
   * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
  function remove(AddressSet storage set, address value) internal returns (bool) {
    return _remove(set._inner, bytes32(uint256(uint160(value))));
  }

  /**
   * @dev Returns true if the value is in the set. O(1).
     */
  function contains(AddressSet storage set, address value) internal view returns (bool) {
    return _contains(set._inner, bytes32(uint256(uint160(value))));
  }

  /**
   * @dev Returns the number of values in the set. O(1).
     */
  function length(AddressSet storage set) internal view returns (uint256) {
    return _length(set._inner);
  }

  /**
   * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(AddressSet storage set, uint256 index) internal view returns (address) {
    return address(uint160(uint256(_at(set._inner, index))));
  }

  /**
   * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function values(AddressSet storage set) internal view returns (address[] memory) {
    bytes32[] memory store = _values(set._inner);
    address[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }

  // UintSet

  struct UintSet {
    Set _inner;
  }

  /**
   * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
  function add(UintSet storage set, uint256 value) internal returns (bool) {
    return _add(set._inner, bytes32(value));
  }

  /**
   * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
  function remove(UintSet storage set, uint256 value) internal returns (bool) {
    return _remove(set._inner, bytes32(value));
  }

  /**
   * @dev Returns true if the value is in the set. O(1).
     */
  function contains(UintSet storage set, uint256 value) internal view returns (bool) {
    return _contains(set._inner, bytes32(value));
  }

  /**
   * @dev Returns the number of values in the set. O(1).
     */
  function length(UintSet storage set) internal view returns (uint256) {
    return _length(set._inner);
  }

  /**
   * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
  function at(UintSet storage set, uint256 index) internal view returns (uint256) {
    return uint256(_at(set._inner, index));
  }

  /**
   * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
  function values(UintSet storage set) internal view returns (uint256[] memory) {
    bytes32[] memory store = _values(set._inner);
    uint256[] memory result;

    /// @solidity memory-safe-assembly
    assembly {
      result := store
    }

    return result;
  }
}

File 21 of 24 : Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
  /**
   * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
  struct InitializableStorage {
    /**
     * @dev Indicates that the contract has been initialized.
         */
    uint64 _initialized;
    /**
     * @dev Indicates that the contract is in the process of being initialized.
         */
    bool _initializing;
  }

  // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
  bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

  /**
   * @dev The contract is already initialized.
     */
  error InvalidInitialization();

  /**
   * @dev The contract is not initializing.
     */
  error NotInitializing();

  /**
   * @dev Triggered when the contract has been initialized or reinitialized.
     */
  event Initialized(uint64 version);

  /**
   * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
  modifier initializer() {
    // solhint-disable-next-line var-name-mixedcase
    InitializableStorage storage $ = _getInitializableStorage();

    // Cache values to avoid duplicated sloads
    bool isTopLevelCall = !$._initializing;
    uint64 initialized = $._initialized;

    // Allowed calls:
    // - initialSetup: the contract is not in the initializing state and no previous version was
    //                 initialized
    // - construction: the contract is initialized at version 1 (no reininitialization) and the
    //                 current contract is just being deployed
    bool initialSetup = initialized == 0 && isTopLevelCall;
    bool construction = initialized == 1 && address(this).code.length == 0;

    if (!initialSetup && !construction) {
      revert InvalidInitialization();
    }
    $._initialized = 1;
    if (isTopLevelCall) {
      $._initializing = true;
    }
    _;
    if (isTopLevelCall) {
      $._initializing = false;
      emit Initialized(1);
    }
  }

  /**
   * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
  modifier reinitializer(uint64 version) {
    // solhint-disable-next-line var-name-mixedcase
    InitializableStorage storage $ = _getInitializableStorage();

    if ($._initializing || $._initialized >= version) {
      revert InvalidInitialization();
    }
    $._initialized = version;
    $._initializing = true;
    _;
    $._initializing = false;
    emit Initialized(version);
  }

  /**
   * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
  modifier onlyInitializing() {
    _checkInitializing();
    _;
  }

  /**
   * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
  function _checkInitializing() internal view virtual {
    if (!_isInitializing()) {
      revert NotInitializing();
    }
  }

  /**
   * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
  function _disableInitializers() internal virtual {
    // solhint-disable-next-line var-name-mixedcase
    InitializableStorage storage $ = _getInitializableStorage();

    if ($._initializing) {
      revert InvalidInitialization();
    }
    if ($._initialized != type(uint64).max) {
      $._initialized = type(uint64).max;
      emit Initialized(type(uint64).max);
    }
  }

  /**
   * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
  function _getInitializedVersion() internal view returns (uint64) {
    return _getInitializableStorage()._initialized;
  }

  /**
   * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
  function _isInitializing() internal view returns (bool) {
    return _getInitializableStorage()._initializing;
  }

  /**
   * @dev Returns a pointer to the storage namespace.
     */
  // solhint-disable-next-line var-name-mixedcase
  function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
    assembly {
      $.slot := INITIALIZABLE_STORAGE
    }
  }
}

File 22 of 24 : Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {

  /**
    * @dev Muldiv operation overflow.
   */
  error MathOverflowedMulDiv();

  enum Rounding {
    Floor, // Toward negative infinity
    Ceil, // Toward positive infinity
    Trunc, // Toward zero
    Expand // Away from zero
  }

  /**
   * @dev Returns the addition of two unsigned integers, with an success flag (no overflow).
     */
  function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    unchecked {
      uint256 c = a + b;
      if (c < a) return (false, 0);
      return (true, c);
    }
  }

  /**
   * @dev Returns the subtraction of two unsigned integers, with an success flag (no overflow).
     */
  function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    unchecked {
      if (b > a) return (false, 0);
      return (true, a - b);
    }
  }

  /**
   * @dev Returns the multiplication of two unsigned integers, with an success flag (no overflow).
     */
  function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    unchecked {
    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
    // benefit is lost if 'b' is also tested.
    // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
      if (a == 0) return (true, 0);
      uint256 c = a * b;
      if (c / a != b) return (false, 0);
      return (true, c);
    }
  }

  /**
   * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
     */
  function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    unchecked {
      if (b == 0) return (false, 0);
      return (true, a / b);
    }
  }

  /**
   * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
     */
  function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    unchecked {
      if (b == 0) return (false, 0);
      return (true, a % b);
    }
  }

  /**
   * @dev Returns the largest of two numbers.
     */
  function max(uint256 a, uint256 b) internal pure returns (uint256) {
    return a > b ? a : b;
  }

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

  /**
   * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
  function average(uint256 a, uint256 b) internal pure returns (uint256) {
    // (a + b) / 2 can overflow.
    return (a & b) + (a ^ b) / 2;
  }

  /**
   * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
  function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
    if (b == 0) {
      // Guarantee the same behavior as in a regular Solidity division.
      return a / b;
    }

    // The following calculation ensures accurate ceiling division without overflow.
    // Since a is non-zero, (a - 1) / b will not overflow.
    // The largest possible result occurs when (a - 1) / b is type(uint256).max,
    // but the largest value we can obtain is type(uint256).max - 1, which happens
    // when a = type(uint256).max and b = 1.
    unchecked {
      return a == 0 ? 0 : (a - 1) / b + 1;
    }
  }

  /**
   * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
  function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
    unchecked {
    // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
    // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
    // variables such that product = prod1 * 2^256 + prod0.
      uint256 prod0 = x * y; // Least significant 256 bits of the product
      uint256 prod1; // Most significant 256 bits of the product
      assembly {
        let mm := mulmod(x, y, not(0))
        prod1 := sub(sub(mm, prod0), lt(mm, prod0))
      }

    // Handle non-overflow cases, 256 by 256 division.
      if (prod1 == 0) {
        // Solidity will revert if denominator == 0, unlike the div opcode on its own.
        // The surrounding unchecked block does not change this fact.
        // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
        return prod0 / denominator;
      }

    // Make sure the result is less than 2^256. Also prevents denominator == 0.
      if (denominator <= prod1) {
        revert MathOverflowedMulDiv();
      }

    ///////////////////////////////////////////////
    // 512 by 256 division.
    ///////////////////////////////////////////////

    // Make division exact by subtracting the remainder from [prod1 prod0].
      uint256 remainder;
      assembly {
      // Compute remainder using mulmod.
        remainder := mulmod(x, y, denominator)

      // Subtract 256 bit number from 512 bit number.
        prod1 := sub(prod1, gt(remainder, prod0))
        prod0 := sub(prod0, remainder)
      }

    // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
    // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

      uint256 twos = denominator & (0 - denominator);
      assembly {
      // Divide denominator by twos.
        denominator := div(denominator, twos)

      // Divide [prod1 prod0] by twos.
        prod0 := div(prod0, twos)

      // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
        twos := add(div(sub(0, twos), twos), 1)
      }

    // Shift in bits from prod1 into prod0.
      prod0 |= prod1 * twos;

    // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
    // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
    // four bits. That is, denominator * inv = 1 mod 2^4.
      uint256 inverse = (3 * denominator) ^ 2;

    // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
    // works in modular arithmetic, doubling the correct bits in each step.
      inverse *= 2 - denominator * inverse; // inverse mod 2^8
      inverse *= 2 - denominator * inverse; // inverse mod 2^16
      inverse *= 2 - denominator * inverse; // inverse mod 2^32
      inverse *= 2 - denominator * inverse; // inverse mod 2^64
      inverse *= 2 - denominator * inverse; // inverse mod 2^128
      inverse *= 2 - denominator * inverse; // inverse mod 2^256

    // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
    // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
    // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
    // is no longer required.
      result = prod0 * inverse;
      return result;
    }
  }

  /**
   * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
  function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
    uint256 result = mulDiv(x, y, denominator);
    if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
      result += 1;
    }
    return result;
  }

  /**
   * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
  function sqrt(uint256 a) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }

    // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
    //
    // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
    // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
    //
    // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
    // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
    // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
    //
    // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
    uint256 result = 1 << (log2(a) >> 1);

    // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
    // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
    // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
    // into the expected uint128 result.
    unchecked {
      result = (result + a / result) >> 1;
      result = (result + a / result) >> 1;
      result = (result + a / result) >> 1;
      result = (result + a / result) >> 1;
      result = (result + a / result) >> 1;
      result = (result + a / result) >> 1;
      result = (result + a / result) >> 1;
      return min(result, a / result);
    }
  }

  /**
   * @notice Calculates sqrt(a), following the selected rounding direction.
     */
  function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
    unchecked {
      uint256 result = sqrt(a);
      return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
    }
  }

  /**
   * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
  function log2(uint256 value) internal pure returns (uint256) {
    uint256 result = 0;
    unchecked {
      if (value >> 128 > 0) {
        value >>= 128;
        result += 128;
      }
      if (value >> 64 > 0) {
        value >>= 64;
        result += 64;
      }
      if (value >> 32 > 0) {
        value >>= 32;
        result += 32;
      }
      if (value >> 16 > 0) {
        value >>= 16;
        result += 16;
      }
      if (value >> 8 > 0) {
        value >>= 8;
        result += 8;
      }
      if (value >> 4 > 0) {
        value >>= 4;
        result += 4;
      }
      if (value >> 2 > 0) {
        value >>= 2;
        result += 2;
      }
      if (value >> 1 > 0) {
        result += 1;
      }
    }
    return result;
  }

  /**
   * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
  function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
    unchecked {
      uint256 result = log2(value);
      return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
    }
  }

  /**
   * @dev Return the log in base 10 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
  function log10(uint256 value) internal pure returns (uint256) {
    uint256 result = 0;
    unchecked {
      if (value >= 10 ** 64) {
        value /= 10 ** 64;
        result += 64;
      }
      if (value >= 10 ** 32) {
        value /= 10 ** 32;
        result += 32;
      }
      if (value >= 10 ** 16) {
        value /= 10 ** 16;
        result += 16;
      }
      if (value >= 10 ** 8) {
        value /= 10 ** 8;
        result += 8;
      }
      if (value >= 10 ** 4) {
        value /= 10 ** 4;
        result += 4;
      }
      if (value >= 10 ** 2) {
        value /= 10 ** 2;
        result += 2;
      }
      if (value >= 10 ** 1) {
        result += 1;
      }
    }
    return result;
  }

  /**
   * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
  function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
    unchecked {
      uint256 result = log10(value);
      return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
    }
  }

  /**
   * @dev Return the log in base 256 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
  function log256(uint256 value) internal pure returns (uint256) {
    uint256 result = 0;
    unchecked {
      if (value >> 128 > 0) {
        value >>= 128;
        result += 16;
      }
      if (value >> 64 > 0) {
        value >>= 64;
        result += 8;
      }
      if (value >> 32 > 0) {
        value >>= 32;
        result += 4;
      }
      if (value >> 16 > 0) {
        value >>= 16;
        result += 2;
      }
      if (value >> 8 > 0) {
        result += 1;
      }
    }
    return result;
  }

  /**
   * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
  function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
    unchecked {
      uint256 result = log256(value);
      return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
    }
  }

  /**
   * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
  function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
    return uint8(rounding) % 2 == 1;
  }

}

File 23 of 24 : Controllable.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "../openzeppelin/Initializable.sol";
import "../interfaces/IControllable.sol";
import "../interfaces/IController.sol";
import "../lib/SlotsLib.sol";

/// @title Implement basic functionality for any contract that require strict control
/// @dev Can be used with upgradeable pattern.
///      Require call __Controllable_init() in any case.
/// @author belbix
abstract contract Controllable is Initializable, IControllable {
  using SlotsLib for bytes32;

  /// @notice Version of the contract
  /// @dev Should be incremented when contract changed
  string public constant CONTROLLABLE_VERSION = "1.0.0";

  bytes32 internal constant _CONTROLLER_SLOT = bytes32(uint256(keccak256("eip1967.controllable.controller")) - 1);
  bytes32 internal constant _CREATED_SLOT = bytes32(uint256(keccak256("eip1967.controllable.created")) - 1);
  bytes32 internal constant _CREATED_BLOCK_SLOT = bytes32(uint256(keccak256("eip1967.controllable.created_block")) - 1);
  bytes32 internal constant _REVISION_SLOT = bytes32(uint256(keccak256("eip1967.controllable.revision")) - 1);
  bytes32 internal constant _PREVIOUS_LOGIC_SLOT = bytes32(uint256(keccak256("eip1967.controllable.prev_logic")) - 1);

  event ContractInitialized(address controller, uint ts, uint block);
  event RevisionIncreased(uint value, address oldLogic);

  // init implementation contract
  constructor() initializer {}

  /// @notice Initialize contract after setup it as proxy implementation
  ///         Save block.timestamp in the "created" variable
  /// @dev Use it only once after first logic setup
  /// @param controller_ Controller address
  function __Controllable_init(address controller_) internal onlyInitializing {
    _init(controller_);
  }

  function _init(address controller_) private {
    require(controller_ != address(0), "Zero controller");
    _CONTROLLER_SLOT.set(controller_);
    _CREATED_SLOT.set(block.timestamp);
    _CREATED_BLOCK_SLOT.set(block.number);
    emit ContractInitialized(controller_, block.timestamp, block.number);
  }

  /// @dev Return true if given address is controller
  function isController(address value_) public override view returns (bool) {
    return value_ == controller();
  }

  /// @notice Return true if given address is setup as governance in Controller
  function isGovernance(address value_) public override view returns (bool) {
    return IController(controller()).governance() == value_;
  }

  /// @dev Contract upgrade counter
  function revision() external view override returns (uint) {
    return _REVISION_SLOT.getUint();
  }

  /// @dev Previous logic implementation
  function previousImplementation() external view override returns (address) {
    return _PREVIOUS_LOGIC_SLOT.getAddress();
  }

  // ************* SETTERS/GETTERS *******************

  /// @notice Return controller address saved in the contract slot
  function controller() public view override returns (address) {
    return _CONTROLLER_SLOT.getAddress();
  }

  /// @notice Return creation timestamp
  /// @return Creation timestamp
  function created() external view override returns (uint256) {
    return _CREATED_SLOT.getUint();
  }

  /// @notice Return creation block number
  /// @return Creation block number
  function createdBlock() external override view returns (uint256) {
    return _CREATED_BLOCK_SLOT.getUint();
  }

  /// @dev Revision should be increased on each contract upgrade
  function increaseRevision(address oldLogic) external override {
    require(msg.sender == address(this), "Increase revision forbidden");
    uint r = _REVISION_SLOT.getUint() + 1;
    _REVISION_SLOT.set(r);
    _PREVIOUS_LOGIC_SLOT.set(oldLogic);
    emit RevisionIncreased(r, oldLogic);
  }

}

File 24 of 24 : LibPRNG.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Library for generating pseudorandom numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibPRNG.sol)
/// @author LazyShuffler based on NextShuffler by aschlosberg (divergencearran)
/// (https://github.com/divergencetech/ethier/blob/main/contracts/random/NextShuffler.sol)
library LibPRNG {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The initial length must be greater than zero and less than `2**32 - 1`.
    error InvalidInitialLazyShufflerLength();

    /// @dev The new length must not be less than the current length.
    error InvalidNewLazyShufflerLength();

    /// @dev The lazy shuffler has not been initialized.
    error LazyShufflerNotInitialized();

    /// @dev Cannot double initialize the lazy shuffler.
    error LazyShufflerAlreadyInitialized();

    /// @dev The lazy shuffle has finished.
    error LazyShuffleFinished();

    /// @dev The queried index is out of bounds.
    error LazyShufflerGetOutOfBounds();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The scalar of ETH and most ERC20s.
    uint256 internal constant WAD = 1e18;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STRUCTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev A pseudorandom number state in memory.
    struct PRNG {
        uint256 state;
    }

    /// @dev A lazy Fisher-Yates shuffler for a range `[0..n)` in storage.
    struct LazyShuffler {
        // Bits Layout:
        // - [0..31]    `numShuffled`
        // - [32..223]  `permutationSlot`
        // - [224..255] `length`
        uint256 _state;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         OPERATIONS                         */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Seeds the `prng` with `state`.
    function seed(PRNG memory prng, uint256 state) internal pure {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(prng, state)
        }
    }

    /// @dev Returns the next pseudorandom uint256.
    /// All bits of the returned uint256 pass the NIST Statistical Test Suite.
    function next(PRNG memory prng) internal pure returns (uint256 result) {
        // We simply use `keccak256` for a great balance between
        // runtime gas costs, bytecode size, and statistical properties.
        //
        // A high-quality LCG with a 32-byte state
        // is only about 30% more gas efficient during runtime,
        // but requires a 32-byte multiplier, which can cause bytecode bloat
        // when this function is inlined.
        //
        // Using this method is about 2x more efficient than
        // `nextRandomness = uint256(keccak256(abi.encode(randomness)))`.
        /// @solidity memory-safe-assembly
        assembly {
            result := keccak256(prng, 0x20)
            mstore(prng, result)
        }
    }

    /// @dev Returns a pseudorandom uint256, uniformly distributed
    /// between 0 (inclusive) and `upper` (exclusive).
    /// If your modulus is big, this method is recommended
    /// for uniform sampling to avoid modulo bias.
    /// For uniform sampling across all uint256 values,
    /// or for small enough moduli such that the bias is neligible,
    /// use {next} instead.
    function uniform(PRNG memory prng, uint256 upper) internal pure returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            for {} 1 {} {
                result := keccak256(prng, 0x20)
                mstore(prng, result)
                if iszero(lt(result, mod(sub(0, upper), upper))) { break }
            }
            result := mod(result, upper)
        }
    }

    /// @dev Shuffles the array in-place with Fisher-Yates shuffle.
    function shuffle(PRNG memory prng, uint256[] memory a) internal pure {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(a)
            let w := not(0)
            let mask := shr(128, w)
            if n {
                for { a := add(a, 0x20) } 1 {} {
                    // We can just directly use `keccak256`, cuz
                    // the other approaches don't save much.
                    let r := keccak256(prng, 0x20)
                    mstore(prng, r)

                    // Note that there will be a very tiny modulo bias
                    // if the length of the array is not a power of 2.
                    // For all practical purposes, it is negligible
                    // and will not be a fairness or security concern.
                    {
                        let j := add(a, shl(5, mod(shr(128, r), n)))
                        n := add(n, w) // `sub(n, 1)`.
                        if iszero(n) { break }

                        let i := add(a, shl(5, n))
                        let t := mload(i)
                        mstore(i, mload(j))
                        mstore(j, t)
                    }

                    {
                        let j := add(a, shl(5, mod(and(r, mask), n)))
                        n := add(n, w) // `sub(n, 1)`.
                        if iszero(n) { break }

                        let i := add(a, shl(5, n))
                        let t := mload(i)
                        mstore(i, mload(j))
                        mstore(j, t)
                    }
                }
            }
        }
    }

    /// @dev Shuffles the bytes in-place with Fisher-Yates shuffle.
    function shuffle(PRNG memory prng, bytes memory a) internal pure {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(a)
            let w := not(0)
            let mask := shr(128, w)
            if n {
                let b := add(a, 0x01)
                for { a := add(a, 0x20) } 1 {} {
                    // We can just directly use `keccak256`, cuz
                    // the other approaches don't save much.
                    let r := keccak256(prng, 0x20)
                    mstore(prng, r)

                    // Note that there will be a very tiny modulo bias
                    // if the length of the array is not a power of 2.
                    // For all practical purposes, it is negligible
                    // and will not be a fairness or security concern.
                    {
                        let o := mod(shr(128, r), n)
                        n := add(n, w) // `sub(n, 1)`.
                        if iszero(n) { break }

                        let t := mload(add(b, n))
                        mstore8(add(a, n), mload(add(b, o)))
                        mstore8(add(a, o), t)
                    }

                    {
                        let o := mod(and(r, mask), n)
                        n := add(n, w) // `sub(n, 1)`.
                        if iszero(n) { break }

                        let t := mload(add(b, n))
                        mstore8(add(a, n), mload(add(b, o)))
                        mstore8(add(a, o), t)
                    }
                }
            }
        }
    }

    /// @dev Returns a sample from the standard normal distribution denominated in `WAD`.
    function standardNormalWad(PRNG memory prng) internal pure returns (int256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            // Technically, this is the Irwin-Hall distribution with 20 samples.
            // The chance of drawing a sample outside 10 σ from the standard normal distribution
            // is ≈ 0.000000000000000000000015, which is insignificant for most practical purposes.
            // Passes the Kolmogorov-Smirnov test for 200k samples. Uses about 322 gas.
            result := keccak256(prng, 0x20)
            mstore(prng, result)
            let n := 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43 // Prime.
            let a := 0x100000000000000000000000000000051 // Prime and a primitive root of `n`.
            let m := 0x1fffffffffffffff1fffffffffffffff1fffffffffffffff1fffffffffffffff
            let s := 0x1000000000000000100000000000000010000000000000001
            let r1 := mulmod(result, a, n)
            let r2 := mulmod(r1, a, n)
            let r3 := mulmod(r2, a, n)
            // forgefmt: disable-next-item
            result := sub(sar(96, mul(26614938895861601847173011183,
                add(add(shr(192, mul(s, add(and(m, result), and(m, r1)))),
                shr(192, mul(s, add(and(m, r2), and(m, r3))))),
                shr(192, mul(s, and(m, mulmod(r3, a, n))))))), 7745966692414833770)
        }
    }

    /// @dev Returns a sample from the unit exponential distribution denominated in `WAD`.
    function exponentialWad(PRNG memory prng) internal pure returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            // Passes the Kolmogorov-Smirnov test for 200k samples.
            // Gas usage varies, starting from about 172+ gas.
            let r := keccak256(prng, 0x20)
            mstore(prng, r)
            let p := shl(129, r)
            let w := shl(1, r)
            if iszero(gt(w, p)) {
                let n := 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43 // Prime.
                let a := 0x100000000000000000000000000000051 // Prime and a primitive root of `n`.
                for {} 1 {} {
                    r := mulmod(r, a, n)
                    if iszero(lt(shl(129, r), w)) {
                        r := mulmod(r, a, n)
                        result := add(1000000000000000000, result)
                        w := shl(1, r)
                        p := shl(129, r)
                        if iszero(lt(w, p)) { break }
                        continue
                    }
                    w := shl(1, r)
                    if iszero(lt(w, shl(129, r))) { break }
                }
            }
            result := add(div(p, shl(129, 170141183460469231732)), result)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*       STORAGE-BASED RANGE LAZY SHUFFLING OPERATIONS        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Initializes the state for lazy-shuffling the range `[0..n)`.
    /// Reverts if `n == 0 || n >= 2**32 - 1`.
    /// Reverts if `$` has already been initialized.
    /// If you need to reduce the length after initialization, just use a fresh new `$`.
    function initialize(LazyShuffler storage $, uint256 n) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(lt(sub(n, 1), 0xfffffffe)) {
                mstore(0x00, 0x83b53941) // `InvalidInitialLazyShufflerLength()`.
                revert(0x1c, 0x04)
            }
            if sload($.slot) {
                mstore(0x00, 0x0c9f11f2) // `LazyShufflerAlreadyInitialized()`.
                revert(0x1c, 0x04)
            }
            mstore(0x00, $.slot)
            sstore($.slot, or(shl(224, n), shl(32, shr(64, keccak256(0x00, 0x20)))))
        }
    }

    /// @dev Increases the length of `$`.
    /// Reverts if `$` has not been initialized.
    function grow(LazyShuffler storage $, uint256 n) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let state := sload($.slot) // The packed value at `$`.
            // If the new length is smaller than the old length, revert.
            if lt(n, shr(224, state)) {
                mstore(0x00, 0xbed37c6e) // `InvalidNewLazyShufflerLength()`.
                revert(0x1c, 0x04)
            }
            if iszero(state) {
                mstore(0x00, 0x1ead2566) // `LazyShufflerNotInitialized()`.
                revert(0x1c, 0x04)
            }
            sstore($.slot, or(shl(224, n), shr(32, shl(32, state))))
        }
    }

    /// @dev Restarts the shuffler by setting `numShuffled` to zero,
    /// such that all elements can be drawn again.
    /// Restarting does NOT clear the internal permutation, nor changes the length.
    /// Even with the same sequence of randomness, reshuffling can yield different results.
    function restart(LazyShuffler storage $) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let state := sload($.slot)
            if iszero(state) {
                mstore(0x00, 0x1ead2566) // `LazyShufflerNotInitialized()`.
                revert(0x1c, 0x04)
            }
            sstore($.slot, shl(32, shr(32, state)))
        }
    }

    /// @dev Returns the number of elements that have been shuffled.
    function numShuffled(LazyShuffler storage $) internal view returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := and(0xffffffff, sload($.slot))
        }
    }

    /// @dev Returns the length of `$`.
    /// Returns zero if `$` is not initialized, else a non-zero value less than `2**32 - 1`.
    function length(LazyShuffler storage $) internal view returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := shr(224, sload($.slot))
        }
    }

    /// @dev Returns if `$` has been initialized.
    function initialized(LazyShuffler storage $) internal view returns (bool result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := iszero(iszero(sload($.slot)))
        }
    }

    /// @dev Returns if there are any more elements left to shuffle.
    /// Reverts if `$` is not initialized.
    function finished(LazyShuffler storage $) internal view returns (bool result) {
        /// @solidity memory-safe-assembly
        assembly {
            let state := sload($.slot) // The packed value at `$`.
            if iszero(state) {
                mstore(0x00, 0x1ead2566) // `LazyShufflerNotInitialized()`.
                revert(0x1c, 0x04)
            }
            result := eq(shr(224, state), and(0xffffffff, state))
        }
    }

    /// @dev Returns the current value stored at `index`, accounting for all historical shuffling.
    /// Reverts if `index` is greater than or equal to the `length` of `$`.
    function get(LazyShuffler storage $, uint256 index) internal view returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            let state := sload($.slot) // The packed value at `$`.
            let n := shr(224, state) // Length of `$`.
            if iszero(lt(index, n)) {
                mstore(0x00, 0x61367cc4) // `LazyShufflerGetOutOfBounds()`.
                revert(0x1c, 0x04)
            }
            let u32 := gt(n, 0xfffe)
            let s := add(shr(sub(4, u32), index), shr(64, shl(32, state))) // Bucket slot.
            let o := shl(add(4, u32), and(index, shr(u32, 15))) // Bucket slot offset (bits).
            let m := sub(shl(shl(u32, 16), 1), 1) // Value mask.
            result := and(m, shr(o, sload(s)))
            result := xor(index, mul(xor(index, sub(result, 1)), iszero(iszero(result))))
        }
    }

    /// @dev Does a single Fisher-Yates shuffle step, increments the `numShuffled` in `$`,
    /// and returns the next value in the shuffled range.
    /// `randomness` can be taken from a good-enough source, or a higher quality source like VRF.
    /// Reverts if there are no more values to shuffle, which includes the case if `$` is not initialized.
    function next(LazyShuffler storage $, uint256 randomness) internal returns (uint256 chosen) {
        /// @solidity memory-safe-assembly
        assembly {
            function _get(u32_, state_, i_) -> _value {
                let s_ := add(shr(sub(4, u32_), i_), shr(64, shl(32, state_))) // Bucket slot.
                let o_ := shl(add(4, u32_), and(i_, shr(u32_, 15))) // Bucket slot offset (bits).
                let m_ := sub(shl(shl(u32_, 16), 1), 1) // Value mask.
                _value := and(m_, shr(o_, sload(s_)))
                _value := xor(i_, mul(xor(i_, sub(_value, 1)), iszero(iszero(_value))))
            }
            function _set(u32_, state_, i_, value_) {
                let s_ := add(shr(sub(4, u32_), i_), shr(64, shl(32, state_))) // Bucket slot.
                let o_ := shl(add(4, u32_), and(i_, shr(u32_, 15))) // Bucket slot offset (bits).
                let m_ := sub(shl(shl(u32_, 16), 1), 1) // Value mask.
                let v_ := sload(s_) // Bucket slot value.
                value_ := mul(iszero(eq(i_, value_)), add(value_, 1))
                sstore(s_, xor(v_, shl(o_, and(m_, xor(shr(o_, v_), value_)))))
            }
            let state := sload($.slot) // The packed value at `$`.
            let shuffled := and(0xffffffff, state) // Number of elements shuffled.
            let n := shr(224, state) // Length of `$`.
            let remainder := sub(n, shuffled) // Number of elements left to shuffle.
            if iszero(remainder) {
                mstore(0x00, 0x51065f79) // `LazyShuffleFinished()`.
                revert(0x1c, 0x04)
            }
            mstore(0x00, randomness) // (Re)hash the randomness so that we don't
            mstore(0x20, shuffled) // need to expect guarantees on its distribution.
            let index := add(mod(keccak256(0x00, 0x40), remainder), shuffled)
            chosen := _get(gt(n, 0xfffe), state, index)
            _set(gt(n, 0xfffe), state, index, _get(gt(n, 0xfffe), state, shuffled))
            _set(gt(n, 0xfffe), state, shuffled, chosen)
            sstore($.slot, add(1, state)) // Increment the `numShuffled` by 1, and store it.
        }
    }
}

Settings
{
  "evmVersion": "istanbul",
  "optimizer": {
    "enabled": true,
    "runs": 50
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "metadata": {
    "useLiteralContent": true
  },
  "libraries": {
    "contracts/lib/StatControllerLib.sol": {
      "StatControllerLib": "0x35b9e14df4879649d0513597d587e7f79bceac70"
    }
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[],"name":"ErrorCannotRemoveItemFromMap","type":"error"},{"inputs":[{"internalType":"address","name":"item","type":"address"}],"name":"ErrorConsumableItemIsUsed","type":"error"},{"inputs":[],"name":"ErrorExperienceMustNotDecrease","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ErrorForbidden","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemType","type":"uint256"},{"internalType":"uint8","name":"itemSlot","type":"uint8"}],"name":"ErrorItemNotEligibleForTheSlot","type":"error"},{"inputs":[],"name":"ErrorItemNotInSlot","type":"error"},{"inputs":[],"name":"ErrorItemSlotBusy","type":"error"},{"inputs":[{"internalType":"uint8","name":"slot","type":"uint8"}],"name":"ErrorItemSlotBusyHand","type":"error"},{"inputs":[],"name":"ErrorMaxLevel","type":"error"},{"inputs":[],"name":"ErrorNotEnoughExperience","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ErrorNotHeroController","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ErrorNotItemController","type":"error"},{"inputs":[],"name":"ErrorWrongLevelUpSum","type":"error"},{"inputs":[],"name":"ErrorZeroKarmaNotAllowed","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint256","name":"length","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"TooHighValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"TooLowX","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"UnexpectedValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"heroClass","type":"uint256"}],"name":"UnknownHeroClass","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"heroToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"add","type":"bool"},{"indexed":false,"internalType":"bool","name":"temporally","type":"bool"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"BonusAttributesChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"heroToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"item","type":"address"}],"name":"ConsumableUsed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"controller","type":"address"},{"indexed":false,"internalType":"uint256","name":"ts","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"block","type":"uint256"}],"name":"ContractInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"heroToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"components":[{"internalType":"uint32","name":"level","type":"uint32"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"uint32","name":"life","type":"uint32"},{"internalType":"uint32","name":"mana","type":"uint32"},{"internalType":"uint32","name":"lifeChances","type":"uint32"}],"indexed":false,"internalType":"struct IStatController.ChangeableStats","name":"change","type":"tuple"},{"indexed":false,"internalType":"bool","name":"increase","type":"bool"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"CurrentStatsChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"index","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"GlobalCustomDataChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"index","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"ngLevel","type":"uint8"}],"name":"HeroCustomDataChangedNg","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"heroToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemType","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemSlot","type":"uint256"},{"indexed":false,"internalType":"address","name":"itemToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"itemTokenId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"equip","type":"bool"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"HeroItemSlotChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"heroToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"heroClass","type":"uint256"},{"components":[{"internalType":"int32","name":"strength","type":"int32"},{"internalType":"int32","name":"dexterity","type":"int32"},{"internalType":"int32","name":"vitality","type":"int32"},{"internalType":"int32","name":"energy","type":"int32"}],"indexed":false,"internalType":"struct IStatController.CoreAttributes","name":"change","type":"tuple"}],"name":"LevelUp","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"heroToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"item","type":"address"}],"name":"RemoveConsumableUsage","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"address","name":"oldLogic","type":"address"}],"name":"RevisionIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"heroToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"TemporallyAttributesCleared","type":"event"},{"inputs":[],"name":"CONTROLLABLE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"uint32","name":"heroLevel","type":"uint32"},{"internalType":"address[]","name":"buffTokens","type":"address[]"},{"internalType":"uint256[]","name":"buffTokenIds","type":"uint256[]"}],"internalType":"struct IStatController.BuffInfo","name":"info","type":"tuple"}],"name":"buffHero","outputs":[{"internalType":"int32[]","name":"","type":"int32[]"},{"internalType":"int32","name":"","type":"int32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"int32[]","name":"changeAttributes","type":"int32[]"},{"internalType":"bool","name":"add","type":"bool"},{"internalType":"bool","name":"temporally","type":"bool"}],"internalType":"struct IStatController.ChangeAttributesInfo","name":"info","type":"tuple"}],"name":"changeBonusAttributes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"components":[{"internalType":"uint32","name":"level","type":"uint32"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"uint32","name":"life","type":"uint32"},{"internalType":"uint32","name":"mana","type":"uint32"},{"internalType":"uint32","name":"lifeChances","type":"uint32"}],"internalType":"struct IStatController.ChangeableStats","name":"change","type":"tuple"},{"internalType":"bool","name":"increase","type":"bool"}],"name":"changeCurrentStats","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint64","name":"heroTokenId","type":"uint64"},{"internalType":"uint256","name":"itemType","type":"uint256"},{"internalType":"uint8","name":"itemSlot","type":"uint8"},{"internalType":"address","name":"itemToken","type":"address"},{"internalType":"uint256","name":"itemTokenId","type":"uint256"},{"internalType":"bool","name":"equip","type":"bool"}],"name":"changeHeroItemSlot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"clearTemporallyAttributes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"clearUsedConsumables","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"controller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"created","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"createdBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getAllHeroCustomData","outputs":[{"internalType":"bytes32[]","name":"keys","type":"bytes32[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"index","type":"bytes32"}],"name":"globalCustomData","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"heroAttribute","outputs":[{"internalType":"int32","name":"","type":"int32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"heroAttributes","outputs":[{"internalType":"int32[]","name":"","type":"int32[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"heroAttributesLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"heroBaseAttributes","outputs":[{"components":[{"internalType":"int32","name":"strength","type":"int32"},{"internalType":"int32","name":"dexterity","type":"int32"},{"internalType":"int32","name":"vitality","type":"int32"},{"internalType":"int32","name":"energy","type":"int32"}],"internalType":"struct IStatController.CoreAttributes","name":"core","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"heroBonusAttributes","outputs":[{"internalType":"int32[]","name":"","type":"int32[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"hero","type":"address"},{"internalType":"uint256","name":"heroId","type":"uint256"},{"internalType":"bytes32","name":"index","type":"bytes32"}],"name":"heroCustomData","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"hero","type":"address"},{"internalType":"uint256","name":"heroId","type":"uint256"},{"internalType":"bytes32","name":"index","type":"bytes32"},{"internalType":"uint8","name":"ngLevel","type":"uint8"}],"name":"heroCustomDataOnNgLevel","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"heroClass","type":"uint256"}],"name":"heroInitialAttributes","outputs":[{"components":[{"components":[{"internalType":"int32","name":"strength","type":"int32"},{"internalType":"int32","name":"dexterity","type":"int32"},{"internalType":"int32","name":"vitality","type":"int32"},{"internalType":"int32","name":"energy","type":"int32"}],"internalType":"struct IStatController.CoreAttributes","name":"core","type":"tuple"},{"components":[{"internalType":"uint256","name":"minDamage","type":"uint256"},{"internalType":"uint256","name":"maxDamage","type":"uint256"},{"internalType":"uint256","name":"attackRating","type":"uint256"},{"internalType":"uint256","name":"defense","type":"uint256"},{"internalType":"uint256","name":"blockRating","type":"uint256"},{"internalType":"uint256","name":"life","type":"uint256"},{"internalType":"uint256","name":"mana","type":"uint256"}],"internalType":"struct StatLib.BaseMultiplier","name":"multiplier","type":"tuple"},{"components":[{"internalType":"uint256","name":"life","type":"uint256"},{"internalType":"uint256","name":"mana","type":"uint256"}],"internalType":"struct StatLib.LevelUp","name":"levelUp","type":"tuple"},{"internalType":"int32","name":"baseLifeChances","type":"int32"}],"internalType":"struct StatLib.InitialHero","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint64","name":"heroTokenId","type":"uint64"},{"internalType":"uint8","name":"itemSlot","type":"uint8"}],"name":"heroItemSlot","outputs":[{"internalType":"bytes32","name":"nftPacked","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"heroItemSlots","outputs":[{"internalType":"uint8[]","name":"","type":"uint8[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"heroStats","outputs":[{"components":[{"internalType":"uint32","name":"level","type":"uint32"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"uint32","name":"life","type":"uint32"},{"internalType":"uint32","name":"mana","type":"uint32"},{"internalType":"uint32","name":"lifeChances","type":"uint32"}],"internalType":"struct IStatController.ChangeableStats","name":"result","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"heroTemporallyAttributes","outputs":[{"internalType":"int32[]","name":"","type":"int32[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"oldLogic","type":"address"}],"name":"increaseRevision","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"controller_","type":"address"}],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"uint256","name":"heroClass","type":"uint256"}],"name":"initNewHero","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"address","name":"item","type":"address"}],"name":"isConsumableUsed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"value_","type":"address"}],"name":"isController","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"value_","type":"address"}],"name":"isGovernance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"isHeroAlive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"level","type":"uint32"}],"name":"levelExperience","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"uint256","name":"heroClass","type":"uint256"},{"components":[{"internalType":"int32","name":"strength","type":"int32"},{"internalType":"int32","name":"dexterity","type":"int32"},{"internalType":"int32","name":"vitality","type":"int32"},{"internalType":"int32","name":"energy","type":"int32"}],"internalType":"struct IStatController.CoreAttributes","name":"change","type":"tuple"}],"name":"levelUp","outputs":[{"internalType":"uint256","name":"newLvl","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"level","type":"uint32"}],"name":"levelUpExperienceRequired","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"previousImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"uint256","name":"heroClass","type":"uint256"}],"name":"reborn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"address","name":"item","type":"address"}],"name":"registerConsumableUsage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"resetHeroCustomData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"int32[]","name":"prevAttributes","type":"int32[]"}],"name":"restoreLifeAndMana","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revision","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"index","type":"bytes32"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"setGlobalCustomData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"index","type":"bytes32"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"setHeroCustomData","outputs":[],"stateMutability":"nonpayable","type":"function"}]

60806040523480156200001157600080fd5b507ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff1615906001600160401b03166000811580156200005d5750825b90506000826001600160401b031660011480156200007a5750303b155b90508115801562000089575080155b15620000a85760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b03191660011785558315620000d757845460ff60401b1916680100000000000000001785555b83156200011e57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050506158a780620001336000396000f3fe608060405234801561001057600080fd5b50600436106102295760003560e01c806357e9ee7b11610131578063af6a5722116100b3578063af6a572214610508578063afb1a9ed1461051b578063b429afeb1461052e578063c756abc214610541578063cf53669014610554578063d4ac484814610567578063dee1f0e414610587578063f16a30661461059a578063f77c4791146105ad578063fbed9ac5146105b5578063ffa1ad74146105d557600080fd5b806357e9ee7b146104145780635d015a9a146104355780636bad2ebd146104485780637527a8c01461045b5780637ac46f971461046e5780637cc9638014610481578063906f0ced14610489578063936725ec1461049c57806395e34c34146104cd5780639d0bcca0146104e05780639ff45dbe146104f557600080fd5b806320a7d299116101ba57806320a7d2991461031b578063236715f014610341578063271d229e14610361578063297835fd14610374578063325a19f11461039557806333237c091461039d5780633a658951146103b05780633bdb788b146103d35780633e4d93f6146103e65780634593144c146103f95780634fac6ccd1461040157600080fd5b80630959f7611461022e5780630a82c861146102545780630f82ecaf1461026757806314e4105a1461027c578063184f61431461028f57806319ab453c146102a25780631c2aafe7146102b55780631f051fa0146102c85780631f74d4b2146102e8578063205a119914610308575b600080fd5b61024161023c3660046149c4565b6105f9565b6040519081526020015b60405180910390f35b610241610262366004614a0e565b610679565b61027a610275366004614a27565b6106a2565b005b61027a61028a366004614a49565b6106c0565b61027a61029d366004614a75565b6106da565b61027a6102b0366004614ab0565b6106fc565b6102416102c3366004614acd565b6107f6565b6102db6102d6366004614a49565b610874565b60405161024b9190614b41565b6102fb6102f6366004614a49565b61088f565b60405161024b9190614b86565b61027a610316366004614acd565b6108a9565b61032e610329366004614acd565b61093e565b60405160039190910b815260200161024b565b61035461034f366004614a0e565b610953565b60405161024b9190614b94565b61027a61036f366004614c27565b610964565b610387610382366004614a49565b610984565b60405161024b929190614c69565b610241610a06565b61027a6103ab366004614acd565b610a3f565b6103c36103be366004614c27565b610ab2565b604051901515815260200161024b565b6102416103e1366004614cfb565b610ac7565b6102416103f4366004614d4d565b610adc565b610241610af3565b61027a61040f366004614ab0565b610b23565b610427610422366004614d68565b610c34565b60405161024b929190614da2565b61027a610443366004614edf565b610ccf565b610241610456366004614a49565b610cea565b610241610469366004614d4d565b610cf6565b61027a61047c366004614a49565b610d01565b610241610d1b565b61027a610497366004614f45565b610d39565b6104c0604051806040016040528060058152602001640312e302e360dc1b81525081565b60405161024b9190614fc7565b6102db6104db366004614a49565b610d58565b6104e8610d6c565b60405161024b9190615016565b61027a61050336600461502a565b610d9c565b6102416105163660046150e0565b610db8565b61027a61052936600461519e565b610dd6565b6103c361053c366004614ab0565b610df2565b61027a61054f366004614a49565b610e17565b6102db610562366004614a49565b610eaa565b61057a610575366004614a49565b610ebe565b60405161024b9190615287565b6103c3610595366004614ab0565b610ed8565b6103c36105a8366004614a49565b610f5d565b6104e8610f71565b6105c86105c3366004614a49565b610fa1565b60405161024b9190615295565b6104c060405180604001604052806005815260200164191718971960d91b81525081565b6000610670610606610f71565b6001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610643573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061066791906152dc565b86868686610fb5565b95945050505050565b600061069c6106866110b1565b6000848152600991909101602052604090205490565b92915050565b6106bc6106ad6110b1565b6106b5610f71565b84846110d5565b5050565b6106bc6106cb6110b1565b6106d3610f71565b8484611132565b6106f66106e56110b1565b6106ed610f71565b8686868661133d565b50505050565b600061070661148f565b805490915060ff600160401b82041615906001600160401b031660008115801561072d5750825b90506000826001600160401b031660011480156107495750303b155b905081158015610757575080155b156107755760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561079f57845460ff60401b1916600160401b1785555b6107a8866114b3565b83156107ee57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b600061086c610803610f71565b6001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610840573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086491906152dc565b8585856114c4565b949350505050565b60606108886108816110b1565b8484611545565b9392505050565b610897614892565b6108886108a26110b1565b84846115c2565b7335b9e14df4879649d0513597d587e7f79bceac7063089aa40d6108cb610f71565b6040516001600160e01b031960e084901b1681526001600160a01b039182166004820152908616602482015260448101859052606481018490526084015b60006040518083038186803b15801561092157600080fd5b505af4158015610935573d6000803e3d6000fd5b50505050505050565b600061086c61094b6110b1565b858585611694565b61095b6148b9565b61069c826116b9565b61097f61096f6110b1565b610977610f71565b858585611742565b505050565b6060806109fa610992610f71565b6001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109cf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109f391906152dc565b85856117dc565b915091505b9250929050565b6000610a3a610a3660017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b61530f565b5490565b905090565b7335b9e14df4879649d0513597d587e7f79bceac70630c2bd671610a616110b1565b610a69610f71565b6040516001600160e01b031960e085901b16815260048101929092526001600160a01b03908116602483015286166044820152606481018590526084810184905260a401610909565b600061086c610abf6110b1565b858585611975565b600061086c610ad46110b1565b8585856119a7565b6000610ae7826119d3565b63ffffffff1692915050565b6000610a3a610a3660017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f161530f565b333014610b755760405162461bcd60e51b815260206004820152601b60248201527a24b731b932b0b9b2903932bb34b9b4b7b7103337b93134b23232b760291b60448201526064015b60405180910390fd5b6000610b93610a36600160008051602061585283398151915261530f565b610b9e906001615322565b9050610bc081610bbd600160008051602061585283398151915261530f565b55565b610bef82610bbd60017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e461530f565b604080518281526001600160a01b03841660208201527ff27e2ef832a4eb8ed8ec553b875eecd44764cda95b1c24170e281539e0a869c8910160405180910390a15050565b606060007335b9e14df4879649d0513597d587e7f79bceac7063bc361372610c5a6110b1565b610c62610f71565b866040518463ffffffff1660e01b8152600401610c81939291906153af565b600060405180830381865af4158015610c9e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610cc69190810190615491565b91509150915091565b61097f610cda6110b1565b610ce2610f71565b858585611a74565b60006108888383611b55565b600061069c82611b5e565b6106bc610d0c6110b1565b610d14610f71565b8484611bc9565b6000610a3a610a36600160008051602061585283398151915261530f565b610935610d446110b1565b610d4c610f71565b89898989898989611c86565b6060610888610d656110b1565b8484611f46565b6000610a3a610a3660017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e461530f565b610db5610da76110b1565b610daf610f71565b83611f5b565b50565b6000610670610dc56110b1565b610dcd610f71565b878787876121b6565b6106f6610de16110b1565b610de9610f71565b86868686612553565b6000610dfc610f71565b6001600160a01b0316826001600160a01b0316149050919050565b7335b9e14df4879649d0513597d587e7f79bceac706324642164610e396110b1565b610e41610f71565b6040516001600160e01b031960e085901b16815260048101929092526001600160a01b039081166024830152851660448201526064810184905260840160006040518083038186803b158015610e9657600080fd5b505af41580156107ee573d6000803e3d6000fd5b6060610888610eb76110b1565b848461277b565b610ec6614940565b610888610ed16110b1565b8484612790565b6000816001600160a01b0316610eec610f71565b6001600160a01b0316635aa6e6756040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f29573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4d91906152dc565b6001600160a01b03161492915050565b6000610888610f6a6110b1565b8484612894565b6000610a3a610a3660017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c361861530f565b6060610888610fae6110b1565b84846128b5565b6000694845524f5f434c41535360b01b830361104257604051631789b7b160e01b81526001600160a01b03871690631789b7b190610ff7908890600401615016565b602060405180830381865afa158015611014573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611038919061553c565b60ff169050610670565b600061107f846110506110b1565b600a0160006110638a8a8960ff16612a42565b8152602001908152602001600020612aa490919063ffffffff16565b915050644b41524d4160d81b84148015611097575080155b156110a7576103e8915050610670565b9695505050505050565b7fca9e8235a410bd2656fc43f888ab589425034944963c2881072ee821e700e60090565b6110de83612ac0565b50600082815260098501602090815260409182902083905581518481529081018390527feacbaeebfddf6fe98883b2aebd3ab60c079050480c8e910625719e1f1c949fb2910160405180910390a150505050565b61113b83612ac0565b5060006111488383612d5e565b60008181526003870160209081526040808320805482518185028101850190935280835294955092939092918301828280156111a357602002820191906000526020600020905b81548152602001906001019080831161118f575b5050505050905060006111b7878686612790565b600084815260028901602090815260408083208b83528184208251602b80825261058082019094529596509094909392909190820161056080368337505060408051602b80825261058082019092529293506000929150602082016105608036833701905050905060005b602b8110156112ae5760006112378883612dae565b9050600381900b156112a557611258826112508361556f565b879190612e12565b5084838151811061126b5761126b615592565b602002602001018160030b60030b815250508083838151811061129057611290615592565b602002602001019060030b908160030b815250505b50600101611222565b50600087815260038c01602052604081206112c89161496e565b600087815260038c01602052604090206112e88b8587848a8f8989612e64565b6112f48c898689612ed2565b7fe4463ace42a449d7bf063bb8fa11b72c776ab039ad1e72d287d73e47c8de0d218a8a33604051611327939291906155a8565b60405180910390a1505050505050505050505050565b600061134886612ac0565b90506000816001600160a01b031663bcd418b087876040518363ffffffff1660e01b815260040161137a9291906155cb565b60a060405180830381865afa158015611397573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113bb91906155e4565b602001519050644b41524d4160d81b841480156113d6575082155b156113f457604051633757c0e960e21b815260040160405180910390fd5b61142a84848a600a01600061140d8b8b8860ff16612a42565b8152602001908152602001600020612f8a9092919063ffffffff16565b50604080516001600160a01b0388168152602081018790529081018590526060810184905260ff821660808201527f8af92dbc104a8f5c91bb1ce162c9bb05b14354b888ff1d97cd8c09ebee4925999060a00160405180910390a15050505050505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6114bb612f97565b610db581612fbe565b600061067085858585896001600160a01b031663bcd418b08a8a6040518363ffffffff1660e01b81526004016114fb9291906155cb565b60a060405180830381865afa158015611518573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061153c91906155e4565b60200151610fb5565b606061086c8460006115578686612d5e565b81526020019081526020016000208054806020026020016040519081016040528092919081815260200182805480156115af57602002820191906000526020600020905b81548152602001906001019080831161159b575b50602b93506115bd92505050565b6130dd565b6115ca614892565b60006115f38560010160006115df8787612d5e565b8152602001908152602001600020546131ad565b905060405180608001604052808260008151811061161357611613615592565b602002602001015160030b81526020018260018151811061163657611636615592565b602002602001015160030b81526020018260028151811061165957611659615592565b602002602001015160030b81526020018260038151811061167c5761167c615592565b602002602001015160030b8152509150509392505050565b600061067085826116a58787612d5e565b815260200190815260200160002083613227565b6116c16148b9565b816001036116d15761069c61328b565b816002036116e15761069c61337b565b816003036116f15761069c6134a5565b816004036117015761069c613595565b816005036117115761069c613685565b816006036117215761069c613775565b604051635613365f60e11b815260048101839052602401610b6c565b919050565b61174b84612ac0565b5061177c818660070160006117608787612d5e565b815260200190815260200160002061386590919063ffffffff16565b61179a578060405162f5d9b360e21b8152600401610b6c9190615016565b7fc6a0e07638c51d1c81ce988c90724173ebe207b85cd9b4563e34460940623e3d8383836040516117cd939291906155a8565b60405180910390a15050505050565b60608060006117e96110b1565b600a01600061186e87878a6001600160a01b031663bcd418b08b8b6040518363ffffffff1660e01b81526004016118219291906155cb565b60a060405180830381865afa15801561183e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061186291906155e4565b6020015160ff16612a42565b8152602001908152602001600020905060006118898261387a565b9050806001600160401b038111156118a3576118a3614dc7565b6040519080825280602002602001820160405280156118cc578160200160208202803683370190505b509350806001600160401b038111156118e7576118e7614dc7565b604051908082528060200260200182016040528015611910578160200160208202803683370190505b50925060005b8181101561196a576119288382613885565b86838151811061193a5761193a615592565b6020026020010186848151811061195357611953615592565b602090810291909101019190915252600101611916565b505050935093915050565b60006106708286600701600061198b8888612d5e565b815260200190815260200160002061389490919063ffffffff16565b60008460050160006119ba8686866138a9565b8152602001908152602001600020549050949350505050565b600063ffffffff821615806119ef5750606363ffffffff831610155b156119fc57506000919050565b670de0b6b3a7640000611a3b611a1384606361566c565b611a1e906002615689565b611a369063ffffffff16670de0b6b3a76400006156a6565b6138de565b611a4d90675cfb2e807b1e000061530f565b611a60620186a063ffffffff86166156a6565b611a6a91906156a6565b61069c91906156d3565b611a7d84612ac0565b506000611a8b868585612790565b90506000611a998585612d5e565b6000818152602089905260409020909150611adb611aba60095b8390613227565b8560095b81518110611ace57611ace615592565b60200260200101516139bf565b83604001818151611aec9190615689565b63ffffffff16905250611b0a611b02600a611ab3565b85600a611abe565b83606001818151611b1b9190615689565b63ffffffff1690525082516020840151604085015160608601516080870151611b4b948d948894919390926139e9565b5050505050505050565b6000602b610888565b600063ffffffff82161580611b7a5750606363ffffffff831610155b15611b8757506000919050565b63ffffffff8216600114611bc057611ba8611ba360018461566c565b6119d3565b611bb1836119d3565b611bbb919061566c565b610ae7565b610ae7826119d3565b611bd283612ac0565b506000846007016000611be58585612d5e565b815260200190815260200160002090506000611c0082613b3c565b905060005b81811015610935576000611c198482613b46565b9050611c258482613b52565b611c4257604051630cd274c160e41b815260040160405180910390fd5b7f990d1d901983cf7739aa5466bcf829c3a1266cd06d92c460cc8bea285cddc800868683604051611c75939291906155a8565b60405180910390a150600101611c05565b611c8f88613b67565b611c9c858560ff16613bfc565b611cc5576040516329c8542d60e11b81526004810186905260ff85166024820152604401610b6c565b600b8460ff1603611d1c576000611cdf8a898960096119a7565b141580611cf957506000611cf68a8989600a6119a7565b14155b15611d1c5760405163160400f960e01b815260ff85166004820152602401610b6c565b60098460ff161480611d315750600a8460ff16145b15611d68576000611d458a8989600b6119a7565b14611d685760405163160400f960e01b815260ff85166004820152602401610b6c565b6000611d7d88886001600160401b0316612d5e565b9050600080611d9a611d918d8c8c8b6119a7565b9060a082901c90565b915091508315611e35576001600160a01b03821615611dcc57604051637101e8f760e11b815260040160405180910390fd5b611dd68686612d5e565b8c6005016000611de78d8d8c6138a9565b815260200190815260200160002081905550611e1f8c6006016000858152602001908152602001600020548860ff1660016000613ca6565b600084815260068e016020526040902055611ecf565b856001600160a01b0316826001600160a01b0316141580611e565750848114155b15611e7457604051631d0507df60e21b815260040160405180910390fd5b8b6005016000611e858c8c8b6138a9565b815260200190815260200160002060009055611ebd8c6006016000858152602001908152602001600020548860ff1660006001613ca6565b600084815260068e0160205260409020555b604080516001600160a01b03808d1682526001600160401b038c1660208301529181018a905260ff89166060820152908716608082015260a0810186905284151560c08201523360e08201527fc6cb8c2a2f03a3e6a43771211e58b1343cb739d7404d4130a938138f0fe24f149061010001611327565b606061086c8460030160006115578686612d5e565b611f6482612ac0565b506000611f7982600001518360200151612d5e565b90506000611f908584600001518560200151612790565b60008381526020879052604081206080860151929350918190611fcf576000858152600289016020908152604080832060038c01909252909120611fed565b6000858152600389016020908152604080832060028c019092529091205b9150915060008660400151516001600160401b0381111561201057612010614dc7565b604051908082528060200260200182016040528015612039578160200160208202803683370190505b50905060005b87604001515181101561211c5760008860400151828151811061206457612064615592565b602002602001015190508060030b6000146121135760008960600151156120af57612090868484612e12565b5082905061209e8885613227565b6120a891906156e7565b90506120df565b6120c4836120bc8461556f565b889190612e12565b508290506120d28885613227565b6120dc919061570e565b90505b6120ea878483613d4f565b808484815181106120fd576120fd615592565b602002602001019060030b908160030b81525050505b5060010161203f565b5061213588858585898c60000151878e60400151612e64565b61214189878688612ed2565b86516020808901516060808b01516080808d0151604080516001600160a01b0390981688529587019490945290151585850152911515908401523390830152517ffce07ece8fe77b8dd7f25ce471bb0c4b9bc4a78340b4bfa97827af2307b142409181900360a00190a1505050505050505050565b60006121c186613e26565b5060006121ce8686612d5e565b60608401516040850151602086015186519394506005936121ef91906156e7565b6121f991906156e7565b61220391906156e7565b60030b14612224576040516342fa645360e11b815260040160405180910390fd5b6000612231898888612790565b9050606363ffffffff16816000015163ffffffff16106122645760405163094b8de560e31b815260040160405180910390fd5b806020015163ffffffff16612282826000015163ffffffff166119d3565b63ffffffff1611156122a7576040516311c7bf1f60e31b815260040160405180910390fd5b8051816122b382615735565b63ffffffff16905250600082815260018a0160205260408120546122d6906131ad565b90508460000151816000815181106122f0576122f0615592565b6020026020010181815161230491906156e7565b60030b905250602085015181518290600190811061232457612324615592565b6020026020010181815161233891906156e7565b60030b905250604085015181518290600290811061235857612358615592565b6020026020010181815161236c91906156e7565b600390810b90915260608701518351909250839190811061238f5761238f615592565b602002602001018181516123a391906156e7565b60030b9052506123b281613eb8565b8a600101600085815260200190815260200160002081905550506000896000016000848152602001908152602001600020905060008a6002016000858152602001908152602001600020905060008b6003016000868152602001908152602001600020905061243d8b848484888f8d600001516000602b81111561243857612438615559565b613f32565b61245e8b848484888f8d602001516001602b81111561243857612438615559565b61247f8b848484888f8d604001516002602b81111561243857612438615559565b6124a08b848484888f8d606001516003602b81111561243857612438615559565b506124b890506124b06009611ab3565b60030b613f68565b63ffffffff1660408301526124d06124b0600a611ab3565b63ffffffff16606083018190528251602084015160408501516080860151612502948f948994909390929091906139e9565b7f084f827316fe91358fc5d55a0d078803e03592c8f0be5630e6b51b5feb08d7f0888888886040516125379493929190615758565b60405180910390a1505163ffffffff1698975050505050505050565b61255c85612ac0565b5060006125698585612d5e565b90506000612578888787612790565b6040810151606082015160808301519293509091851561266157600085815260208c905260408120906125ab6009611ab3565b905060006125ba83600a613227565b905060006125cb60165b8590613227565b90508a60200151886020018181516125e39190615689565b63ffffffff169052506126166125fc600385900b613f68565b60408d015161260b908a615689565b63ffffffff16613f83565b96506126366126278360030b613f68565b60608d015161260b9089615689565b95506126566126478260030b613f68565b60808d015161260b9088615689565b945050505050612718565b602087015163ffffffff161561268a5760405163ca9cdbef60e01b815260040160405180910390fd5b866040015163ffffffff168363ffffffff16116126a85760006126b7565b60408701516126b7908461566c565b9250866080015163ffffffff168163ffffffff16116126d75760006126e6565b60808701516126e6908261566c565b9050866060015163ffffffff168263ffffffff1611612706576000612715565b6060870151612715908361566c565b91505b61272f8b86866000015187602001518787876139e9565b7ff92789f80d24dd48838ae6569cf1fe54ca007b75af463e061bd4732677002b2b8989898933604051612766959493929190615783565b60405180910390a15050505050505050505050565b606061086c8460020160006115578686612d5e565b612798614940565b60006127c18560040160006127ad8787612d5e565b815260200190815260200160002054613f99565b90506040518060a00160405280826000815181106127e1576127e1615592565b602002602001015163ffffffff1681526020018260018151811061280757612807615592565b602002602001015163ffffffff1681526020018260028151811061282d5761282d615592565b602002602001015163ffffffff1681526020018260038151811061285357612853615592565b602002602001015163ffffffff1681526020018260048151811061287957612879615592565b602002602001015163ffffffff168152509150509392505050565b60006128a1848484612790565b6080015163ffffffff161515949350505050565b606060006128e08560060160006128cc8787612d5e565b81526020019081526020016000205461400e565b9050600081516001600160401b038111156128fd576128fd614dc7565b604051908082528060200260200182016040528015612926578160200160208202803683370190505b5090506000805b835160ff168160ff16101561299e57838160ff168151811061295157612951615592565b602002602001015160ff16600014612996578083838151811061297657612976615592565b60ff9092166020928302919091019091015281612992816157c6565b9250505b60010161292d565b506000816001600160401b038111156129b9576129b9614dc7565b6040519080825280602002602001820160405280156129e2578160200160208202803683370190505b50905060005b82811015612a3657838181518110612a0257612a02615592565b6020026020010151828281518110612a1c57612a1c615592565b60ff909216602092830291909101909101526001016129e8565b50979650505050505050565b60006001600160401b03831115612a6f57604051633995b34160e01b815260048101849052602401610b6c565b506001600160a01b039290921660a09190911b67ffffffffffffffff60a01b161760e09190911b6001600160e01b0319161790565b6000808080612ab3868661407f565b9097909650945050505050565b6000803390506000836001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b06573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b2a91906152dc565b9050816001600160a01b0316816001600160a01b031614158015612bc15750816001600160a01b0316846001600160a01b03166331423c266040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b91573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612bb591906152dc565b6001600160a01b031614155b8015612c405750816001600160a01b0316846001600160a01b031663683fedf76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c10573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c3491906152dc565b6001600160a01b031614155b8015612cbf5750816001600160a01b0316846001600160a01b031663524a562f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c8f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cb391906152dc565b6001600160a01b031614155b8015612d3e5750816001600160a01b0316846001600160a01b03166389dd9f136040518163ffffffff1660e01b8152600401602060405180830381865afa158015612d0e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d3291906152dc565b6001600160a01b031614155b15610888578160405163f652640360e01b8152600401610b6c9190615016565b60006001600160401b03821115612d8b57604051633995b34160e01b815260048101839052602401610b6c565b5067ffffffffffffffff60a01b60a09190911b166001600160a01b039091161790565b8151600090612dbe6008846156d3565b10612dcb5750600061069c565b612dd66008836157df565b612de19060206156a6565b83612ded6008856156d3565b81518110612dfd57612dfd615592565b602002602001015160001c901c905092915050565b6000806000612e218686613227565b60030b905060008460030b8260030b612e3a91906157f3565b9350839050612e4d600383900b8261581b565b9250612e5a878786613d4f565b5050935093915050565b805160005b81811015612ec657828181518110612e8357612e83615592565b602002602001015160030b600014612ebe57612ebe8a8a8a8a8a868b8b8981518110612eb157612eb1615592565b60200260200101516140b9565b600101612e69565b50505050505050505050565b6000612ee16124b060096125c4565b90506000612ef36124b085600a613227565b90506000836040015163ffffffff16831015612f2c57612f1d83856040015163ffffffff16613f83565b63ffffffff1660408501525060015b836060015163ffffffff16821015612f6157612f5282856060015163ffffffff16613f83565b63ffffffff1660608501525060015b801561093557610935878786600001518760200151886040015189606001518a608001516139e9565b600061086c8484846142d1565b612f9f6142ee565b612fbc57604051631afcd79f60e31b815260040160405180910390fd5b565b6001600160a01b0381166130065760405162461bcd60e51b815260206004820152600f60248201526e2d32b9379031b7b73a3937b63632b960891b6044820152606401610b6c565b61303581610bbd60017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c361861530f565b61306442610bbd60017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b61530f565b61309343610bbd60017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f161530f565b604080516001600160a01b0383168152426020820152438183015290517f1a2dd071001ebf6e03174e3df5b305795a4ad5d41d8fdb9ba41dbbe2367134269181900360600190a150565b60606000826001600160401b038111156130f9576130f9614dc7565b604051908082528060200260200182016040528015613122578160200160208202803683370190505b50905060005b84518110156131a55760005b600881101561319c5760008161314b8460086156a6565b6131559190615322565b9050858110613164575061319c565b61316e8782612dae565b84828151811061318057613180615592565b60039290920b6020928302919091019091015250600101613134565b50600101613128565b509392505050565b604080516008808252610120820190925260609160009190602082016101008036833701905050905060005b6008811015613220576131ed8160206156a6565b8460001c901c82828151811061320557613205615592565b60039290920b602092830291909101909101526001016131d9565b5092915050565b81546000906132376008846156d3565b106132445750600061069c565b61324f6008836157df565b61325a9060206156a6565b836132666008856156d3565b8154811061327657613276615592565b600091825260209091200154901c9392505050565b6132936148b9565b60405180608001604052806040518060800160405280600f60030b8152602001600f60030b8152602001601e60030b8152602001600a60030b81525081526020016040518060e0016040528067016345785d8a000081526020016702c68af0bb1400008152602001671bc16d674ec800008152602001671bc16d674ec80000815260200167016345785d8a000081526020016714d1120d7b16000081526020016706f05b59d3b2000081525081526020016040518060400160405280671bc16d674ec800008152602001670de0b6b3a76400008152508152602001600560030b815250905090565b6133836148b9565b600061338d614308565b6040805161010081018252601e60808201908152600560a0830152601960c0830152600a60e0808401919091529082528251908101909252670214e8348c4f00008252919250602080830191908101846133ef576706f05b59d3b200006133f9565b6703782dace9d900005b6001600160401b031681526020018461341a576729a2241af62c0000613424565b671bc16d674ec800005b6001600160401b03168152602001670de0b6b3a7640000815260200167011c37937e080000815260200167120a871cc002000081526020016706f05b59d3b20000815250815260200160405180604001604052806718fae27693b400008152602001670de0b6b3a76400008152508152602001600560030b81525091505090565b6134ad6148b9565b60405180608001604052806040518060800160405280600a60030b8152602001600f60030b8152602001601460030b8152602001601960030b81525081526020016040518060e0016040528067016345785d8a000081526020016702c68af0bb1400008152602001671bc16d674ec800008152602001670de0b6b3a7640000815260200167016345785d8a00008152602001670de0b6b3a76400008152602001671bc16d674ec800008152508152602001604051806040016040528067120a871cc00200008152602001671bc16d674ec800008152508152602001600560030b815250905090565b61359d6148b9565b60405180608001604052806040518060800160405280600f60030b8152602001601960030b8152602001600f60030b8152602001600f60030b81525081526020016040518060e0016040528067016345785d8a000081526020016702c68af0bb1400008152602001673782dace9d90000081526020016729a2241af62c000081526020016702c68af0bb14000081526020016710a741a4627800008152602001670de0b6b3a7640000815250815260200160405180604001604052806717979cfe362a000081526020016714d1120d7b1600008152508152602001600560030b815250905090565b61368d6148b9565b60405180608001604052806040518060800160405280601460030b8152602001601460030b8152602001601460030b8152602001600a60030b81525081526020016040518060e00160405280670214e8348c4f000081526020016703782dace9d9000081526020016729a2241af62c000081526020016722b1c8c1227a00008152602001670214e8348c4f000081526020016714d1120d7b16000081526020016714d1120d7b160000815250815260200160405180604001604052806714d1120d7b16000081526020016714d1120d7b1600008152508152602001600160030b815250905090565b61377d6148b9565b60405180608001604052806040518060800160405280603260030b8152602001601e60030b8152602001603260030b8152602001600f60030b81525081526020016040518060e001604052806702c68af0bb1400008152602001670429d069189e00008152602001674563918244f4000081526020016729a2241af62c00008152602001670214e8348c4f00008152602001671bc16d674ec800008152602001671bc16d674ec80000815250815260200160405180604001604052806717979cfe362a000081526020016714d1120d7b1600008152508152602001600160030b815250905090565b6000610888836001600160a01b03841661431e565b600061069c82614368565b6000808080612ab38686614373565b6000610888836001600160a01b03841661439e565b60609290921b6001600160601b03191660209190911b6bffffffffffffffff00000000161760189190911b63ff000000161790565b6000670de0b6b3a764000082101561390c57604051637046c4a960e01b815260048101839052602401610b6c565b6000613928613923670de0b6b3a7640000856156d3565b6143b6565b9050600061393e82670de0b6b3a76400006156a6565b905083821c670de0b6b3a764000081900361395b57509392505050565b6706f05b59d3b200005b80156139b557670de0b6b3a764000061397e83806156a6565b61398891906156d3565b9150671bc16d674ec8000082106139ad576139a38184615322565b9250600182901c91505b60011c613965565b5090949350505050565b60008160030b8360030b126139e0576139d8828461570e565b60030b610888565b50600092915050565b63ffffffff811615801590613a02575063ffffffff8316155b15613a0c57600192505b60408051600580825260c082019092526000916020820160a0803683370190505090508581600081518110613a4357613a43615592565b602002602001019063ffffffff16908163ffffffff16815250508481600181518110613a7157613a71615592565b602002602001019063ffffffff16908163ffffffff16815250508381600281518110613a9f57613a9f615592565b602002602001019063ffffffff16908163ffffffff16815250508281600381518110613acd57613acd615592565b602002602001019063ffffffff16908163ffffffff16815250508181600481518110613afb57613afb615592565b602002602001019063ffffffff16908163ffffffff1681525050613b1e81614495565b60009788526004909801602052505060409094209490945550505050565b600061069c825490565b6000610888838361450f565b6000610888836001600160a01b038416614539565b336001600160a01b0316816001600160a01b03166331423c266040518163ffffffff1660e01b8152600401602060405180830381865afa158015613baf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613bd391906152dc565b6001600160a01b031614610db5573360405163f6b9c4d960e01b8152600401610b6c9190615016565b6000821580613c09575081155b15613c165750600061069c565b60058311613c27575080821461069c565b60068303613c46576007821480613c3f575060085b82145b905061069c565b60088303613c55576006613c3c565b60098303613c64576009613c3c565b60078303613c7357600a613c3c565b600a8303613c8257600b613c3c565b600b83036139e057600c821480613c995750600d82145b80613c3f5750600e613c3c565b600080613cb28661400e565b90508260ff16818681518110613cca57613cca615592565b602002602001015160ff1614613d1f578260ff16818681518110613cf057613cf0615592565b602002602001015160ff1660405163c230690360e01b8152600401610b6c929190918252602082015260400190565b83818681518110613d3257613d32615592565b602002602001019060ff16908160ff16815250506110a78161462c565b6000613d5c6008846156d3565b90506000613d6b6008856157df565b613d769060206156a6565b8554909150808310613dc257855460018101875560008781526020812090910155805b83811015613dc0578654600181810189556000898152602081209092019190915501613d99565b505b818463ffffffff1663ffffffff16901b8263ffffffff901b19878581548110613ded57613ded615592565b906000526020600020015460001c161760001b868481548110613e1257613e12615592565b600091825260209091200155505050505050565b600080826001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015613e67573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613e8b91906152dc565b90506001600160a01b038116331461069c57336040516303f2374960e01b8152600401610b6c9190615016565b80516000906008811115613ee957604051633d71388b60e21b81526004810182905260086024820152604401610b6c565b6000805b828110156131a557613f008160206156a6565b858281518110613f1257613f12615592565b602090810291909101015163ffffffff16901b9190911790600101613eed565b8160030b600014611b4b576000613f4a888385612e12565b509050613f5d8989898989878a886140b9565b505050505050505050565b6000808260030b13613f7c57506000919050565b5060030b90565b6000818310613f925781610888565b5090919050565b604080516008808252610120820190925260609160009190602082016101008036833701905050905060005b600881101561322057613fd98160206156a6565b8460001c901c828281518110613ff157613ff1615592565b63ffffffff90921660209283029190910190910152600101613fc5565b6040805160208082526104208201909252606091600091908082016104008036833701905050905060005b60208110156132205761404d8160086156a6565b8460001c901c82828151811061406557614065615592565b60ff90921660209283029190910190910152600101614039565b60008181526002830160205260408120548190806140ae576140a185856146a3565b9250600091506109ff9050565b6001925090506109ff565b6000886001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156140f9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061411d91906152dc565b6001600160a01b0316631789b7b1846040518263ffffffff1660e01b81526004016141489190615016565b602060405180830381865afa158015614165573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614189919061553c565b60ff16905083614200576141d860046141a560045b8990613227565b6141b260045b8b90613227565b6141bc86866146af565b6141c691906156e7565b6141d091906156e7565b8a9190613d4f565b6141fb60056141e7600561419e565b6141f160056141ab565b6141bc86866146e9565b613f5d565b600184036142715761422b6006614217600661419e565b61422160066141ab565b6141bc8686614711565b61424e600761423a600761419e565b61424460076141ab565b6141bc8686614739565b6141fb600861425d600861419e565b61426760086141ab565b6141bc8686614761565b600284036142a1576141fb6009614288600961419e565b61429260096141ab565b6141bc86868b600001516147a7565b60038403613f5d57613f5d600a6142b8600a61419e565b6142c2600a6141ab565b6141bc86868b6000015161481e565b6000828152600284016020526040812082905561086c848461487a565b60006142f861148f565b54600160401b900460ff16919050565b60006201b254461480610a3a5750504660fa1490565b600061432a838361439e565b6143605750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561069c565b50600061069c565b600061069c82613b3c565b600080806143818585613b46565b600081815260029690960160205260409095205494959350505050565b60009081526001919091016020526040902054151590565b6000600160801b82106143d657608091821c916143d39082615322565b90505b600160401b82106143f457604091821c916143f19082615322565b90505b640100000000821061441357602091821c916144109082615322565b90505b62010000821061443057601091821c9161442d9082615322565b90505b610100821061444c57600891821c916144499082615322565b90505b6010821061446757600491821c916144649082615322565b90505b6004821061448257600291821c9161447f9082615322565b90505b6002821061173d5761069c600182615322565b805160009060088111156144c657604051633d71388b60e21b81526004810182905260086024820152604401610b6c565b6000805b828110156131a5576144dd8160206156a6565b8582815181106144ef576144ef615592565b602090810291909101015163ffffffff16901b91909117906001016144ca565b600082600001828154811061452657614526615592565b9060005260206000200154905092915050565b6000818152600183016020526040812054801561462257600061455d60018361530f565b85549091506000906145719060019061530f565b90508082146145d657600086600001828154811061459157614591615592565b90600052602060002001549050808760000184815481106145b4576145b4615592565b6000918252602080832090910192909255918252600188019052604090208390555b85548690806145e7576145e761583b565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505061069c565b600091505061069c565b8051600090602081111561465d57604051633d71388b60e21b81526004810182905260206024820152604401610b6c565b6000805b828110156131a5576146748160086156a6565b85828151811061468657614686615592565b602090810291909101015160ff16901b9190911790600101614661565b60006108888383614886565b6000670de0b6b3a76400006146c3836116b9565b60200151516146d5600386900b613f68565b6146df91906156a6565b61088891906156d3565b6000670de0b6b3a76400006146fd836116b9565b60200151602001516146d58560030b613f68565b6000670de0b6b3a7640000614725836116b9565b60200151604001516146d58560030b613f68565b6000670de0b6b3a764000061474d836116b9565b60200151606001516146d58560030b613f68565b6000610888670de0b6b3a7640000614778846116b9565b602001516080015161478c8660030b613f68565b61479691906156a6565b6147a091906156d3565b604b613f83565b6000670de0b6b3a76400006147bb846116b9565b60400151516147d09063ffffffff85166156a6565b6147da91906156d3565b670de0b6b3a76400006147ec856116b9565b6020015160a001516148008760030b613f68565b61480a91906156a6565b61481491906156d3565b61086c9190615322565b6000670de0b6b3a7640000614832846116b9565b604001516020015161484a9063ffffffff85166156a6565b61485491906156d3565b670de0b6b3a7640000614866856116b9565b6020015160c001516148008760030b613f68565b6000610888838361431e565b6000610888838361439e565b60408051608081018252600080825260208201819052918101829052606081019190915290565b60405180608001604052806148cc614892565b81526020016149116040518060e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b8152602001614933604051806040016040528060008152602001600081525090565b8152600060209091015290565b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915290565b5080546000825590600052602060002090810190610db591905b8082111561499c5760008155600101614988565b5090565b6001600160a01b0381168114610db557600080fd5b60ff81168114610db557600080fd5b600080600080608085870312156149da57600080fd5b84356149e5816149a0565b935060208501359250604085013591506060850135614a03816149b5565b939692955090935050565b600060208284031215614a2057600080fd5b5035919050565b60008060408385031215614a3a57600080fd5b50508035926020909101359150565b60008060408385031215614a5c57600080fd5b8235614a67816149a0565b946020939093013593505050565b60008060008060808587031215614a8b57600080fd5b8435614a96816149a0565b966020860135965060408601359560600135945092505050565b600060208284031215614ac257600080fd5b8135610888816149a0565b600080600060608486031215614ae257600080fd5b8335614aed816149a0565b95602085013595506040909401359392505050565b60008151808452602080850194506020840160005b83811015614b3657815160030b87529582019590820190600101614b17565b509495945050505050565b6020815260006108886020830184614b02565b805160030b8252602081015160030b6020830152604081015160030b6040830152606081015160030b60608301525050565b6080810161069c8284614b54565b60006101c082019050614ba8828451614b54565b602083015180516080840152602081015160a0840152604081015160c0840152606081015160e0840152608081015161010084015260a081015161012084015260c0810151610140840152506040830151614c1161016084018280518252602090810151910152565b5060608301516132206101a084018260030b9052565b600080600060608486031215614c3c57600080fd5b8335614c47816149a0565b9250602084013591506040840135614c5e816149a0565b809150509250925092565b604080825283519082018190526000906020906060840190828701845b82811015614ca257815184529284019290840190600101614c86565b5050508381038285015284518082528583019183019060005b81811015614cd757835183529284019291840191600101614cbb565b5090979650505050505050565b80356001600160401b038116811461173d57600080fd5b600080600060608486031215614d1057600080fd5b8335614d1b816149a0565b9250614d2960208501614ce4565b91506040840135614c5e816149b5565b803563ffffffff8116811461173d57600080fd5b600060208284031215614d5f57600080fd5b61088882614d39565b600060208284031215614d7a57600080fd5b81356001600160401b03811115614d9057600080fd5b820160a0818503121561088857600080fd5b604081526000614db56040830185614b02565b90508260030b60208301529392505050565b634e487b7160e01b600052604160045260246000fd5b60405160a081016001600160401b0381118282101715614dff57614dff614dc7565b60405290565b604051601f8201601f191681016001600160401b0381118282101715614e2d57614e2d614dc7565b604052919050565b60006001600160401b03821115614e4e57614e4e614dc7565b5060051b60200190565b8060030b8114610db557600080fd5b600082601f830112614e7857600080fd5b81356020614e8d614e8883614e35565b614e05565b8083825260208201915060208460051b870101935086841115614eaf57600080fd5b602086015b84811015614ed4578035614ec781614e58565b8352918301918301614eb4565b509695505050505050565b600080600060608486031215614ef457600080fd5b8335614eff816149a0565b92506020840135915060408401356001600160401b03811115614f2157600080fd5b614f2d86828701614e67565b9150509250925092565b8015158114610db557600080fd5b600080600080600080600060e0888a031215614f6057600080fd5b8735614f6b816149a0565b9650614f7960208901614ce4565b9550604088013594506060880135614f90816149b5565b93506080880135614fa0816149a0565b925060a0880135915060c0880135614fb781614f37565b8091505092959891949750929550565b60006020808352835180602085015260005b81811015614ff557858101830151858201604001528201614fd9565b506000604082860101526040601f19601f8301168501019250505092915050565b6001600160a01b0391909116815260200190565b60006020828403121561503c57600080fd5b81356001600160401b038082111561505357600080fd5b9083019060a0828603121561506757600080fd5b61506f614ddd565b823561507a816149a0565b81526020838101359082015260408301358281111561509857600080fd5b6150a487828601614e67565b604083015250606083013591506150ba82614f37565b816060820152608083013592506150d083614f37565b6080810192909252509392505050565b60008060008084860360e08112156150f757600080fd5b8535615102816149a0565b945060208601359350604086013592506080605f198201121561512457600080fd5b50604051608081018181106001600160401b038211171561514757615147614dc7565b604052606086013561515881614e58565b8152608086013561516881614e58565b602082015260a086013561517b81614e58565b604082015260c086013561518e81614e58565b6060820152939692955090935050565b6000806000808486036101008112156151b657600080fd5b85356151c1816149a0565b94506020860135935060a0603f19820112156151dc57600080fd5b506151e5614ddd565b6151f160408701614d39565b81526151ff60608701614d39565b602082015261521060808701614d39565b604082015261522160a08701614d39565b606082015261523260c08701614d39565b6080820152915060e0850135614a0381614f37565b63ffffffff808251168352806020830151166020840152806040830151166040840152806060830151166060840152806080830151166080840152505050565b60a0810161069c8284615247565b6020808252825182820181905260009190848201906040850190845b818110156152d057835160ff16835292840192918401916001016152b1565b50909695505050505050565b6000602082840312156152ee57600080fd5b8151610888816149a0565b634e487b7160e01b600052601160045260246000fd5b8181038181111561069c5761069c6152f9565b8082018082111561069c5761069c6152f9565b6000808335601e1984360301811261534c57600080fd5b83016020810192503590506001600160401b0381111561536b57600080fd5b8060051b36038213156109ff57600080fd5b81835260006001600160fb1b0383111561539657600080fd5b8260051b80836020870137939093016020019392505050565b8381526000602060018060a01b03808616602085015260606040850152610100840185356153dc816149a0565b821660608601526020860135608086015263ffffffff6153fe60408801614d39565b1660a08601526154116060870187615335565b60a060c0880152918290529060009061012087015b8183101561544f578335615439816149a0565b8516815292850192600192909201918501615426565b61545c60808a018a615335565b898303605f190160e08b01529650945061547781878761537d565b9b9a5050505050505050505050565b805161173d81614e58565b600080604083850312156154a457600080fd5b82516001600160401b038111156154ba57600080fd5b8301601f810185136154cb57600080fd5b805160206154db614e8883614e35565b82815260059290921b830181019181810190888411156154fa57600080fd5b938201935b8385101561552157845161551281614e58565b825293820193908201906154ff565b95506155309050868201615486565b93505050509250929050565b60006020828403121561554e57600080fd5b8151610888816149b5565b634e487b7160e01b600052602160045260246000fd5b60008160030b637fffffff198103615589576155896152f9565b60000392915050565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b0393841681526020810192909252909116604082015260600190565b6001600160a01b03929092168252602082015260400190565b600060a082840312156155f657600080fd5b6155fe614ddd565b8251615609816149b5565b81526020830151615619816149b5565b6020820152604083015161562c81614f37565b6040820152606083015168ffffffffffffffffff8116811461564d57600080fd5b60608201526080830151615660816149a0565b60808201529392505050565b63ffffffff828116828216039080821115613220576132206152f9565b63ffffffff818116838216019080821115613220576132206152f9565b808202811582820484141761069c5761069c6152f9565b634e487b7160e01b600052601260045260246000fd5b6000826156e2576156e26156bd565b500490565b600381810b9083900b01637fffffff8113637fffffff198212171561069c5761069c6152f9565b600382810b9082900b03637fffffff198112637fffffff8213171561069c5761069c6152f9565b600063ffffffff80831681810361574e5761574e6152f9565b6001019392505050565b6001600160a01b0385168152602081018490526040810183905260e081016106706060830184614b54565b6001600160a01b038681168252602082018690526101208201906157aa6040840187615247565b84151560e0840152808416610100840152509695505050505050565b6000600182016157d8576157d86152f9565b5060010190565b6000826157ee576157ee6156bd565b500690565b8082018281126000831280158216821582161715615813576158136152f9565b505092915050565b8181036000831280158383131683831282161715613220576132206152f9565b634e487b7160e01b600052603160045260246000fdfe22573091f17911fb166032a3d9e0554aa73d31b7b7ddea4a4dd2995650af84bda26469706673582212202fcf5d7a378f3e19a84b59dc634aa7c8cb6073fd81d9ddd39606771bdd8a800b64736f6c63430008170033

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106102295760003560e01c806357e9ee7b11610131578063af6a5722116100b3578063af6a572214610508578063afb1a9ed1461051b578063b429afeb1461052e578063c756abc214610541578063cf53669014610554578063d4ac484814610567578063dee1f0e414610587578063f16a30661461059a578063f77c4791146105ad578063fbed9ac5146105b5578063ffa1ad74146105d557600080fd5b806357e9ee7b146104145780635d015a9a146104355780636bad2ebd146104485780637527a8c01461045b5780637ac46f971461046e5780637cc9638014610481578063906f0ced14610489578063936725ec1461049c57806395e34c34146104cd5780639d0bcca0146104e05780639ff45dbe146104f557600080fd5b806320a7d299116101ba57806320a7d2991461031b578063236715f014610341578063271d229e14610361578063297835fd14610374578063325a19f11461039557806333237c091461039d5780633a658951146103b05780633bdb788b146103d35780633e4d93f6146103e65780634593144c146103f95780634fac6ccd1461040157600080fd5b80630959f7611461022e5780630a82c861146102545780630f82ecaf1461026757806314e4105a1461027c578063184f61431461028f57806319ab453c146102a25780631c2aafe7146102b55780631f051fa0146102c85780631f74d4b2146102e8578063205a119914610308575b600080fd5b61024161023c3660046149c4565b6105f9565b6040519081526020015b60405180910390f35b610241610262366004614a0e565b610679565b61027a610275366004614a27565b6106a2565b005b61027a61028a366004614a49565b6106c0565b61027a61029d366004614a75565b6106da565b61027a6102b0366004614ab0565b6106fc565b6102416102c3366004614acd565b6107f6565b6102db6102d6366004614a49565b610874565b60405161024b9190614b41565b6102fb6102f6366004614a49565b61088f565b60405161024b9190614b86565b61027a610316366004614acd565b6108a9565b61032e610329366004614acd565b61093e565b60405160039190910b815260200161024b565b61035461034f366004614a0e565b610953565b60405161024b9190614b94565b61027a61036f366004614c27565b610964565b610387610382366004614a49565b610984565b60405161024b929190614c69565b610241610a06565b61027a6103ab366004614acd565b610a3f565b6103c36103be366004614c27565b610ab2565b604051901515815260200161024b565b6102416103e1366004614cfb565b610ac7565b6102416103f4366004614d4d565b610adc565b610241610af3565b61027a61040f366004614ab0565b610b23565b610427610422366004614d68565b610c34565b60405161024b929190614da2565b61027a610443366004614edf565b610ccf565b610241610456366004614a49565b610cea565b610241610469366004614d4d565b610cf6565b61027a61047c366004614a49565b610d01565b610241610d1b565b61027a610497366004614f45565b610d39565b6104c0604051806040016040528060058152602001640312e302e360dc1b81525081565b60405161024b9190614fc7565b6102db6104db366004614a49565b610d58565b6104e8610d6c565b60405161024b9190615016565b61027a61050336600461502a565b610d9c565b6102416105163660046150e0565b610db8565b61027a61052936600461519e565b610dd6565b6103c361053c366004614ab0565b610df2565b61027a61054f366004614a49565b610e17565b6102db610562366004614a49565b610eaa565b61057a610575366004614a49565b610ebe565b60405161024b9190615287565b6103c3610595366004614ab0565b610ed8565b6103c36105a8366004614a49565b610f5d565b6104e8610f71565b6105c86105c3366004614a49565b610fa1565b60405161024b9190615295565b6104c060405180604001604052806005815260200164191718971960d91b81525081565b6000610670610606610f71565b6001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610643573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061066791906152dc565b86868686610fb5565b95945050505050565b600061069c6106866110b1565b6000848152600991909101602052604090205490565b92915050565b6106bc6106ad6110b1565b6106b5610f71565b84846110d5565b5050565b6106bc6106cb6110b1565b6106d3610f71565b8484611132565b6106f66106e56110b1565b6106ed610f71565b8686868661133d565b50505050565b600061070661148f565b805490915060ff600160401b82041615906001600160401b031660008115801561072d5750825b90506000826001600160401b031660011480156107495750303b155b905081158015610757575080155b156107755760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561079f57845460ff60401b1916600160401b1785555b6107a8866114b3565b83156107ee57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b600061086c610803610f71565b6001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610840573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086491906152dc565b8585856114c4565b949350505050565b60606108886108816110b1565b8484611545565b9392505050565b610897614892565b6108886108a26110b1565b84846115c2565b7335b9e14df4879649d0513597d587e7f79bceac7063089aa40d6108cb610f71565b6040516001600160e01b031960e084901b1681526001600160a01b039182166004820152908616602482015260448101859052606481018490526084015b60006040518083038186803b15801561092157600080fd5b505af4158015610935573d6000803e3d6000fd5b50505050505050565b600061086c61094b6110b1565b858585611694565b61095b6148b9565b61069c826116b9565b61097f61096f6110b1565b610977610f71565b858585611742565b505050565b6060806109fa610992610f71565b6001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109cf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109f391906152dc565b85856117dc565b915091505b9250929050565b6000610a3a610a3660017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b61530f565b5490565b905090565b7335b9e14df4879649d0513597d587e7f79bceac70630c2bd671610a616110b1565b610a69610f71565b6040516001600160e01b031960e085901b16815260048101929092526001600160a01b03908116602483015286166044820152606481018590526084810184905260a401610909565b600061086c610abf6110b1565b858585611975565b600061086c610ad46110b1565b8585856119a7565b6000610ae7826119d3565b63ffffffff1692915050565b6000610a3a610a3660017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f161530f565b333014610b755760405162461bcd60e51b815260206004820152601b60248201527a24b731b932b0b9b2903932bb34b9b4b7b7103337b93134b23232b760291b60448201526064015b60405180910390fd5b6000610b93610a36600160008051602061585283398151915261530f565b610b9e906001615322565b9050610bc081610bbd600160008051602061585283398151915261530f565b55565b610bef82610bbd60017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e461530f565b604080518281526001600160a01b03841660208201527ff27e2ef832a4eb8ed8ec553b875eecd44764cda95b1c24170e281539e0a869c8910160405180910390a15050565b606060007335b9e14df4879649d0513597d587e7f79bceac7063bc361372610c5a6110b1565b610c62610f71565b866040518463ffffffff1660e01b8152600401610c81939291906153af565b600060405180830381865af4158015610c9e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610cc69190810190615491565b91509150915091565b61097f610cda6110b1565b610ce2610f71565b858585611a74565b60006108888383611b55565b600061069c82611b5e565b6106bc610d0c6110b1565b610d14610f71565b8484611bc9565b6000610a3a610a36600160008051602061585283398151915261530f565b610935610d446110b1565b610d4c610f71565b89898989898989611c86565b6060610888610d656110b1565b8484611f46565b6000610a3a610a3660017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e461530f565b610db5610da76110b1565b610daf610f71565b83611f5b565b50565b6000610670610dc56110b1565b610dcd610f71565b878787876121b6565b6106f6610de16110b1565b610de9610f71565b86868686612553565b6000610dfc610f71565b6001600160a01b0316826001600160a01b0316149050919050565b7335b9e14df4879649d0513597d587e7f79bceac706324642164610e396110b1565b610e41610f71565b6040516001600160e01b031960e085901b16815260048101929092526001600160a01b039081166024830152851660448201526064810184905260840160006040518083038186803b158015610e9657600080fd5b505af41580156107ee573d6000803e3d6000fd5b6060610888610eb76110b1565b848461277b565b610ec6614940565b610888610ed16110b1565b8484612790565b6000816001600160a01b0316610eec610f71565b6001600160a01b0316635aa6e6756040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f29573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4d91906152dc565b6001600160a01b03161492915050565b6000610888610f6a6110b1565b8484612894565b6000610a3a610a3660017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c361861530f565b6060610888610fae6110b1565b84846128b5565b6000694845524f5f434c41535360b01b830361104257604051631789b7b160e01b81526001600160a01b03871690631789b7b190610ff7908890600401615016565b602060405180830381865afa158015611014573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611038919061553c565b60ff169050610670565b600061107f846110506110b1565b600a0160006110638a8a8960ff16612a42565b8152602001908152602001600020612aa490919063ffffffff16565b915050644b41524d4160d81b84148015611097575080155b156110a7576103e8915050610670565b9695505050505050565b7fca9e8235a410bd2656fc43f888ab589425034944963c2881072ee821e700e60090565b6110de83612ac0565b50600082815260098501602090815260409182902083905581518481529081018390527feacbaeebfddf6fe98883b2aebd3ab60c079050480c8e910625719e1f1c949fb2910160405180910390a150505050565b61113b83612ac0565b5060006111488383612d5e565b60008181526003870160209081526040808320805482518185028101850190935280835294955092939092918301828280156111a357602002820191906000526020600020905b81548152602001906001019080831161118f575b5050505050905060006111b7878686612790565b600084815260028901602090815260408083208b83528184208251602b80825261058082019094529596509094909392909190820161056080368337505060408051602b80825261058082019092529293506000929150602082016105608036833701905050905060005b602b8110156112ae5760006112378883612dae565b9050600381900b156112a557611258826112508361556f565b879190612e12565b5084838151811061126b5761126b615592565b602002602001018160030b60030b815250508083838151811061129057611290615592565b602002602001019060030b908160030b815250505b50600101611222565b50600087815260038c01602052604081206112c89161496e565b600087815260038c01602052604090206112e88b8587848a8f8989612e64565b6112f48c898689612ed2565b7fe4463ace42a449d7bf063bb8fa11b72c776ab039ad1e72d287d73e47c8de0d218a8a33604051611327939291906155a8565b60405180910390a1505050505050505050505050565b600061134886612ac0565b90506000816001600160a01b031663bcd418b087876040518363ffffffff1660e01b815260040161137a9291906155cb565b60a060405180830381865afa158015611397573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113bb91906155e4565b602001519050644b41524d4160d81b841480156113d6575082155b156113f457604051633757c0e960e21b815260040160405180910390fd5b61142a84848a600a01600061140d8b8b8860ff16612a42565b8152602001908152602001600020612f8a9092919063ffffffff16565b50604080516001600160a01b0388168152602081018790529081018590526060810184905260ff821660808201527f8af92dbc104a8f5c91bb1ce162c9bb05b14354b888ff1d97cd8c09ebee4925999060a00160405180910390a15050505050505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6114bb612f97565b610db581612fbe565b600061067085858585896001600160a01b031663bcd418b08a8a6040518363ffffffff1660e01b81526004016114fb9291906155cb565b60a060405180830381865afa158015611518573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061153c91906155e4565b60200151610fb5565b606061086c8460006115578686612d5e565b81526020019081526020016000208054806020026020016040519081016040528092919081815260200182805480156115af57602002820191906000526020600020905b81548152602001906001019080831161159b575b50602b93506115bd92505050565b6130dd565b6115ca614892565b60006115f38560010160006115df8787612d5e565b8152602001908152602001600020546131ad565b905060405180608001604052808260008151811061161357611613615592565b602002602001015160030b81526020018260018151811061163657611636615592565b602002602001015160030b81526020018260028151811061165957611659615592565b602002602001015160030b81526020018260038151811061167c5761167c615592565b602002602001015160030b8152509150509392505050565b600061067085826116a58787612d5e565b815260200190815260200160002083613227565b6116c16148b9565b816001036116d15761069c61328b565b816002036116e15761069c61337b565b816003036116f15761069c6134a5565b816004036117015761069c613595565b816005036117115761069c613685565b816006036117215761069c613775565b604051635613365f60e11b815260048101839052602401610b6c565b919050565b61174b84612ac0565b5061177c818660070160006117608787612d5e565b815260200190815260200160002061386590919063ffffffff16565b61179a578060405162f5d9b360e21b8152600401610b6c9190615016565b7fc6a0e07638c51d1c81ce988c90724173ebe207b85cd9b4563e34460940623e3d8383836040516117cd939291906155a8565b60405180910390a15050505050565b60608060006117e96110b1565b600a01600061186e87878a6001600160a01b031663bcd418b08b8b6040518363ffffffff1660e01b81526004016118219291906155cb565b60a060405180830381865afa15801561183e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061186291906155e4565b6020015160ff16612a42565b8152602001908152602001600020905060006118898261387a565b9050806001600160401b038111156118a3576118a3614dc7565b6040519080825280602002602001820160405280156118cc578160200160208202803683370190505b509350806001600160401b038111156118e7576118e7614dc7565b604051908082528060200260200182016040528015611910578160200160208202803683370190505b50925060005b8181101561196a576119288382613885565b86838151811061193a5761193a615592565b6020026020010186848151811061195357611953615592565b602090810291909101019190915252600101611916565b505050935093915050565b60006106708286600701600061198b8888612d5e565b815260200190815260200160002061389490919063ffffffff16565b60008460050160006119ba8686866138a9565b8152602001908152602001600020549050949350505050565b600063ffffffff821615806119ef5750606363ffffffff831610155b156119fc57506000919050565b670de0b6b3a7640000611a3b611a1384606361566c565b611a1e906002615689565b611a369063ffffffff16670de0b6b3a76400006156a6565b6138de565b611a4d90675cfb2e807b1e000061530f565b611a60620186a063ffffffff86166156a6565b611a6a91906156a6565b61069c91906156d3565b611a7d84612ac0565b506000611a8b868585612790565b90506000611a998585612d5e565b6000818152602089905260409020909150611adb611aba60095b8390613227565b8560095b81518110611ace57611ace615592565b60200260200101516139bf565b83604001818151611aec9190615689565b63ffffffff16905250611b0a611b02600a611ab3565b85600a611abe565b83606001818151611b1b9190615689565b63ffffffff1690525082516020840151604085015160608601516080870151611b4b948d948894919390926139e9565b5050505050505050565b6000602b610888565b600063ffffffff82161580611b7a5750606363ffffffff831610155b15611b8757506000919050565b63ffffffff8216600114611bc057611ba8611ba360018461566c565b6119d3565b611bb1836119d3565b611bbb919061566c565b610ae7565b610ae7826119d3565b611bd283612ac0565b506000846007016000611be58585612d5e565b815260200190815260200160002090506000611c0082613b3c565b905060005b81811015610935576000611c198482613b46565b9050611c258482613b52565b611c4257604051630cd274c160e41b815260040160405180910390fd5b7f990d1d901983cf7739aa5466bcf829c3a1266cd06d92c460cc8bea285cddc800868683604051611c75939291906155a8565b60405180910390a150600101611c05565b611c8f88613b67565b611c9c858560ff16613bfc565b611cc5576040516329c8542d60e11b81526004810186905260ff85166024820152604401610b6c565b600b8460ff1603611d1c576000611cdf8a898960096119a7565b141580611cf957506000611cf68a8989600a6119a7565b14155b15611d1c5760405163160400f960e01b815260ff85166004820152602401610b6c565b60098460ff161480611d315750600a8460ff16145b15611d68576000611d458a8989600b6119a7565b14611d685760405163160400f960e01b815260ff85166004820152602401610b6c565b6000611d7d88886001600160401b0316612d5e565b9050600080611d9a611d918d8c8c8b6119a7565b9060a082901c90565b915091508315611e35576001600160a01b03821615611dcc57604051637101e8f760e11b815260040160405180910390fd5b611dd68686612d5e565b8c6005016000611de78d8d8c6138a9565b815260200190815260200160002081905550611e1f8c6006016000858152602001908152602001600020548860ff1660016000613ca6565b600084815260068e016020526040902055611ecf565b856001600160a01b0316826001600160a01b0316141580611e565750848114155b15611e7457604051631d0507df60e21b815260040160405180910390fd5b8b6005016000611e858c8c8b6138a9565b815260200190815260200160002060009055611ebd8c6006016000858152602001908152602001600020548860ff1660006001613ca6565b600084815260068e0160205260409020555b604080516001600160a01b03808d1682526001600160401b038c1660208301529181018a905260ff89166060820152908716608082015260a0810186905284151560c08201523360e08201527fc6cb8c2a2f03a3e6a43771211e58b1343cb739d7404d4130a938138f0fe24f149061010001611327565b606061086c8460030160006115578686612d5e565b611f6482612ac0565b506000611f7982600001518360200151612d5e565b90506000611f908584600001518560200151612790565b60008381526020879052604081206080860151929350918190611fcf576000858152600289016020908152604080832060038c01909252909120611fed565b6000858152600389016020908152604080832060028c019092529091205b9150915060008660400151516001600160401b0381111561201057612010614dc7565b604051908082528060200260200182016040528015612039578160200160208202803683370190505b50905060005b87604001515181101561211c5760008860400151828151811061206457612064615592565b602002602001015190508060030b6000146121135760008960600151156120af57612090868484612e12565b5082905061209e8885613227565b6120a891906156e7565b90506120df565b6120c4836120bc8461556f565b889190612e12565b508290506120d28885613227565b6120dc919061570e565b90505b6120ea878483613d4f565b808484815181106120fd576120fd615592565b602002602001019060030b908160030b81525050505b5060010161203f565b5061213588858585898c60000151878e60400151612e64565b61214189878688612ed2565b86516020808901516060808b01516080808d0151604080516001600160a01b0390981688529587019490945290151585850152911515908401523390830152517ffce07ece8fe77b8dd7f25ce471bb0c4b9bc4a78340b4bfa97827af2307b142409181900360a00190a1505050505050505050565b60006121c186613e26565b5060006121ce8686612d5e565b60608401516040850151602086015186519394506005936121ef91906156e7565b6121f991906156e7565b61220391906156e7565b60030b14612224576040516342fa645360e11b815260040160405180910390fd5b6000612231898888612790565b9050606363ffffffff16816000015163ffffffff16106122645760405163094b8de560e31b815260040160405180910390fd5b806020015163ffffffff16612282826000015163ffffffff166119d3565b63ffffffff1611156122a7576040516311c7bf1f60e31b815260040160405180910390fd5b8051816122b382615735565b63ffffffff16905250600082815260018a0160205260408120546122d6906131ad565b90508460000151816000815181106122f0576122f0615592565b6020026020010181815161230491906156e7565b60030b905250602085015181518290600190811061232457612324615592565b6020026020010181815161233891906156e7565b60030b905250604085015181518290600290811061235857612358615592565b6020026020010181815161236c91906156e7565b600390810b90915260608701518351909250839190811061238f5761238f615592565b602002602001018181516123a391906156e7565b60030b9052506123b281613eb8565b8a600101600085815260200190815260200160002081905550506000896000016000848152602001908152602001600020905060008a6002016000858152602001908152602001600020905060008b6003016000868152602001908152602001600020905061243d8b848484888f8d600001516000602b81111561243857612438615559565b613f32565b61245e8b848484888f8d602001516001602b81111561243857612438615559565b61247f8b848484888f8d604001516002602b81111561243857612438615559565b6124a08b848484888f8d606001516003602b81111561243857612438615559565b506124b890506124b06009611ab3565b60030b613f68565b63ffffffff1660408301526124d06124b0600a611ab3565b63ffffffff16606083018190528251602084015160408501516080860151612502948f948994909390929091906139e9565b7f084f827316fe91358fc5d55a0d078803e03592c8f0be5630e6b51b5feb08d7f0888888886040516125379493929190615758565b60405180910390a1505163ffffffff1698975050505050505050565b61255c85612ac0565b5060006125698585612d5e565b90506000612578888787612790565b6040810151606082015160808301519293509091851561266157600085815260208c905260408120906125ab6009611ab3565b905060006125ba83600a613227565b905060006125cb60165b8590613227565b90508a60200151886020018181516125e39190615689565b63ffffffff169052506126166125fc600385900b613f68565b60408d015161260b908a615689565b63ffffffff16613f83565b96506126366126278360030b613f68565b60608d015161260b9089615689565b95506126566126478260030b613f68565b60808d015161260b9088615689565b945050505050612718565b602087015163ffffffff161561268a5760405163ca9cdbef60e01b815260040160405180910390fd5b866040015163ffffffff168363ffffffff16116126a85760006126b7565b60408701516126b7908461566c565b9250866080015163ffffffff168163ffffffff16116126d75760006126e6565b60808701516126e6908261566c565b9050866060015163ffffffff168263ffffffff1611612706576000612715565b6060870151612715908361566c565b91505b61272f8b86866000015187602001518787876139e9565b7ff92789f80d24dd48838ae6569cf1fe54ca007b75af463e061bd4732677002b2b8989898933604051612766959493929190615783565b60405180910390a15050505050505050505050565b606061086c8460020160006115578686612d5e565b612798614940565b60006127c18560040160006127ad8787612d5e565b815260200190815260200160002054613f99565b90506040518060a00160405280826000815181106127e1576127e1615592565b602002602001015163ffffffff1681526020018260018151811061280757612807615592565b602002602001015163ffffffff1681526020018260028151811061282d5761282d615592565b602002602001015163ffffffff1681526020018260038151811061285357612853615592565b602002602001015163ffffffff1681526020018260048151811061287957612879615592565b602002602001015163ffffffff168152509150509392505050565b60006128a1848484612790565b6080015163ffffffff161515949350505050565b606060006128e08560060160006128cc8787612d5e565b81526020019081526020016000205461400e565b9050600081516001600160401b038111156128fd576128fd614dc7565b604051908082528060200260200182016040528015612926578160200160208202803683370190505b5090506000805b835160ff168160ff16101561299e57838160ff168151811061295157612951615592565b602002602001015160ff16600014612996578083838151811061297657612976615592565b60ff9092166020928302919091019091015281612992816157c6565b9250505b60010161292d565b506000816001600160401b038111156129b9576129b9614dc7565b6040519080825280602002602001820160405280156129e2578160200160208202803683370190505b50905060005b82811015612a3657838181518110612a0257612a02615592565b6020026020010151828281518110612a1c57612a1c615592565b60ff909216602092830291909101909101526001016129e8565b50979650505050505050565b60006001600160401b03831115612a6f57604051633995b34160e01b815260048101849052602401610b6c565b506001600160a01b039290921660a09190911b67ffffffffffffffff60a01b161760e09190911b6001600160e01b0319161790565b6000808080612ab3868661407f565b9097909650945050505050565b6000803390506000836001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b06573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b2a91906152dc565b9050816001600160a01b0316816001600160a01b031614158015612bc15750816001600160a01b0316846001600160a01b03166331423c266040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b91573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612bb591906152dc565b6001600160a01b031614155b8015612c405750816001600160a01b0316846001600160a01b031663683fedf76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c10573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c3491906152dc565b6001600160a01b031614155b8015612cbf5750816001600160a01b0316846001600160a01b031663524a562f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c8f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cb391906152dc565b6001600160a01b031614155b8015612d3e5750816001600160a01b0316846001600160a01b03166389dd9f136040518163ffffffff1660e01b8152600401602060405180830381865afa158015612d0e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d3291906152dc565b6001600160a01b031614155b15610888578160405163f652640360e01b8152600401610b6c9190615016565b60006001600160401b03821115612d8b57604051633995b34160e01b815260048101839052602401610b6c565b5067ffffffffffffffff60a01b60a09190911b166001600160a01b039091161790565b8151600090612dbe6008846156d3565b10612dcb5750600061069c565b612dd66008836157df565b612de19060206156a6565b83612ded6008856156d3565b81518110612dfd57612dfd615592565b602002602001015160001c901c905092915050565b6000806000612e218686613227565b60030b905060008460030b8260030b612e3a91906157f3565b9350839050612e4d600383900b8261581b565b9250612e5a878786613d4f565b5050935093915050565b805160005b81811015612ec657828181518110612e8357612e83615592565b602002602001015160030b600014612ebe57612ebe8a8a8a8a8a868b8b8981518110612eb157612eb1615592565b60200260200101516140b9565b600101612e69565b50505050505050505050565b6000612ee16124b060096125c4565b90506000612ef36124b085600a613227565b90506000836040015163ffffffff16831015612f2c57612f1d83856040015163ffffffff16613f83565b63ffffffff1660408501525060015b836060015163ffffffff16821015612f6157612f5282856060015163ffffffff16613f83565b63ffffffff1660608501525060015b801561093557610935878786600001518760200151886040015189606001518a608001516139e9565b600061086c8484846142d1565b612f9f6142ee565b612fbc57604051631afcd79f60e31b815260040160405180910390fd5b565b6001600160a01b0381166130065760405162461bcd60e51b815260206004820152600f60248201526e2d32b9379031b7b73a3937b63632b960891b6044820152606401610b6c565b61303581610bbd60017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c361861530f565b61306442610bbd60017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b61530f565b61309343610bbd60017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f161530f565b604080516001600160a01b0383168152426020820152438183015290517f1a2dd071001ebf6e03174e3df5b305795a4ad5d41d8fdb9ba41dbbe2367134269181900360600190a150565b60606000826001600160401b038111156130f9576130f9614dc7565b604051908082528060200260200182016040528015613122578160200160208202803683370190505b50905060005b84518110156131a55760005b600881101561319c5760008161314b8460086156a6565b6131559190615322565b9050858110613164575061319c565b61316e8782612dae565b84828151811061318057613180615592565b60039290920b6020928302919091019091015250600101613134565b50600101613128565b509392505050565b604080516008808252610120820190925260609160009190602082016101008036833701905050905060005b6008811015613220576131ed8160206156a6565b8460001c901c82828151811061320557613205615592565b60039290920b602092830291909101909101526001016131d9565b5092915050565b81546000906132376008846156d3565b106132445750600061069c565b61324f6008836157df565b61325a9060206156a6565b836132666008856156d3565b8154811061327657613276615592565b600091825260209091200154901c9392505050565b6132936148b9565b60405180608001604052806040518060800160405280600f60030b8152602001600f60030b8152602001601e60030b8152602001600a60030b81525081526020016040518060e0016040528067016345785d8a000081526020016702c68af0bb1400008152602001671bc16d674ec800008152602001671bc16d674ec80000815260200167016345785d8a000081526020016714d1120d7b16000081526020016706f05b59d3b2000081525081526020016040518060400160405280671bc16d674ec800008152602001670de0b6b3a76400008152508152602001600560030b815250905090565b6133836148b9565b600061338d614308565b6040805161010081018252601e60808201908152600560a0830152601960c0830152600a60e0808401919091529082528251908101909252670214e8348c4f00008252919250602080830191908101846133ef576706f05b59d3b200006133f9565b6703782dace9d900005b6001600160401b031681526020018461341a576729a2241af62c0000613424565b671bc16d674ec800005b6001600160401b03168152602001670de0b6b3a7640000815260200167011c37937e080000815260200167120a871cc002000081526020016706f05b59d3b20000815250815260200160405180604001604052806718fae27693b400008152602001670de0b6b3a76400008152508152602001600560030b81525091505090565b6134ad6148b9565b60405180608001604052806040518060800160405280600a60030b8152602001600f60030b8152602001601460030b8152602001601960030b81525081526020016040518060e0016040528067016345785d8a000081526020016702c68af0bb1400008152602001671bc16d674ec800008152602001670de0b6b3a7640000815260200167016345785d8a00008152602001670de0b6b3a76400008152602001671bc16d674ec800008152508152602001604051806040016040528067120a871cc00200008152602001671bc16d674ec800008152508152602001600560030b815250905090565b61359d6148b9565b60405180608001604052806040518060800160405280600f60030b8152602001601960030b8152602001600f60030b8152602001600f60030b81525081526020016040518060e0016040528067016345785d8a000081526020016702c68af0bb1400008152602001673782dace9d90000081526020016729a2241af62c000081526020016702c68af0bb14000081526020016710a741a4627800008152602001670de0b6b3a7640000815250815260200160405180604001604052806717979cfe362a000081526020016714d1120d7b1600008152508152602001600560030b815250905090565b61368d6148b9565b60405180608001604052806040518060800160405280601460030b8152602001601460030b8152602001601460030b8152602001600a60030b81525081526020016040518060e00160405280670214e8348c4f000081526020016703782dace9d9000081526020016729a2241af62c000081526020016722b1c8c1227a00008152602001670214e8348c4f000081526020016714d1120d7b16000081526020016714d1120d7b160000815250815260200160405180604001604052806714d1120d7b16000081526020016714d1120d7b1600008152508152602001600160030b815250905090565b61377d6148b9565b60405180608001604052806040518060800160405280603260030b8152602001601e60030b8152602001603260030b8152602001600f60030b81525081526020016040518060e001604052806702c68af0bb1400008152602001670429d069189e00008152602001674563918244f4000081526020016729a2241af62c00008152602001670214e8348c4f00008152602001671bc16d674ec800008152602001671bc16d674ec80000815250815260200160405180604001604052806717979cfe362a000081526020016714d1120d7b1600008152508152602001600160030b815250905090565b6000610888836001600160a01b03841661431e565b600061069c82614368565b6000808080612ab38686614373565b6000610888836001600160a01b03841661439e565b60609290921b6001600160601b03191660209190911b6bffffffffffffffff00000000161760189190911b63ff000000161790565b6000670de0b6b3a764000082101561390c57604051637046c4a960e01b815260048101839052602401610b6c565b6000613928613923670de0b6b3a7640000856156d3565b6143b6565b9050600061393e82670de0b6b3a76400006156a6565b905083821c670de0b6b3a764000081900361395b57509392505050565b6706f05b59d3b200005b80156139b557670de0b6b3a764000061397e83806156a6565b61398891906156d3565b9150671bc16d674ec8000082106139ad576139a38184615322565b9250600182901c91505b60011c613965565b5090949350505050565b60008160030b8360030b126139e0576139d8828461570e565b60030b610888565b50600092915050565b63ffffffff811615801590613a02575063ffffffff8316155b15613a0c57600192505b60408051600580825260c082019092526000916020820160a0803683370190505090508581600081518110613a4357613a43615592565b602002602001019063ffffffff16908163ffffffff16815250508481600181518110613a7157613a71615592565b602002602001019063ffffffff16908163ffffffff16815250508381600281518110613a9f57613a9f615592565b602002602001019063ffffffff16908163ffffffff16815250508281600381518110613acd57613acd615592565b602002602001019063ffffffff16908163ffffffff16815250508181600481518110613afb57613afb615592565b602002602001019063ffffffff16908163ffffffff1681525050613b1e81614495565b60009788526004909801602052505060409094209490945550505050565b600061069c825490565b6000610888838361450f565b6000610888836001600160a01b038416614539565b336001600160a01b0316816001600160a01b03166331423c266040518163ffffffff1660e01b8152600401602060405180830381865afa158015613baf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613bd391906152dc565b6001600160a01b031614610db5573360405163f6b9c4d960e01b8152600401610b6c9190615016565b6000821580613c09575081155b15613c165750600061069c565b60058311613c27575080821461069c565b60068303613c46576007821480613c3f575060085b82145b905061069c565b60088303613c55576006613c3c565b60098303613c64576009613c3c565b60078303613c7357600a613c3c565b600a8303613c8257600b613c3c565b600b83036139e057600c821480613c995750600d82145b80613c3f5750600e613c3c565b600080613cb28661400e565b90508260ff16818681518110613cca57613cca615592565b602002602001015160ff1614613d1f578260ff16818681518110613cf057613cf0615592565b602002602001015160ff1660405163c230690360e01b8152600401610b6c929190918252602082015260400190565b83818681518110613d3257613d32615592565b602002602001019060ff16908160ff16815250506110a78161462c565b6000613d5c6008846156d3565b90506000613d6b6008856157df565b613d769060206156a6565b8554909150808310613dc257855460018101875560008781526020812090910155805b83811015613dc0578654600181810189556000898152602081209092019190915501613d99565b505b818463ffffffff1663ffffffff16901b8263ffffffff901b19878581548110613ded57613ded615592565b906000526020600020015460001c161760001b868481548110613e1257613e12615592565b600091825260209091200155505050505050565b600080826001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015613e67573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613e8b91906152dc565b90506001600160a01b038116331461069c57336040516303f2374960e01b8152600401610b6c9190615016565b80516000906008811115613ee957604051633d71388b60e21b81526004810182905260086024820152604401610b6c565b6000805b828110156131a557613f008160206156a6565b858281518110613f1257613f12615592565b602090810291909101015163ffffffff16901b9190911790600101613eed565b8160030b600014611b4b576000613f4a888385612e12565b509050613f5d8989898989878a886140b9565b505050505050505050565b6000808260030b13613f7c57506000919050565b5060030b90565b6000818310613f925781610888565b5090919050565b604080516008808252610120820190925260609160009190602082016101008036833701905050905060005b600881101561322057613fd98160206156a6565b8460001c901c828281518110613ff157613ff1615592565b63ffffffff90921660209283029190910190910152600101613fc5565b6040805160208082526104208201909252606091600091908082016104008036833701905050905060005b60208110156132205761404d8160086156a6565b8460001c901c82828151811061406557614065615592565b60ff90921660209283029190910190910152600101614039565b60008181526002830160205260408120548190806140ae576140a185856146a3565b9250600091506109ff9050565b6001925090506109ff565b6000886001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156140f9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061411d91906152dc565b6001600160a01b0316631789b7b1846040518263ffffffff1660e01b81526004016141489190615016565b602060405180830381865afa158015614165573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614189919061553c565b60ff16905083614200576141d860046141a560045b8990613227565b6141b260045b8b90613227565b6141bc86866146af565b6141c691906156e7565b6141d091906156e7565b8a9190613d4f565b6141fb60056141e7600561419e565b6141f160056141ab565b6141bc86866146e9565b613f5d565b600184036142715761422b6006614217600661419e565b61422160066141ab565b6141bc8686614711565b61424e600761423a600761419e565b61424460076141ab565b6141bc8686614739565b6141fb600861425d600861419e565b61426760086141ab565b6141bc8686614761565b600284036142a1576141fb6009614288600961419e565b61429260096141ab565b6141bc86868b600001516147a7565b60038403613f5d57613f5d600a6142b8600a61419e565b6142c2600a6141ab565b6141bc86868b6000015161481e565b6000828152600284016020526040812082905561086c848461487a565b60006142f861148f565b54600160401b900460ff16919050565b60006201b254461480610a3a5750504660fa1490565b600061432a838361439e565b6143605750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561069c565b50600061069c565b600061069c82613b3c565b600080806143818585613b46565b600081815260029690960160205260409095205494959350505050565b60009081526001919091016020526040902054151590565b6000600160801b82106143d657608091821c916143d39082615322565b90505b600160401b82106143f457604091821c916143f19082615322565b90505b640100000000821061441357602091821c916144109082615322565b90505b62010000821061443057601091821c9161442d9082615322565b90505b610100821061444c57600891821c916144499082615322565b90505b6010821061446757600491821c916144649082615322565b90505b6004821061448257600291821c9161447f9082615322565b90505b6002821061173d5761069c600182615322565b805160009060088111156144c657604051633d71388b60e21b81526004810182905260086024820152604401610b6c565b6000805b828110156131a5576144dd8160206156a6565b8582815181106144ef576144ef615592565b602090810291909101015163ffffffff16901b91909117906001016144ca565b600082600001828154811061452657614526615592565b9060005260206000200154905092915050565b6000818152600183016020526040812054801561462257600061455d60018361530f565b85549091506000906145719060019061530f565b90508082146145d657600086600001828154811061459157614591615592565b90600052602060002001549050808760000184815481106145b4576145b4615592565b6000918252602080832090910192909255918252600188019052604090208390555b85548690806145e7576145e761583b565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505061069c565b600091505061069c565b8051600090602081111561465d57604051633d71388b60e21b81526004810182905260206024820152604401610b6c565b6000805b828110156131a5576146748160086156a6565b85828151811061468657614686615592565b602090810291909101015160ff16901b9190911790600101614661565b60006108888383614886565b6000670de0b6b3a76400006146c3836116b9565b60200151516146d5600386900b613f68565b6146df91906156a6565b61088891906156d3565b6000670de0b6b3a76400006146fd836116b9565b60200151602001516146d58560030b613f68565b6000670de0b6b3a7640000614725836116b9565b60200151604001516146d58560030b613f68565b6000670de0b6b3a764000061474d836116b9565b60200151606001516146d58560030b613f68565b6000610888670de0b6b3a7640000614778846116b9565b602001516080015161478c8660030b613f68565b61479691906156a6565b6147a091906156d3565b604b613f83565b6000670de0b6b3a76400006147bb846116b9565b60400151516147d09063ffffffff85166156a6565b6147da91906156d3565b670de0b6b3a76400006147ec856116b9565b6020015160a001516148008760030b613f68565b61480a91906156a6565b61481491906156d3565b61086c9190615322565b6000670de0b6b3a7640000614832846116b9565b604001516020015161484a9063ffffffff85166156a6565b61485491906156d3565b670de0b6b3a7640000614866856116b9565b6020015160c001516148008760030b613f68565b6000610888838361431e565b6000610888838361439e565b60408051608081018252600080825260208201819052918101829052606081019190915290565b60405180608001604052806148cc614892565b81526020016149116040518060e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b8152602001614933604051806040016040528060008152602001600081525090565b8152600060209091015290565b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915290565b5080546000825590600052602060002090810190610db591905b8082111561499c5760008155600101614988565b5090565b6001600160a01b0381168114610db557600080fd5b60ff81168114610db557600080fd5b600080600080608085870312156149da57600080fd5b84356149e5816149a0565b935060208501359250604085013591506060850135614a03816149b5565b939692955090935050565b600060208284031215614a2057600080fd5b5035919050565b60008060408385031215614a3a57600080fd5b50508035926020909101359150565b60008060408385031215614a5c57600080fd5b8235614a67816149a0565b946020939093013593505050565b60008060008060808587031215614a8b57600080fd5b8435614a96816149a0565b966020860135965060408601359560600135945092505050565b600060208284031215614ac257600080fd5b8135610888816149a0565b600080600060608486031215614ae257600080fd5b8335614aed816149a0565b95602085013595506040909401359392505050565b60008151808452602080850194506020840160005b83811015614b3657815160030b87529582019590820190600101614b17565b509495945050505050565b6020815260006108886020830184614b02565b805160030b8252602081015160030b6020830152604081015160030b6040830152606081015160030b60608301525050565b6080810161069c8284614b54565b60006101c082019050614ba8828451614b54565b602083015180516080840152602081015160a0840152604081015160c0840152606081015160e0840152608081015161010084015260a081015161012084015260c0810151610140840152506040830151614c1161016084018280518252602090810151910152565b5060608301516132206101a084018260030b9052565b600080600060608486031215614c3c57600080fd5b8335614c47816149a0565b9250602084013591506040840135614c5e816149a0565b809150509250925092565b604080825283519082018190526000906020906060840190828701845b82811015614ca257815184529284019290840190600101614c86565b5050508381038285015284518082528583019183019060005b81811015614cd757835183529284019291840191600101614cbb565b5090979650505050505050565b80356001600160401b038116811461173d57600080fd5b600080600060608486031215614d1057600080fd5b8335614d1b816149a0565b9250614d2960208501614ce4565b91506040840135614c5e816149b5565b803563ffffffff8116811461173d57600080fd5b600060208284031215614d5f57600080fd5b61088882614d39565b600060208284031215614d7a57600080fd5b81356001600160401b03811115614d9057600080fd5b820160a0818503121561088857600080fd5b604081526000614db56040830185614b02565b90508260030b60208301529392505050565b634e487b7160e01b600052604160045260246000fd5b60405160a081016001600160401b0381118282101715614dff57614dff614dc7565b60405290565b604051601f8201601f191681016001600160401b0381118282101715614e2d57614e2d614dc7565b604052919050565b60006001600160401b03821115614e4e57614e4e614dc7565b5060051b60200190565b8060030b8114610db557600080fd5b600082601f830112614e7857600080fd5b81356020614e8d614e8883614e35565b614e05565b8083825260208201915060208460051b870101935086841115614eaf57600080fd5b602086015b84811015614ed4578035614ec781614e58565b8352918301918301614eb4565b509695505050505050565b600080600060608486031215614ef457600080fd5b8335614eff816149a0565b92506020840135915060408401356001600160401b03811115614f2157600080fd5b614f2d86828701614e67565b9150509250925092565b8015158114610db557600080fd5b600080600080600080600060e0888a031215614f6057600080fd5b8735614f6b816149a0565b9650614f7960208901614ce4565b9550604088013594506060880135614f90816149b5565b93506080880135614fa0816149a0565b925060a0880135915060c0880135614fb781614f37565b8091505092959891949750929550565b60006020808352835180602085015260005b81811015614ff557858101830151858201604001528201614fd9565b506000604082860101526040601f19601f8301168501019250505092915050565b6001600160a01b0391909116815260200190565b60006020828403121561503c57600080fd5b81356001600160401b038082111561505357600080fd5b9083019060a0828603121561506757600080fd5b61506f614ddd565b823561507a816149a0565b81526020838101359082015260408301358281111561509857600080fd5b6150a487828601614e67565b604083015250606083013591506150ba82614f37565b816060820152608083013592506150d083614f37565b6080810192909252509392505050565b60008060008084860360e08112156150f757600080fd5b8535615102816149a0565b945060208601359350604086013592506080605f198201121561512457600080fd5b50604051608081018181106001600160401b038211171561514757615147614dc7565b604052606086013561515881614e58565b8152608086013561516881614e58565b602082015260a086013561517b81614e58565b604082015260c086013561518e81614e58565b6060820152939692955090935050565b6000806000808486036101008112156151b657600080fd5b85356151c1816149a0565b94506020860135935060a0603f19820112156151dc57600080fd5b506151e5614ddd565b6151f160408701614d39565b81526151ff60608701614d39565b602082015261521060808701614d39565b604082015261522160a08701614d39565b606082015261523260c08701614d39565b6080820152915060e0850135614a0381614f37565b63ffffffff808251168352806020830151166020840152806040830151166040840152806060830151166060840152806080830151166080840152505050565b60a0810161069c8284615247565b6020808252825182820181905260009190848201906040850190845b818110156152d057835160ff16835292840192918401916001016152b1565b50909695505050505050565b6000602082840312156152ee57600080fd5b8151610888816149a0565b634e487b7160e01b600052601160045260246000fd5b8181038181111561069c5761069c6152f9565b8082018082111561069c5761069c6152f9565b6000808335601e1984360301811261534c57600080fd5b83016020810192503590506001600160401b0381111561536b57600080fd5b8060051b36038213156109ff57600080fd5b81835260006001600160fb1b0383111561539657600080fd5b8260051b80836020870137939093016020019392505050565b8381526000602060018060a01b03808616602085015260606040850152610100840185356153dc816149a0565b821660608601526020860135608086015263ffffffff6153fe60408801614d39565b1660a08601526154116060870187615335565b60a060c0880152918290529060009061012087015b8183101561544f578335615439816149a0565b8516815292850192600192909201918501615426565b61545c60808a018a615335565b898303605f190160e08b01529650945061547781878761537d565b9b9a5050505050505050505050565b805161173d81614e58565b600080604083850312156154a457600080fd5b82516001600160401b038111156154ba57600080fd5b8301601f810185136154cb57600080fd5b805160206154db614e8883614e35565b82815260059290921b830181019181810190888411156154fa57600080fd5b938201935b8385101561552157845161551281614e58565b825293820193908201906154ff565b95506155309050868201615486565b93505050509250929050565b60006020828403121561554e57600080fd5b8151610888816149b5565b634e487b7160e01b600052602160045260246000fd5b60008160030b637fffffff198103615589576155896152f9565b60000392915050565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b0393841681526020810192909252909116604082015260600190565b6001600160a01b03929092168252602082015260400190565b600060a082840312156155f657600080fd5b6155fe614ddd565b8251615609816149b5565b81526020830151615619816149b5565b6020820152604083015161562c81614f37565b6040820152606083015168ffffffffffffffffff8116811461564d57600080fd5b60608201526080830151615660816149a0565b60808201529392505050565b63ffffffff828116828216039080821115613220576132206152f9565b63ffffffff818116838216019080821115613220576132206152f9565b808202811582820484141761069c5761069c6152f9565b634e487b7160e01b600052601260045260246000fd5b6000826156e2576156e26156bd565b500490565b600381810b9083900b01637fffffff8113637fffffff198212171561069c5761069c6152f9565b600382810b9082900b03637fffffff198112637fffffff8213171561069c5761069c6152f9565b600063ffffffff80831681810361574e5761574e6152f9565b6001019392505050565b6001600160a01b0385168152602081018490526040810183905260e081016106706060830184614b54565b6001600160a01b038681168252602082018690526101208201906157aa6040840187615247565b84151560e0840152808416610100840152509695505050505050565b6000600182016157d8576157d86152f9565b5060010190565b6000826157ee576157ee6156bd565b500690565b8082018281126000831280158216821582161715615813576158136152f9565b505092915050565b8181036000831280158383131683831282161715613220576132206152f9565b634e487b7160e01b600052603160045260246000fdfe22573091f17911fb166032a3d9e0554aa73d31b7b7ddea4a4dd2995650af84bda26469706673582212202fcf5d7a378f3e19a84b59dc634aa7c8cb6073fd81d9ddd39606771bdd8a800b64736f6c63430008170033

Block Transaction Gas Used Reward
view all blocks produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.