Contract

0x11b3Cf8d16627D6E90131E99BeB5cd67941927A5

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:
GuildController

Compiler Version
v0.8.23+commit.f704f362

Optimization Enabled:
Yes with 50 runs

Other Settings:
istanbul EvmVersion
File 1 of 41 : GuildController.sol
// SPDX-License-Identifier: BUSL-1.1
/**
            ▒▓▒  ▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓███▓▓▒     ▒▒▒▒▓▓▓▒▓▓▓▓▓▓▓██▓
             ▒██▒▓▓▓▓█▓██████████████████▓  ▒▒▒▓███████████████▒
              ▒██▒▓█████████████████████▒ ▒▓██████████▓███████
               ▒███████████▓▒                   ▒███▓▓██████▓
                 █████████▒                     ▒▓▒▓███████▒
                  ███████▓      ▒▒▒▒▒▓▓█▓▒     ▓█▓████████
                   ▒▒▒▒▒   ▒▒▒▒▓▓▓█████▒      ▓█████████▓
                         ▒▓▓▓▒▓██████▓      ▒▓▓████████▒
                       ▒██▓▓▓███████▒      ▒▒▓███▓████
                        ▒███▓█████▒       ▒▒█████▓██▓
                          ██████▓   ▒▒▒▓██▓██▓█████▒
                           ▒▒▓▓▒   ▒██▓▒▓▓████████
                                  ▓█████▓███████▓
                                 ██▓▓██████████▒
                                ▒█████████████
                                 ███████████▓
      ▒▓▓▓▓▓▓▒▓                  ▒█████████▒                      ▒▓▓
    ▒▓█▒   ▒▒█▒▒                   ▓██████                       ▒▒▓▓▒
   ▒▒█▒       ▓▒                    ▒████                       ▒▓█▓█▓▒
   ▓▒██▓▒                             ██                       ▒▓█▓▓▓██▒
    ▓█▓▓▓▓▓█▓▓▓▒        ▒▒▒         ▒▒▒▓▓▓▓▒▓▒▒▓▒▓▓▓▓▓▓▓▓▒    ▒▓█▒ ▒▓▒▓█▓
     ▒▓█▓▓▓▓▓▓▓▓▓▓▒    ▒▒▒▓▒     ▒▒▒▓▓     ▓▓  ▓▓█▓   ▒▒▓▓   ▒▒█▒   ▒▓▒▓█▓
            ▒▒▓▓▓▒▓▒  ▒▓▓▓▒█▒   ▒▒▒█▒          ▒▒█▓▒▒▒▓▓▓▒   ▓██▓▓▓▓▓▓▓███▓
 ▒            ▒▓▓█▓  ▒▓▓▓▓█▓█▓  ▒█▓▓▒          ▓▓█▓▒▓█▓▒▒   ▓█▓        ▓███▓
▓▓▒         ▒▒▓▓█▓▒▒▓█▒   ▒▓██▓  ▓██▓▒     ▒█▓ ▓▓██   ▒▓▓▓▒▒▓█▓        ▒▓████▒
 ██▓▓▒▒▒▒▓▓███▓▒ ▒▓▓▓▓▒▒ ▒▓▓▓▓▓▓▓▒▒▒▓█▓▓▓▓█▓▓▒▒▓▓▓▓▓▒    ▒▓████▓▒     ▓▓███████▓▓▒
*/
pragma solidity 0.8.23;

import "../interfaces/IApplicationEvents.sol";
import "../interfaces/IGuildController.sol";
import "../lib/GuildLib.sol";
import "../proxy/Controllable.sol";
import "../relay/ERC2771Context.sol";

contract GuildController is Initializable, Controllable, ERC2771Context, IGuildController {
  //region ------------------------ CONSTANTS

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

  uint256 private constant NOT_ENTERED = 1;
  uint256 private constant ENTERED = 2;
  //endregion ------------------------ CONSTANTS

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

  function init(address controller_) external initializer {
    __Controllable_init(controller_);

    GuildLib._S().guildsParam[IGuildController.GuildsParams.BASE_FEE_2] = GuildLib.DEFAULT_BASE_FEE;
  }
  //endregion ------------------------ INITIALIZER

  //region ------------------------ ReentrancyGuard
  /// @notice Prevents a contract from calling itself, directly or indirectly.
  /// @dev Implementation is based on openzeppelin/ReentrancyGuard but no local status variable is used.
  modifier nonReentrant() {
    _nonReentrantBefore();
    _;
    _nonReentrantAfter();
  }

  function _nonReentrantBefore() private {
    // On the first call to nonReentrant, _status will be NOT_ENTERED
    if (_getReentrantStatus() == ENTERED) revert IAppErrors.ReentrancyGuardReentrantCall();

    // Any calls to nonReentrant after this point will fail
    _setReentrantStatus(ENTERED);
  }

  function _nonReentrantAfter() private {
    _setReentrantStatus(NOT_ENTERED);
  }

  function _getReentrantStatus() internal view returns (uint) {
    return GuildLib._S().guildsParam[IGuildController.GuildsParams.REENTRANT_STATUS_4];
  }

  function _setReentrantStatus(uint status) internal {
    GuildLib._S().guildsParam[IGuildController.GuildsParams.REENTRANT_STATUS_4] = status;
  }

  //endregion ------------------------ ReentrancyGuard

  //region ------------------------ Views
  function getGuildParamValue(uint paramId) external view returns (uint) {
    return GuildLib.getGuildParamValue(paramId);
  }

  function getGuildData(uint guildId) external view returns (
    string memory guildName,
    string memory urlLogo,
    address owner,
    uint8 guildLevel,
    uint64 pvpCounter,
    uint toHelperRatio
  ) {
    GuildData memory data = GuildLib.getGuildData(guildId);
    return (data.guildName, data.urlLogo, data.owner, data.guildLevel, data.pvpCounter, data.toHelperRatio);
  }

  function getGuildByName(string memory name) external view returns (uint guildId) {
    return GuildLib.getGuildByName(name);
  }

  function memberOf(address user) external override view returns (uint guildId) {
    return GuildLib.memberOf(user);
  }

  function guildMembers(uint guildId) external view returns (address[] memory) {
    return GuildLib.guildMembers(guildId);
  }

  function getRights(address user) external view returns (uint) {
    return GuildLib.getRights(user);
  }

  function isPeacefulRelation(uint guildId, uint guildId2) external view returns (bool) {
    return GuildLib.isPeacefulRelation(guildId, guildId2);
  }

  /// @notice Amount of base fee in game tokens
  function getBaseFee() external view returns (uint) {
    return GuildLib.getBaseFee();
  }

  function getGuildBank(uint guildId) external view returns (address) {
    return GuildLib.getGuildBank(guildId);
  }

  function getUserPvpPoints(uint guildId, address user) external view returns (uint64 capacityPvpPoints, uint64 spentPvpPoints) {
    return GuildLib.getUserPvpPoints(guildId, user);
  }

  /// @return guildRequestIds Return full list of guild-requests with a given status for a given guild
  /// @param status 0 - not checked, 1 - accepted, 2 - canceled, 3 - removed by the user
  function listGuildRequests(uint guildId, uint8 status) external view returns (uint[] memory guildRequestIds) {
    return GuildLib.listGuildRequests(guildId, status);
  }

  /// @return status 0 - not checked, 1 - accepted, 2 - canceled, 3 - removed by the user
  /// @return user
  /// @return guildId
  /// @return userMessage Message to the guild owner from the user
  function getGuildRequest(uint guildRequestId) external view returns (
    uint8 status,
    address user,
    uint guildId,
    string memory userMessage
  ) {
    return GuildLib.getGuildRequest(guildRequestId);
  }

  /// @notice Get all requests registered by the user and not yet accepted/rejected/canceled
  function getUserActiveGuildRequests(address user) external view returns(uint[] memory guildRequestIds) {
    return GuildLib.getUserActiveGuildRequests(user);
  }

  function getGuildRequestDepositAmount(uint guildId) external view returns (uint) {
    return GuildLib.getGuildRequestDepositAmount(guildId);
  }

  /// @notice Ensure that the {user} has given {right}, revert otherwise
  /// @notice right Value of type IGuildController.GuildRightBits
  function checkPermissions(address user, uint right) external view returns (uint guildId, uint rights) {
    return GuildLib._checkPermissions(IController(controller()), user, IGuildController.GuildRightBits(right));
  }

  /// @notice True if the given {user} has given {right} in the guild to which he belongs currently
  /// @notice right Value of type IGuildController.GuildRightBits
  function hasPermission(address user, uint8 rightBit) external view returns (bool userHasTheRight) {
    (, , userHasTheRight) = GuildLib._hasPermission(user, IGuildController.GuildRightBits(rightBit));
  }

  function getGuildDescription(uint guildId) external view returns (string memory) {
    return GuildLib.getGuildDescription(guildId);
  }

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

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

  /// @param fee Base fee value in terms of game token
  function setBaseFee(uint fee) external {
    GuildLib.setBaseFee(IController(controller()), fee);
  }

  function setShelterController(address shelterController_) external {
    GuildLib.setShelterController(IController(controller()), shelterController_);
  }

  function setShelterAuctionController(address shelterAuction_) external {
    GuildLib.setShelterAuctionController(IController(controller()), shelterAuction_);
  }

  //endregion ------------------------ Gov actions

  //region ------------------------ Actions

  /// @notice Create new guild, return ID of the new guild
  function createGuild(string memory name, string memory urlLogo, uint8 toHelperRatio) external returns (uint) {
    return GuildLib.createGuild(_isNotSmartContract(), IController(controller()), _msgSender(), name, urlLogo, toHelperRatio);
  }

  /// @notice Edit roles of the given member of the guild to which msgSender belongs
  function changeRoles(address user, uint maskRights) external {
    GuildLib.changeRoles(IController(controller()), _msgSender(), user, maskRights);
  }

  /// @notice Remove given member from the guild to which msgSender belongs
  /// @dev To delete the guild the owner should remove all members and remove himself at the end
  function removeGuildMember(address userToRemove) external {
    GuildLib.removeGuildMember(IController(controller()), _msgSender(), userToRemove);
  }

  /// @notice Increment level of the guild, pay BASE_FEE * new level
  function guildLevelUp() external {
    GuildLib.guildLevelUp(IController(controller()), _msgSender());
  }

  /// @notice Rename the guild, pay BASE_FEE
  function rename(string memory newGuildName) external {
    GuildLib.rename(IController(controller()), _msgSender(), newGuildName);
  }

  function changeLogo(string memory newLogoUrl) external {
    GuildLib.changeLogo(IController(controller()), _msgSender(), newLogoUrl);
  }

  function changeDescription(string memory newDescription) external {
    GuildLib.changeDescription(IController(controller()), _msgSender(), newDescription);
  }

  function setRelation(uint otherGuildId, bool peace) external {
    GuildLib.setRelation(IController(controller()), _msgSender(), otherGuildId, peace);
  }

  /// @notice Set helper ratio for guild reinforcement
  /// @param value Percent in the range [10..50], see constants in ReinforcementControllerLib
  function setToHelperRatio(uint8 value) external {
    GuildLib.setToHelperRatio(IController(controller()), _msgSender(), value);
  }

  /// @notice Set max amount of pvp-points that is allowed to be used by each guild member
  /// Guild owner has no limits even if capacity is set.
  function setPvpPointsCapacity(uint64 capacityPvpPoints, address[] memory users) external {
    GuildLib.setPvpPointsCapacity(IController(controller()), _msgSender(), capacityPvpPoints, users);
  }

  function transferOwnership(address newAdmin) external {
    GuildLib.transferOwnership(IController(controller()), _msgSender(), newAdmin);
  }

  //endregion ------------------------ Actions

  //region ------------------------ Guild requests
  /// @notice User sends request to join to the guild. Assume approve on request-deposit.
  /// @dev User is able to send multiple requests. But any user can belong to single guild only.
  /// Any attempts to accept request for the user that is already member of a guild will revert.
  /// @param userMessage Any info provided by the user to the guild
  function addGuildRequest(uint guildId, string memory userMessage) external {
    GuildLib.addGuildRequest(_isNotSmartContract(), IController(controller()), _msgSender(), guildId, userMessage);
  }

  /// @notice Guild owner or user with permissions accepts guild request and so add the user to the guild
  /// Deposit is returned to the user, guild-request is marked as accepted and removed from the list of user guild requests
  /// @param maskRights Set of rights of the new guild member.
  /// if NOT-admin accepts the request then {maskRights} should be equal to 0.
  /// Admin is able to set any value of {maskRights} except ADMIN_0
  function acceptGuildRequest(uint guildRequestId, uint maskRights) external nonReentrant {
    GuildLib.acceptGuildRequest(IController(controller()), _msgSender(), guildRequestId, maskRights);
  }

  /// @notice Guild owner or user with permissions rejects guild request and so doesn't add the user to the guild
  /// Deposit is returned to the user, guild-request is marked as rejected and removed from the list of user guild requests
  function rejectGuildRequest(uint guildRequestId) external nonReentrant {
    GuildLib.rejectGuildRequest(IController(controller()), _msgSender(), guildRequestId);
  }

  /// @notice The user cancels his guild request.
  /// Deposit is returned to the user, guild-request is marked as canceled and removed from the list of user guild requests
  function cancelGuildRequest(uint guildRequestId) external nonReentrant {
    GuildLib.cancelGuildRequest(IController(controller()), _msgSender(), guildRequestId);
  }

  /// @notice Set deposit amount required to create new guild request
  /// @param amount 0 is allowed
  function setGuildRequestDepositAmount(uint amount) external {
    GuildLib.setGuildRequestDepositAmount(IController(controller()), _msgSender(), amount);
  }
  //endregion ------------------------ Guild requests

  //region ------------------------ Guild bank
  /// @notice Transfer given {amount} of the given {token} from the guild bank to the given {recipient}.
  /// The guild bank belongs to the guild to which the message sender belongs.
  function transfer(address token, address recipient, uint amount) external {
    GuildLib.transfer(IController(controller()), _msgSender(), token, recipient, amount);
  }

  /// @notice Transfer given {amounts} of the given {token} from guild bank to the given {recipients}.
  /// The guild bank belongs to the guild to which the message sender belongs.
  function transferMulti(address token, address[] memory recipients, uint[] memory amounts) external {
    GuildLib.transferMulti(IController(controller()), _msgSender(), token, recipients, amounts);
  }

  function transferNftMulti(address to, address[] memory nfts, uint256[] memory tokenIds) external {
    GuildLib.transferNftMulti(IController(controller()), _msgSender(), to, nfts, tokenIds);
  }

  /// @notice Top up balance of the guild bank of the guild.
  /// The guild bank belongs to the guild to which the message sender belongs.
  function topUpGuildBank(address token, uint amount) external {
    GuildLib.topUpGuildBank(IController(controller()), _msgSender(), token, amount);
  }
  //endregion ------------------------ Guild bank

  //region ------------------------ Shelters
  function usePvpPoints(uint guildId, address user, uint64 priceInPvpPoints) external {
    return GuildLib.usePvpPoints(guildId, user, priceInPvpPoints);
  }

  function guildToShelter(uint guildId) external view returns (uint shelterId) {
    return GuildLib.guildToShelter(guildId);
  }

  function shelterController() external view returns (address) {
    return GuildLib._shelterController();
  }

  function shelterAuctionController() external view returns (address) {
    return GuildLib._shelterAuctionController();
  }

  function payFromGuildBank(uint guildId, uint shelterPrice) external {
    return GuildLib.payFromGuildBank(IController(controller()), guildId, shelterPrice);
  }

  function payFromBalance(uint amount, address from) external {
    return GuildLib.payFromBalance(IController(controller()), amount, from);
  }

  function payForAuctionBid(uint guildId, uint amount, uint bid) external {
    return GuildLib.payForAuctionBid(IController(controller()), guildId, amount, bid);
  }
  //endregion ------------------------ Shelters
}

File 2 of 41 : 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 41 : 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 41 : 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 41 : 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 41 : 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 41 : IERC165.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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

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

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

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

pragma solidity ^0.8.0;

import "./IERC165.sol";

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

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

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

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

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

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

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

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

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

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

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

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

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

pragma solidity 0.8.23;

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

interface IFightCalculator {

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

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

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

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

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

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

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

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

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

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

File 11 of 41 : IGameToken.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "./IERC20.sol";

interface IGameToken is IERC20 {

  function minter() external view returns (address);

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

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

  function setMinter(address minter_) external;

  function pause(bool value) external;

}

File 12 of 41 : 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 13 of 41 : IGuildBank.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

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

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

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

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

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

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

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

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

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

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

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

  enum GuildRequestStatus {
    NONE_0,
    ACCEPTED_1,
    REJECTED_2,
    CANCELED_3
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  struct GuildRequestDeposit {
    bool initialized;
    uint192 amount;
  }

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

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

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

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

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

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

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

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

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

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

interface IHeroController {

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

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

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

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

    // ---

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

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

    // ---

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

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

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

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


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

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

    mapping(bytes32 packedHero => HeroInfo) heroInfo;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    /// @notice Desired hero name
    string heroName;

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

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


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

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

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

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

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

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

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

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

  // ---

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

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

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

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

  function resetLifeAndMana(address hero, uint heroId) external;

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

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

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

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

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

File 16 of 41 : 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 17 of 41 : IMinter.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;


interface IMinter {

  function amountForDungeon(uint dungeonBiomeLevel, uint heroLevel) external view returns (uint);

  function mintDungeonReward(uint64 dungeonId, uint dungeonBiomeLevel, uint heroLevel) external returns (uint amount);

}

File 18 of 41 : 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 19 of 41 : IReinforcementController.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

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

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

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

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

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

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


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

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

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

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

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


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

    mapping(bytes32 packedHero => HeroInfoV2) stakedHeroesV2;

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

    mapping(uint biome => LastWindowsV2) stat24hV2;
  }


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

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

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

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

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

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

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

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

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

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

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

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

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

File 20 of 41 : IShelterAuction.sol
// SPDX-License-Identifier: BUSL-1.1

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

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

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

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

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

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

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

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

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

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

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

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

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

    uint128 positionId;

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

    uint128 sellerGuildId;
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

File 22 of 41 : 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 23 of 41 : 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 24 of 41 : ITreasury.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

import "./IItemController.sol";

interface ITreasury {

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

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

File 25 of 41 : IUserController.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.23;

interface IUserController {

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

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

    END_SLOT
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/// @notice Common internal utils
library AppLib {

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

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

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

File 27 of 41 : 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 28 of 41 : GuildLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../interfaces/IAppErrors.sol";
import "../interfaces/IApplicationEvents.sol";
import "../interfaces/IGuildController.sol";
import "../interfaces/IShelterAuction.sol";
import "../interfaces/IShelterController.sol";
import "../interfaces/IUserController.sol";
import "../lib/StringLib.sol";
import "../token/GuildBank.sol";
import "./ReinforcementControllerLib.sol";
import "./StatLib.sol";

library GuildLib {
  using EnumerableSet for EnumerableSet.UintSet;
  using EnumerableSet for EnumerableSet.AddressSet;
  using EnumerableSet for EnumerableSet.UintSet;

  //region ------------------------ Constants
  /// @dev keccak256(abi.encode(uint256(keccak256("guild.controller.main")) - 1)) & ~bytes32(uint256(0xff))
  bytes32 internal constant GUILD_CONTROLLER_STORAGE_LOCATION = 0x1c4340ff8478a236ed13a5ce46f8e8b8a6037975df340a04c54725978699d100;
  uint8 internal constant FIRST_LEVEL = 1;
  uint8 internal constant MAX_LEVEL = 10;
  /// @notice Default fee for creation guild, renaming, etc. in terms of game token
  uint internal constant DEFAULT_BASE_FEE = 10_000e18;
  uint internal constant MAX_LOGO_URL_LENGTH = 256;
  uint internal constant MAX_DESCRIPTION_LENGTH = 10240;

  uint internal constant MAX_GUILD_NAME_LENGTH = 20;
  uint internal constant MAX_GUILD_REQUEST_MESSAGE_LENGTH = 256;

  uint internal constant MAX_GUILD_MEMBERS_ON_LEVEL_1 = 10;
  uint internal constant MAX_GUILD_MEMBERS_INC_PER_LEVEL = 2;

  /// @notice Default amount of deposit required to create a guild request
  uint internal constant DEFAULT_REQUEST_GUILD_MEMBERSHIP_DEPOSIT_AMOUNT = 1e18;
  //endregion ------------------------ Constants

  //region ------------------------ Restrictions
  function _onlyEoa(bool isEoa) internal pure {
    if (!isEoa) revert IAppErrors.ErrorOnlyEoa();
  }

  function _onlyGovernance(IController controller) internal view {
    if (controller.governance() != msg.sender) revert IAppErrors.NotGovernance(msg.sender);
  }

  function _onlyDeployer(IController controller) internal view {
    if (!controller.isDeployer(msg.sender)) revert IAppErrors.ErrorNotDeployer(msg.sender);
  }

  function _onlyShelterController() internal view {
    if (msg.sender != _shelterController()) revert IAppErrors.ErrorNotShelterController();
  }

  function _notPaused(IController controller) internal view {
    if (controller.onPause()) revert IAppErrors.ErrorPaused();
  }

  function _onlyNotGuildMember(address user) internal view {
    if (_S().memberToGuild[user] != 0) revert IAppErrors.AlreadyGuildMember();
  }

  function _onlyFreeValidGuildName(string memory name) internal view {
    if (_S().nameToGuild[name] != 0) revert IAppErrors.NameTaken();
    if (bytes(name).length >= MAX_GUILD_NAME_LENGTH) revert IAppErrors.TooBigName();
    if (!StringLib.isASCIILettersOnly(name)) revert IAppErrors.WrongSymbolsInTheName();
    if (bytes(name).length == 0) revert IAppErrors.EmptyNameNotAllowed();
  }

  function _onlyValidLogo(string memory urlLogo) internal pure {
    // check logo url, empty logo and duplicates are allowed
    if (bytes(urlLogo).length >= MAX_LOGO_URL_LENGTH) revert IAppErrors.TooLongUrl();
  }

  function _onlyNotZeroAddress(address a) internal pure {
    if (a == address(0)) revert IAppErrors.ZeroAddress();
  }
  //endregion ------------------------ Restrictions

  //region ------------------------ Storage

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

  //region ------------------------ Views
  function getGuildParamValue(uint paramId) internal view returns (uint) {
    return _S().guildsParam[IGuildController.GuildsParams(paramId)];
  }

  function getGuildData(uint guildId) internal view returns (IGuildController.GuildData memory) {
    return _S().guildData[guildId];
  }

  function getGuildByName(string memory name) internal view returns (uint guildId) {
    return _S().nameToGuild[name];
  }

  function memberOf(address user) internal view returns (uint guildId) {
    return _S().memberToGuild[user];
  }

  function guildMembers(uint guildId) internal view returns (address[] memory) {
    return _S().members[guildId].values();
  }

  function getRights(address user) internal view returns (uint) {
    return _S().rights[user];
  }

  function isPeacefulRelation(uint guildId, uint guildId2) internal view returns (bool) {
    return _S().relationsPeaceful[_getGuildsPairKey(guildId, guildId2)];
  }

  function getGuildBank(uint guildId) internal view returns (address) {
    return _S().guildBanks[guildId];
  }

  function getBaseFee() internal view returns (uint) {
    return _S().guildsParam[IGuildController.GuildsParams.BASE_FEE_2];
  }

  function getGuildDescription(uint guildId) internal view returns (string memory) {
    return _S().guildDescription[guildId];
  }

  function getUserPvpPoints(uint guildId, address user) internal view returns (uint64 capacityPvpPoints, uint64 spentPvpPoints) {
    IGuildController.UserPvpPoints memory data = _S().userPvpPoints[guildId][user];
    return (data.capacityPvpPoints, data.spentPvpPoints);
  }

  /// @return guildRequestIds Return full list of guild-requests with given status for the given guild
  /// @param status 0 - not checked, 1 - accepted, 2 - canceled, 3 - removed by the user
  function listGuildRequests(uint guildId, uint8 status) internal view returns (uint[] memory guildRequestIds) {
    // assume here that the total number of guild requests of any kind is not too big
    return _S().guildRequests[guildId][IGuildController.GuildRequestStatus(status)].values();
  }

  /// @return status 0 - not checked, 1 - accepted, 2 - canceled, 3 - removed by the user
  /// @return user
  /// @return guildId
  /// @return userMessage Message to the guild owner from the user
  function getGuildRequest(uint guildRequestId) internal view returns (
    uint8 status,
    address user,
    uint guildId,
    string memory userMessage
  ) {
    IGuildController.GuildRequestData memory data = _S().guildRequestData[guildRequestId];
    return (uint8(data.status), data.user, data.guildId, data.userMessage);
  }

  /// @notice Get all requests registered by the user and not yet accepted/rejected/canceled
  function getUserActiveGuildRequests(address user) internal view returns(uint[] memory guildRequestIds) {
    return _S().userActiveGuildRequests[user].values();
  }

  /// @notice Deposit amount required to create a guild request. Amount is configured by guild, 0 is allowed.
  function getGuildRequestDepositAmount(uint guildId) internal view returns (uint) {
    IGuildController.GuildRequestDeposit memory data = _S().guildRequestDepositAmounts[guildId];
    return data.initialized
      ? data.amount
      : DEFAULT_REQUEST_GUILD_MEMBERSHIP_DEPOSIT_AMOUNT;
  }

  function guildToShelter(uint guildId) internal view returns (uint shelterId) {
    IShelterController sc = IShelterController(_shelterController());
    return address(sc) == address(0) ? 0 : sc.guildToShelter(guildId);
  }
  //endregion ------------------------ Views

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

  /// @param fee Base fee value in terms of game token
  function setBaseFee(IController controller, uint fee) external {
    _onlyGovernance(controller);

    if (fee == 0) revert IAppErrors.ZeroValueNotAllowed();

    _S().guildsParam[IGuildController.GuildsParams.BASE_FEE_2] = fee;

    emit IApplicationEvents.SetGuildBaseFee(fee);
  }

  function setShelterController(IController controller, address shelterController) external {
    _setAddress(controller, IGuildController.GuildsParams.SHELTER_CONTROLLER_5, shelterController);
    emit IApplicationEvents.SetShelterController(shelterController);
  }

  function setShelterAuctionController(IController controller, address shelterAuction) external {
    _setAddress(controller, IGuildController.GuildsParams.SHELTER_AUCTION_6, shelterAuction);
    emit IApplicationEvents.SetShelterAuction(shelterAuction);
  }

  function _setAddress(IController controller, IGuildController.GuildsParams paramId, address value) internal {
    _onlyDeployer(controller);
    if (_S().guildsParam[paramId] != 0) revert IAppErrors.AlreadyInitialized();
    _S().guildsParam[paramId] = uint160(value);
  }
  //endregion ------------------------ Gov actions

  //region ------------------------ Guild requests
  /// @notice User sends request to join to the guild. Assume approve on guild-request deposit amount.
  /// @dev User is able to send multiple requests. But any user can belong to single guild only.
  /// Any attempts to accept request for the user that is already member of a guild will revert.
  /// @param userMessage Any info provided by the user to the guild
  function addGuildRequest(
    bool isEoa,
    IController controller,
    address msgSender,
    uint guildId,
    string memory userMessage
  ) internal {
    _onlyEoa(isEoa);
    _notPaused(controller);
    _onlyNotGuildMember(msgSender);

    if (bytes(userMessage).length >= MAX_GUILD_REQUEST_MESSAGE_LENGTH) revert IAppErrors.TooLongString();

    uint depositAmount = getGuildRequestDepositAmount(guildId);
    if (depositAmount != 0) {
      // take deposit from user
      IERC20(controller.gameToken()).transferFrom(msgSender, _S().guildBanks[guildId], depositAmount);
    }

    // register new guild request
    uint guildRequestId = _generateGuildId(IGuildController.GuildsParams.COUNTER_GUILD_REQUESTS_3);
    _S().guildRequests[guildId][IGuildController.GuildRequestStatus.NONE_0].add(guildRequestId);
    _S().userActiveGuildRequests[msgSender].add(guildRequestId);
    _S().guildRequestData[guildRequestId] = IGuildController.GuildRequestData({
      guildId: guildId,
      user: msgSender,
      userMessage: userMessage,
      status: IGuildController.GuildRequestStatus.NONE_0
    });

    emit IApplicationEvents.GuildRequestRegistered(msgSender, guildId, userMessage, depositAmount);
  }

  /// @notice Guild owner or user with permissions accepts guild request and so add the user to the guild
  /// Guild-request is marked as accepted and removed from the list of user guild requests
  /// @param maskRights Set of rights of the new guild member.
  /// if NOT-admin accepts the request then {maskRights} should be equal to 0.
  /// Admin is able to set any value of {maskRights} except ADMIN_0
  function acceptGuildRequest(IController controller, address msgSender, uint guildRequestId, uint maskRights) internal {
    _changeGuildRequestStatus(controller, msgSender, guildRequestId, true, maskRights);
  }

  /// @notice Guild owner or user with permissions rejects guild request and so doesn't add the user to the guild
  /// Guild-request is marked as rejected and removed from the list of user guild requests
  function rejectGuildRequest(IController controller, address msgSender, uint guildRequestId) internal {
    _changeGuildRequestStatus(controller, msgSender, guildRequestId, false, 0);
  }

  /// @notice The user cancels his guild request.
  /// Guild-request is marked as canceled and removed from the list of user guild requests
  function cancelGuildRequest(IController controller, address msgSender, uint guildRequestId) internal {
    IGuildController.GuildRequestData storage requestData = _S().guildRequestData[guildRequestId];
    if (msgSender != requestData.user) revert IAppErrors.GuildRequestNotAvailable();
    _notPaused(controller);

    _setGuildRequestStatus(
      msgSender,
      requestData,
      requestData.guildId,
      guildRequestId,
      IGuildController.GuildRequestStatus.CANCELED_3,
      msgSender
    );
  }

  function setGuildRequestDepositAmount(IController controller, address msgSender, uint amount) external {
    (uint guildId,) = _checkPermissions(controller, msgSender, IGuildController.GuildRightBits.SET_GUILD_PARAMS_11);

    _S().guildRequestDepositAmounts[guildId] = IGuildController.GuildRequestDeposit({
      initialized: true,
      amount: uint192(amount)
    });

    emit IApplicationEvents.SetGuildRequestDepositAmount(guildId, msgSender, amount);
  }
  //endregion ------------------------ Guild requests

  //region ------------------------ Guild requests internal
  function _changeGuildRequestStatus(IController controller, address msgSender, uint guildRequestId, bool accept, uint maskRights) internal {
    (uint guildId, uint msgSenderRights) = _checkPermissions(controller, msgSender, IGuildController.GuildRightBits.ADD_MEMBER_4);

    IGuildController.GuildRequestData storage requestData = _S().guildRequestData[guildRequestId];
    if (guildId != requestData.guildId) revert IAppErrors.WrongGuild();
    address user = requestData.user;

    _setGuildRequestStatus(
      msgSender,
      requestData,
      guildId,
      guildRequestId,
      accept ? IGuildController.GuildRequestStatus.ACCEPTED_1 : IGuildController.GuildRequestStatus.REJECTED_2,
      user
    );

    if (accept) {
      if (maskRights != 0 && (msgSenderRights & _getMaskRights(IGuildController.GuildRightBits.ADMIN_0)) == 0) revert IAppErrors.NotAdminCannotAddMemberWithNotZeroRights();
      _addGuildMember(guildId, user, maskRights);
    }
  }

  /// @notice Add new member to the guild to which msgSender belongs
  function _addGuildMember(uint guildId, address newUser, uint maskRights) internal {
    IGuildController.GuildData memory guildData = _S().guildData[guildId];
    _onlyNotGuildMember(newUser);

    if ((maskRights & _getMaskRights(IGuildController.GuildRightBits.ADMIN_0)) != 0) revert IAppErrors.SecondGuildAdminIsNotAllowed();

    uint guildSize = _S().members[guildId].length();
    if (guildSize == _getMaxMembersNumber(guildData.guildLevel)) revert IAppErrors.GuildHasMaxSize(guildSize);

    _S().members[guildId].add(newUser);
    _S().memberToGuild[newUser] = guildId;
    _S().rights[newUser] = maskRights;

    emit IApplicationEvents.AddToGuild(guildId, newUser);
  }

  /// @notice Change status of the guild request, unregister it from the list of active user requests, return deposit
  function _setGuildRequestStatus(
    address msgSender,
    IGuildController.GuildRequestData storage requestData,
    uint guildId,
    uint guildRequestId,
    IGuildController.GuildRequestStatus newStatus,
    address user
  ) internal {
    mapping(IGuildController.GuildRequestStatus => EnumerableSet.UintSet) storage guildRequests = _S().guildRequests[guildId];
    if (!guildRequests[IGuildController.GuildRequestStatus.NONE_0].contains(guildRequestId)) {
      revert IAppErrors.GuildRequestNotActive();
    }

    // move request to the list with different status
    // guild owner should always be able to view any request and access user-message-info stored in the request-data
    guildRequests[IGuildController.GuildRequestStatus.NONE_0].remove(guildRequestId);
    guildRequests[newStatus].add(guildRequestId);
    requestData.status = newStatus;

    _S().userActiveGuildRequests[user].remove(guildRequestId);

    emit IApplicationEvents.GuildRequestStatusChanged(msgSender, guildRequestId, uint8(newStatus), user);
  }
  //endregion ------------------------ Guild requests internal

  //region ------------------------ Actions

  /// @notice Create new guild, return ID of the new guild
  /// @param toHelperRatio Percent of fee for guild reinforcement. Value in range [_FEE_MIN ... _TO_HELPER_RATIO_MAX]
  function createGuild(
    bool isEoa,
    IController controller,
    address msgSender,
    string memory name,
    string memory urlLogo,
    uint8 toHelperRatio
  ) internal returns (uint) {
    _onlyEoa(isEoa);
    _notPaused(controller);

    // user can be a member of a single guild only
    _onlyNotGuildMember(msgSender);
    _onlyFreeValidGuildName(name);
    validateToHelperRatio(toHelperRatio);
    _onlyValidLogo(urlLogo);

    uint guildId = _generateGuildId(IGuildController.GuildsParams.COUNTER_GUILD_IDS_1);
    IGuildController.GuildData memory guildData = IGuildController.GuildData({
      owner: msgSender,
      urlLogo: urlLogo,
      guildName: name,
      guildLevel: FIRST_LEVEL,
      pvpCounter: 0,
      toHelperRatio: toHelperRatio
    });

    _S().guildData[guildId] = guildData;
    _S().nameToGuild[name] = guildId;

    _S().members[guildId].add(msgSender);
    _S().memberToGuild[msgSender] = guildId;
    _S().rights[msgSender] = _getMaskRights(IGuildController.GuildRightBits.ADMIN_0);

    _S().guildBanks[guildId] = _deployNewGuildBank(controller.guildController(), guildId);

    // pay base fee for guild creation
    _process(controller, getBaseFee(), msgSender);

    emit IApplicationEvents.GuildCreated(guildData.owner, guildId, guildData.guildName, guildData.urlLogo);

    return guildId;
  }

  /// @notice Edit roles of the given member of the guild to which msgSender belongs
  function changeRoles(IController controller, address msgSender, address user, uint maskRights) external {
    (uint senderGuildId, uint senderRights) = _checkPermissions(controller, msgSender, IGuildController.GuildRightBits.CHANGE_ROLES_7);
    (uint userGuildId, uint userRights, bool isUserAdmin) = _hasPermission(user, IGuildController.GuildRightBits.ADMIN_0);
    if (senderGuildId != userGuildId) revert IAppErrors.NotGuildMember();

    if (
      // don't allow to change rights of the owner
      // owner is not able to change even own rights - his rights should be ADMIN_0 always
      isUserAdmin

      // don't allow to set admin rights to any other user - assume, that there is only 1 admin = owner of the guild
      || ((maskRights & _getMaskRights(IGuildController.GuildRightBits.ADMIN_0)) != 0)
    ) revert IAppErrors.ErrorForbidden(user);

    if ((senderRights & _getMaskRights(IGuildController.GuildRightBits.ADMIN_0)) == 0) {
      if (
        // don't allow to change ANY rights of the user with users-control permissions
        ((userRights & _getMaskRights(IGuildController.GuildRightBits.CHANGE_ROLES_7)) != 0)
        || ((userRights & _getMaskRights(IGuildController.GuildRightBits.REMOVE_MEMBER_5)) != 0)

        // don't allow to set users-control permissions
        || ((maskRights & _getMaskRights(IGuildController.GuildRightBits.CHANGE_ROLES_7)) != 0)
        || ((maskRights & _getMaskRights(IGuildController.GuildRightBits.REMOVE_MEMBER_5)) != 0)
      ) revert IAppErrors.AdminOnly();
    }

    _S().rights[user] = maskRights;

    emit IApplicationEvents.ChangeGuildRights(senderGuildId, user, maskRights);
  }

  /// @notice Remove given member from the guild to which msgSender belongs
  /// @dev To delete the guild the owner should remove all members and remove himself at the end
  function removeGuildMember(IController controller, address msgSender, address userToRemove) internal {
    uint guildId;
    if (msgSender == userToRemove) {
      _notPaused(controller);
      guildId = _getValidGuildId(msgSender, true); // user is always able to remove himself from a guild
    } else {
      uint maskRights;
      (guildId, maskRights) = _checkPermissions(controller, msgSender, IGuildController.GuildRightBits.REMOVE_MEMBER_5);
      _onlyNotZeroAddress(userToRemove);

      (uint userGuildId, uint userRights, bool admin) = _hasPermission(userToRemove, IGuildController.GuildRightBits.ADMIN_0);
      if (
        admin
        || userGuildId != guildId
        || (( // don't allow not-admin to remove a user with users-control permissions
            ((userRights & _getMaskRights(IGuildController.GuildRightBits.CHANGE_ROLES_7)) != 0)
            || ((userRights & _getMaskRights(IGuildController.GuildRightBits.REMOVE_MEMBER_5)) != 0)
          ) && (
            ((maskRights & _getMaskRights(IGuildController.GuildRightBits.ADMIN_0)) == 0)
        ))
      ) revert IAppErrors.ErrorForbidden(userToRemove);
    }

    IGuildController.GuildData memory guildData = _S().guildData[guildId];
    if (guildData.owner == userToRemove) {
      // owner can be removed by the last one only
      if (_S().members[guildId].length() > 1) revert IAppErrors.CannotRemoveGuildOwnerFromNotEmptyGuild();
    }

    _S().members[guildId].remove(userToRemove);
    delete _S().memberToGuild[userToRemove];
    delete _S().rights[userToRemove];

    emit IApplicationEvents.RemoveFromGuild(guildId, userToRemove);

    // Removed member can have staked heroes in guild reinforcement.
    // The heroes are NOT withdrawn automatically, the member is responsible to withdraw them himself
    // All rewards for staked heroes will continue to be transferred to guild bank until the heroes are withdrawn.

    uint guildSize = _S().members[guildId].length();
    if (guildSize == 0) {
      _deleteGuild(guildId, guildData);
    }
  }

  /// @notice Increment level of the guild, pay base_fee * new level
  function guildLevelUp(IController controller, address msgSender) internal {
    (uint guildId,) = _checkPermissions(controller, msgSender, IGuildController.GuildRightBits.LEVEL_UP_8);

    uint8 oldGuildLevel = _S().guildData[guildId].guildLevel;
    if (oldGuildLevel == MAX_LEVEL) revert IAppErrors.GuildHasMaxLevel(oldGuildLevel);

    // level up
    uint8 guildLevel = oldGuildLevel + 1;
    _S().guildData[guildId].guildLevel = guildLevel;

    // pay for level up
    _process(controller, getBaseFee() * guildLevel, msgSender);

    emit IApplicationEvents.GuildLevelUp(guildId, guildLevel);
  }

  /// @notice Rename the guild, pay base_fee
  function rename(IController controller, address msgSender, string memory newGuildName) internal {
    (uint guildId,) = _checkPermissions(controller, msgSender, IGuildController.GuildRightBits.RENAME_1);
    _onlyFreeValidGuildName(newGuildName);

    // rename
    string memory oldGuildName = _S().guildData[guildId].guildName;
    delete _S().nameToGuild[oldGuildName]; // old name is free to use now

    _S().guildData[guildId].guildName = newGuildName;
    _S().nameToGuild[newGuildName] = guildId;

    // pay for renaming
    _process(controller, getBaseFee(), msgSender);

    emit IApplicationEvents.GuildRename(guildId, newGuildName);
  }

  /// @notice Free change of the guild logo
  function changeLogo(IController controller, address msgSender, string memory newLogoUrl) internal {
    (uint guildId,) = _checkPermissions(controller, msgSender, IGuildController.GuildRightBits.CHANGE_LOGO_2);

    _onlyValidLogo(newLogoUrl);

    // free change (no payment)
    _S().guildData[guildId].urlLogo = newLogoUrl;

    emit IApplicationEvents.GuildLogoChanged(guildId, newLogoUrl);
  }

  /// @notice Free change of the guild description
  function changeDescription(IController controller, address msgSender, string memory newDescription) internal {
    (uint guildId,) = _checkPermissions(controller, msgSender, IGuildController.GuildRightBits.RENAME_1);

    if (bytes(newDescription).length >= MAX_DESCRIPTION_LENGTH) revert IAppErrors.TooLongDescription();

    _S().guildDescription[guildId] = newDescription;

    emit IApplicationEvents.GuildDescriptionChanged(guildId, newDescription);
  }

  /// @notice Set relation between two guilds
  function setRelation(IController controller, address msgSender, uint otherGuildId, bool peace) internal {
    (uint guildId,) = _checkPermissions(controller, msgSender, IGuildController.GuildRightBits.SET_RELATION_KIND_9);

    // todo check alliances: it's not allowed to set war-relation to the co-member of the alliance

    _S().relationsPeaceful[_getGuildsPairKey(guildId, otherGuildId)] = peace;

    emit IApplicationEvents.SetGuildRelation(guildId, otherGuildId, peace);
  }

  function setToHelperRatio(IController controller, address msgSender, uint8 value) external {
    (uint guildId,) = _checkPermissions(controller, msgSender, IGuildController.GuildRightBits.SET_GUILD_PARAMS_11);
    validateToHelperRatio(value);

    _S().guildData[guildId].toHelperRatio = value;

    emit IApplicationEvents.SetToHelperRatio(guildId, value, msgSender);
  }

  function setPvpPointsCapacity(IController controller, address msgSender, uint64 capacityPvpPoints, address[] memory users) external {
    (uint senderGuildId,) = _checkPermissions(controller, msgSender, IGuildController.GuildRightBits.CHANGE_PURCHASING_SHELTER_ITEMS_CAPACITY_12);

    uint len = users.length;
    for (uint i; i < len; ++i) {
      uint userGuildId = _S().memberToGuild[users[i]];
      if (senderGuildId != userGuildId) revert IAppErrors.NotGuildMember();

      _S().userPvpPoints[senderGuildId][users[i]].capacityPvpPoints = capacityPvpPoints;
    }

    emit IApplicationEvents.SetPvpPointsCapacity(msgSender, capacityPvpPoints, users);
  }

  function transferOwnership(IController controller, address msgSender, address newAdmin) external {
    (uint oldAdminGuildId,) = _checkPermissions(controller, msgSender, IGuildController.GuildRightBits.ADMIN_0);
    uint newAdminGuildId = _getValidGuildId(newAdmin, true);
    if (oldAdminGuildId != newAdminGuildId) revert IAppErrors.WrongGuild();

    _S().rights[msgSender] = 0;
    _S().rights[newAdmin] = _getMaskRights(IGuildController.GuildRightBits.ADMIN_0);
    _S().guildData[oldAdminGuildId].owner = newAdmin;
  }
  //endregion ------------------------ Actions

  //region ------------------------ Guild bank
  /// @notice Deploy guild bank contract instance
  function _deployNewGuildBank(address guildController, uint guildId) internal returns (address deployed) {
    // Assume that this internal function can be called by GuildController only, so there are no restriction checks here
    deployed = address(new GuildBank(guildController, guildId));

    emit IApplicationEvents.GuildBankDeployed(guildId, deployed);
  }

  /// @notice Transfer given {amount} of {token} from guild bank to {recipient}
  function transfer(IController controller, address msgSender, address token, address recipient, uint amount) internal {
    IGuildBank guildBank = _getGuildBankCheckBankOperationPermission(controller, msgSender, IGuildController.GuildRightBits.BANK_TOKENS_OPERATION_6);
    _transferFromGuildBank(guildBank, msgSender, token, recipient, amount);
  }

  function transferMulti(IController controller, address msgSender, address token, address[] memory recipients, uint[] memory amounts) internal {
    IGuildBank guildBank = _getGuildBankCheckBankOperationPermission(controller, msgSender, IGuildController.GuildRightBits.BANK_TOKENS_OPERATION_6);

    uint len = recipients.length;
    if (len != amounts.length) revert IAppErrors.LengthsMismatch();

    for (uint i; i < len; ++i) {
      _transferFromGuildBank(guildBank, msgSender, token, recipients[i], amounts[i]);
    }
  }

  /// @notice Transfer given {nfts} from guild bank to {recipient}
  function transferNftMulti(IController controller, address msgSender, address recipient, address[] memory nfts, uint256[] memory tokenIds) internal {
    IGuildBank guildBank = _getGuildBankCheckBankOperationPermission(controller, msgSender, IGuildController.GuildRightBits.BANK_ITEMS_OPERATION_10);

    guildBank.transferNftMulti(recipient, nfts, tokenIds);
    emit IApplicationEvents.TransferNftFromGuildBank(msgSender, nfts, tokenIds, recipient);
  }

  function topUpGuildBank(IController controller, address msgSender, address token, uint amount) internal {
    // no restrictions - any guild member is allowed to top up balance of the guild bank
    _notPaused(controller);

    if (amount != 0) {
      uint guildId = _getValidGuildId(msgSender, true);
      IGuildBank guildBank = IGuildBank(_S().guildBanks[guildId]);

      IERC20(token).transferFrom(msgSender, address(guildBank), amount);
      emit IApplicationEvents.TopUpGuildBank(msgSender, guildId, address(guildBank), amount);
    }
  }

  //endregion ------------------------ Guild bank

  //region ------------------------ Shelters

  function usePvpPoints(uint guildId, address user, uint64 priceInPvpPoints) external {
    _onlyShelterController();

    // the guild should have enough PVP points
    IGuildController.GuildData storage guildData = _S().guildData[guildId];
    uint64 pvpCounter = guildData.pvpCounter;
    if (pvpCounter < priceInPvpPoints) revert IAppErrors.GuildHasNotEnoughPvpPoints(pvpCounter, priceInPvpPoints);

    guildData.pvpCounter = pvpCounter - priceInPvpPoints;

    // the user should have permission to use required amount of PVP-points from the guild balance
    IGuildController.UserPvpPoints storage userPvpPoints = _S().userPvpPoints[guildId][user];
    IGuildController.UserPvpPoints memory pvpPointsLocal = userPvpPoints;

    if (pvpPointsLocal.spentPvpPoints + priceInPvpPoints > pvpPointsLocal.capacityPvpPoints && guildData.owner != user) {
      revert IAppErrors.NotEnoughPvpPointsCapacity(user, pvpPointsLocal.spentPvpPoints, priceInPvpPoints, pvpPointsLocal.capacityPvpPoints);
    }

    userPvpPoints.spentPvpPoints = pvpPointsLocal.spentPvpPoints + priceInPvpPoints;
  }

  /// @notice pay for the shelter from the guild bank
  function payFromGuildBank(IController controller, uint guildId, uint shelterPrice) external {
    _onlyShelterController();

    address gameToken = controller.gameToken();

    address guildBank = getGuildBank(guildId);
    if (IERC20(gameToken).balanceOf(guildBank) < shelterPrice) revert IAppErrors.NotEnoughGuildBankBalance(guildId);

    IGuildBank(guildBank).approve(gameToken, address(controller), shelterPrice);
    controller.process(gameToken, shelterPrice, guildBank);
  }

  function payFromBalance(IController controller, uint amount, address from) internal {
    _onlyShelterController();

    _process(controller, amount, from);
  }

  function payForAuctionBid(IController controller, uint guildId, uint amount, uint bid) external {
    address shelterAuction = _shelterAuctionController();
    if (shelterAuction == address(0)) revert IAppErrors.NotInitialized();

    if (msg.sender != shelterAuction) revert IAppErrors.OnlyShelterAuction();

    if (amount != 0) {
      address guildBank = getGuildBank(guildId);
      IGuildBank(guildBank).transfer(controller.gameToken(), shelterAuction, amount);

      emit IApplicationEvents.PayForBidFromGuildBank(guildId, amount, bid);
    }
  }

  //endregion ------------------------ Shelters

  //region ------------------------ Internal logic
  /// @notice Check if the {user} has given permission in the guild. Permissions are specified by bitmask {rights}.
  /// Admin is marked by zero bit, he has all permissions always.
  function _checkPermissions(IController controller, address user, IGuildController.GuildRightBits right) internal view returns (uint guildId, uint rights) {
    _notPaused(controller);

    bool userHasRight;
    (guildId, rights, userHasRight) = _hasPermission(user, right);
    if (guildId == 0) revert IAppErrors.NotGuildMember();
    if (!userHasRight) {
      revert IAppErrors.GuildActionForbidden(uint(right));
    }
  }

  /// @notice Check if the {user} has given permission in the guild, no revert.
  function _hasPermission(address user, IGuildController.GuildRightBits rightBit) internal view returns (uint guildId, uint rights, bool userHasRight) {
    guildId = _getValidGuildId(user, false);
    rights = _S().rights[user];
    userHasRight = (
      (rights & _getMaskRights(IGuildController.GuildRightBits.ADMIN_0)) != 0
      || (rights & _getMaskRights(rightBit)) != 0
    );
  }

  function _getValidGuildId(address user, bool revertOnZero) internal view returns (uint guildId) {
    guildId = _S().memberToGuild[user];
    if (guildId == 0 && revertOnZero) revert IAppErrors.NotGuildMember();
  }

  function _getGuildBankCheckBankOperationPermission(
    IController controller,
    address user,
    IGuildController.GuildRightBits right
  ) internal view returns (IGuildBank guildBank) {
    (uint guildId,) = _checkPermissions(controller, user, right);
    return IGuildBank(_S().guildBanks[guildId]);
  }

  /// @notice Max number of guild members depends on guildLevel as 25 + 5 * level
  function _getMaxMembersNumber(uint8 guildLevel) internal pure returns (uint) {
    return MAX_GUILD_MEMBERS_ON_LEVEL_1 + (guildLevel - 1) * MAX_GUILD_MEMBERS_INC_PER_LEVEL;
  }

  /// @notice Generate unique pair key for (G1, G2). Guarantee that F(G1, G2) == F(G2, G1)
  function _getGuildsPairKey(uint guildId1, uint guildId2) internal pure returns (bytes32) {
    return guildId1 < guildId2
      ? keccak256(abi.encodePacked(guildId1, guildId2))
      : keccak256(abi.encodePacked(guildId2, guildId1));
  }

  /// @notice Generate mask-rights with given permission
  function _getMaskRights(IGuildController.GuildRightBits right) internal pure returns (uint) {
    return 2 ** uint(right);
  }

  function validateToHelperRatio(uint8 toHelperRatio) internal pure {
    if (toHelperRatio > ReinforcementControllerLib._TO_HELPER_RATIO_MAX) revert IAppErrors.MaxFee(toHelperRatio);
    if (toHelperRatio < ReinforcementControllerLib._FEE_MIN) revert IAppErrors.MinFee(toHelperRatio);
  }

  function _process(IController controller, uint amount, address from) internal {
    controller.process(controller.gameToken(), amount, from);
  }

  /// @notice Delete the guild as soon as last member has left it
  function _deleteGuild(uint guildId, IGuildController.GuildData memory guildData) internal{

    delete _S().nameToGuild[guildData.guildName];
    delete _S().guildData[guildId];

    // guild bank is not cleared, guildId is never reused

    IShelterController sc = IShelterController(_shelterController());
    if (address(sc) != address(0)) {
      sc.clearShelter(guildId);
    }

    // ensure that the guild has no bid to purchase any shelter
    address shelterAuction = _shelterAuctionController();
    if (shelterAuction != address(0)) {
      (uint positionId, ) = IShelterAuction(shelterAuction).positionByBuyer(guildId);
      if (positionId != 0) revert IAppErrors.AuctionBidOpened(positionId);
    }

    emit IApplicationEvents.GuildDeleted(guildId);
  }

  /// @notice Generate id for new guild, increment id-counter
  /// @dev uint is used to store id. In the code of auction we assume that it's safe to use uint128 to store such ids
  function _generateGuildId(IGuildController.GuildsParams guildParamId) internal returns (uint uid) {
    uid = _S().guildsParam[guildParamId] + 1;
    _S().guildsParam[guildParamId] = uid;
  }

  function _shelterController() internal view returns (address) {
    return address(uint160(_S().guildsParam[IGuildController.GuildsParams.SHELTER_CONTROLLER_5]));
  }

  function _shelterAuctionController() internal view returns (address) {
    return address(uint160(_S().guildsParam[IGuildController.GuildsParams.SHELTER_AUCTION_6]));
  }

  function _transferFromGuildBank(IGuildBank guildBank, address msgSender, address token, address recipient, uint amount) internal {
    if (amount != 0) {
      _onlyNotZeroAddress(recipient);
      guildBank.transfer(token, recipient, amount);
      emit IApplicationEvents.TransferFromGuildBank(msgSender, token, amount, recipient);
    }
  }
  //endregion ------------------------ Internal logic
}

File 29 of 41 : 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 30 of 41 : ReinforcementControllerLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import "../openzeppelin/Math.sol";
import "../interfaces/IApplicationEvents.sol";
import "../interfaces/IDungeonFactory.sol";
import "../interfaces/IERC20.sol";
import "../interfaces/IERC721.sol";
import "../interfaces/IGuildController.sol";
import "../interfaces/IHeroController.sol";
import "../interfaces/IGameToken.sol";
import "../interfaces/IMinter.sol";
import "../interfaces/IOracle.sol";
import "../interfaces/ITreasury.sol";
import "../interfaces/IReinforcementController.sol";
import "../lib/CalcLib.sol";
import "../lib/PackingLib.sol";
import "../lib/AppLib.sol";

library ReinforcementControllerLib {
  using EnumerableSet for EnumerableSet.Bytes32Set;
  using EnumerableMap for EnumerableMap.AddressToUintMap;
  using EnumerableMap for EnumerableMap.Bytes32ToUintMap;
  using PackingLib for bytes32;
  using PackingLib for address;
  using PackingLib for uint8[];

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

  /// @dev keccak256(abi.encode(uint256(keccak256("reinforcement.controller.main")) - 1)) & ~bytes32(uint256(0xff))
  bytes32 internal constant MAIN_STORAGE_LOCATION = 0x5a053c541e08c6bd7dfc3042a100e83af246544a23ecda1a47bf22b441b00c00;
  uint internal constant _SEARCH_WINDOW = 100;
  int32 internal constant _ATTRIBUTES_RATIO = 20;
  uint internal constant _FEE_MIN = 10;
  uint internal constant _TO_HELPER_RATIO_MAX = 50;
  uint internal constant _STAKE_REDUCE_DELAY = 7 days;
  uint internal constant _DELAY_FACTOR = 2;
  uint internal constant _SIP001_COUNT_REQUIRED_SKILLS = 3;

  /// @notice Min level of shelter where guild reinforcement is allowed. 2, 3 - allowed, 1 - forbidden.
  uint internal constant MIN_SHELTER_LEVEL_GUILD_REINFORCEMENT_ALLOWED = 2;

  /// @notice Guild hero staking is not allowed during following period after withdrawing the hero
  uint internal constant HERO_COOLDOWN_PERIOD_AFTER_GUILD_HERO_WITHDRAWING = 1 days;

  uint internal constant STATUS_HELPER_FREE = 0;

  /// @notice 24 hours is divided on "baskets". Each basket covers given interval of the hours.
  uint constant internal BASKET_INTERVAL = 3;
  //endregion ------------------------ Constants

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

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

  /// @notice Ensure that the user is a member of a guild, the guild has a shelter and the shelter has level > 1
  function onlyGuildWithShelterEnoughLevel(IGuildController gc, uint guildId) internal view {
    uint shelterId = gc.guildToShelter(guildId);
    if (shelterId == 0) revert IAppErrors.GuildHasNoShelter();

    (, uint8 shelterLevel, ) = PackingLib.unpackShelterId(shelterId);
    if (shelterLevel < MIN_SHELTER_LEVEL_GUILD_REINFORCEMENT_ALLOWED) revert IAppErrors.ShelterHasNotEnoughLevelForReinforcement();
  }

  function onlyNotPausedEoaOwner(bool isEoa, IController controller, address msgSender, address heroToken, uint heroId) internal view {
    if (!isEoa) revert IAppErrors.ErrorOnlyEoa();
    if (controller.onPause()) revert IAppErrors.ErrorPaused();
    if (IERC721(heroToken).ownerOf(heroId) != msgSender) revert IAppErrors.ErrorNotOwner(heroToken, heroId);
  }

  function onlyDungeonFactory(IController controller) internal view {
    if (controller.dungeonFactory() != msg.sender) revert IAppErrors.ErrorNotDungeonFactory(msg.sender);
  }

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

  function _checkStakeAllowed(bool isEoa, IController controller, address msgSender, address heroToken, uint heroId)
  internal view returns (IHeroController){
    onlyNotPausedEoaOwner(isEoa, controller, msgSender, heroToken, heroId);

    IHeroController hc = IHeroController(controller.heroController());
    if (hc.heroClass(heroToken) == 0) revert IAppErrors.ErrorHeroIsNotRegistered(heroToken);

    if (IDungeonFactory(controller.dungeonFactory()).currentDungeon(heroToken, heroId) != 0) revert IAppErrors.HeroInDungeon();
    if (isStaked(heroToken, heroId)) revert IAppErrors.AlreadyStaked();

    return hc;
  }

  function _checkWithdrawAllowed(bool isEoa, IController controller, address msgSender, address heroToken, uint heroId) internal view {
    onlyNotPausedEoaOwner(isEoa, controller, msgSender, heroToken, heroId);
    if (IHeroController(controller.heroController()).heroClass(heroToken) == 0) revert IAppErrors.ErrorHeroIsNotRegistered(heroToken);
  }

  function _memberOf(IController controller, address msgSender) internal view returns (IGuildController gc, uint guildId) {
    gc = IGuildController(controller.guildController());
    guildId = gc.memberOf(msgSender);
    if (guildId == 0) revert IAppErrors.NotGuildMember();
  }
  //endregion ------------------------ Restrictions

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

  function _S() internal pure returns (IReinforcementController.MainState storage s) {
    assembly {
      s.slot := MAIN_STORAGE_LOCATION
    }
    return s;
  }

  function toHelperRatio(IController controller, address heroToken, uint heroId) internal view returns (uint) {
    // Assume that this function is called by dungeonLib before reinforcement releasing
    // so for guild-reinforcement we can detect guild by guildHelperOf
    uint guildId = busyGuildHelperOf(heroToken, heroId);
    if (guildId == 0) {
      // Helper doesn't receive any reward at the end of the dungeon in Reinforcement V2
      // fixed reward-amount is paid to the helper in askHeroV2, that's all
      return 0;
    }  else {
      // guild reinforcement
      (, , , , , uint _toHelperRatio) = IGuildController(controller.guildController()).getGuildData(guildId);
      return _toHelperRatio;
    }
  }

  function heroInfo(address heroToken, uint heroId) internal view returns (IReinforcementController.HeroInfo memory) {
    return unpackHeroInfo(_S()._stakedHeroes[heroToken.packNftId(heroId)]);
  }

  function heroInfoV2(address heroToken, uint heroId) internal view returns (IReinforcementController.HeroInfoV2 memory) {
    return _S().stakedHeroesV2[heroToken.packNftId(heroId)];
  }

  /// @notice Check if the hero is staked using classic or guild reinforcement
  function isStaked(address heroToken, uint heroId) internal view returns (bool) {
    return isStakedV2(heroToken, heroId)
      || getStakedHelperGuild(heroToken, heroId) != 0
      || isStakedV1(heroToken, heroId);
  }

  function isStakedV1(address heroToken, uint heroId) internal view returns (bool) {
    return heroInfo(heroToken, heroId).biome != 0;
  }

  function isStakedV2(address heroToken, uint heroId) internal view returns (bool) {
    return heroInfoV2(heroToken, heroId).biome != 0;
  }

  /// @return Return the guild in which the hero is staked for guild reinforcement
  function getStakedHelperGuild(address heroToken, uint heroId) internal view returns (uint) {
    return _S().stakedGuildHeroes[heroToken.packNftId(heroId)];
  }

  function stakedGuildHelpersLength(uint guildId) internal view returns (uint) {
    return _S().guildHelpers[guildId].length();
  }

  function stakedGuildHelperByIndex(uint guildId, uint index) internal view returns (
    address helper,
    uint helperId,
    uint busyInGuildId
  ) {
    bytes32 packedHelper;
    (packedHelper, busyInGuildId) =  _S().guildHelpers[guildId].at(index);
    (helper, helperId) = PackingLib.unpackNftId(packedHelper);
  }

  function earned(address heroToken, uint heroId) internal view returns (
    address[] memory tokens,
    uint[] memory amounts,
    address[] memory nfts,
    uint[] memory ids
  ){
    EnumerableMap.AddressToUintMap storage erc20Rewards = _S()._heroTokenRewards[heroToken.packNftId(heroId)];
    uint length = erc20Rewards.length();
    tokens = new address[](length);
    amounts = new uint[](length);
    for (uint i; i < length; ++i) {
      (tokens[i], amounts[i]) = erc20Rewards.at(i);
    }

    bytes32[] storage nftRewards = _S()._heroNftRewards[heroToken.packNftId(heroId)];
    length = nftRewards.length;
    nfts = new address[](length);
    ids = new uint[](length);
    for (uint i; i < length; ++i) {
      (nfts[i], ids[i]) = PackingLib.unpackNftId(nftRewards[i]);
    }
  }

  /// @notice Return the guild in which the hero is currently asked for guild reinforcement
  function busyGuildHelperOf(address heroToken, uint heroId) internal view returns (uint guildId) {
    return _S().busyGuildHelpers[heroToken.packNftId(heroId)];
  }

  /// @notice Return moment of last withdrawing of the hero from guild reinforcement
  function lastGuildHeroWithdrawTs(address heroToken, uint heroId) internal view returns (uint guildId) {
    return _S().lastGuildHeroWithdrawTs[heroToken.packNftId(heroId)];
  }

  function getConfigV2() internal view returns (uint32 minNumberHits, uint32 maxNumberHits, uint32 lowDivider, uint32 highDivider, uint8 levelLimit) {
    return PackingLib.unpackConfigReinforcementV2(
      bytes32(_S().configParams[IReinforcementController.ConfigParams.V2_MIN_MAX_BOARD_0])
    );
  }

  function getFeeAmount(address gameToken, uint hitsLast24h, uint8 biome) internal view returns (uint feeAmount) {
    return _getFeeAmount(gameToken, hitsLast24h, biome);
  }

  function getHitsNumberPerLast24Hours(uint8 biome, uint blockTimestamp) internal view returns (uint hitsLast24h) {
    IReinforcementController.LastWindowsV2 memory stat24h = _S().stat24hV2[biome];
    (hitsLast24h, ) = getHitsNumberPerLast24Hours(blockTimestamp, BASKET_INTERVAL, stat24h);
  }

  function getLastWindowsV2(uint8 biome) internal view returns (IReinforcementController.LastWindowsV2 memory) {
    return _S().stat24hV2[biome];
  }

  function heroesByBiomeV2Length(uint8 biome) internal view returns (uint) {
    return _S().heroesByBiomeV2[biome].length();
  }

  function heroesByBiomeV2ByIndex(uint8 biome, uint index) internal view returns (address helper, uint helperId) {
    bytes32 packedHelper =_S().heroesByBiomeV2[biome].at(index);
    (helper, helperId) = PackingLib.unpackNftId(packedHelper);
  }

  function heroesByBiomeV2(uint8 biome) internal view returns (address[] memory helpers, uint[] memory helperIds) {
    EnumerableSet.Bytes32Set storage packedHeroes = _S().heroesByBiomeV2[biome];
    uint len = packedHeroes.length();

    helpers = new address[](len);
    helperIds = new uint[](len);

    for (uint i; i < len; ++i) {
      (helpers[i], helperIds[i]) = PackingLib.unpackNftId(packedHeroes.at(i));
    }
    return (helpers, helperIds);
  }

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

  //region ------------------------ GOV ACTIONS
  function setConfigV2(IController controller, IReinforcementController.ConfigReinforcementV2 memory config) internal {
    onlyDeployer(controller);
    _S().configParams[IReinforcementController.ConfigParams.V2_MIN_MAX_BOARD_0] = uint(
      PackingLib.packConfigReinforcementV2(config.minNumberHits, config.maxNumberHits, config.lowDivider, config.highDivider, config.levelLimit)
    );
  }

  //endregion ------------------------ GOV ACTIONS

  //region ------------------------ Reinforcement V1
  /// @notice Reverse operation for {stakeHero}
  function withdrawHero(bool isEoa, IController controller, address msgSender, address heroToken, uint heroId) internal {
    IReinforcementController.MainState storage s = _S();

    _checkWithdrawAllowed(isEoa, controller, msgSender, heroToken, heroId);

    (uint8 biome, , , ) = PackingLib.unpackReinforcementHeroInfo(s._stakedHeroes[heroToken.packNftId(heroId)]);
    if (biome == 0) revert IAppErrors.NotStaked();

    s._internalIdsByBiomes[biome].remove(heroToken.packNftId(heroId));
    delete s._stakedHeroes[heroToken.packNftId(heroId)];

    emit IApplicationEvents.HeroWithdraw(heroToken, heroId);
  }

  function unpackHeroInfo(bytes32 packed) internal pure returns (IReinforcementController.HeroInfo memory info) {
    (info.biome, info.score, info.fee, info.stakeTs) = PackingLib.unpackReinforcementHeroInfo(packed);
    return info;
  }
  //endregion ------------------------ Reinforcement V1

  //region ------------------------ Rewards for reinforcement of any kind
  /// @notice For classic reinforcement: register reward in _S(), keep tokens on balance of this contract
  /// For guild reinforcement: re-send reward to the guild bank.
  /// @dev Only for dungeon. Assume the tokens already sent to this contract.
  function registerTokenReward(IController controller, address heroToken, uint heroId, address token, uint amount) internal {
    onlyDungeonFactory(controller);

    uint guildId = busyGuildHelperOf(heroToken, heroId);
    if (guildId == 0) {
      // classic reinforcement: save all rewards to _heroTokenRewards
      EnumerableMap.AddressToUintMap storage rewards = _S()._heroTokenRewards[heroToken.packNftId(heroId)];

      (,uint existAmount) = rewards.tryGet(token);
      rewards.set(token, existAmount + amount);

      emit IApplicationEvents.TokenRewardRegistered(heroToken, heroId, token, amount, existAmount + amount);
    } else {
      // guild reinforcement: send all rewards to guild bank
      address guildBank = IGuildController(controller.guildController()).getGuildBank(guildId);
      IERC20(token).transfer(guildBank, amount);
      emit IApplicationEvents.GuildTokenRewardRegistered(heroToken, heroId, token, amount, guildId);
    }
  }

  /// @notice For classic reinforcement: register reward in _S(), keep the token on balance of this contract
  /// For guild reinforcement: re-send NFT-reward to the guild bank.
  /// @dev Only for dungeon. Assume the NFT already sent to this contract.
  function registerNftReward(IController controller, address heroToken, uint heroId, address token, uint tokenId) internal {
    onlyDungeonFactory(controller);

    uint guildId = busyGuildHelperOf(heroToken, heroId);
    if (guildId == 0) {
        // classic reinforcement: save all rewards to _heroNftRewards
      _S()._heroNftRewards[heroToken.packNftId(heroId)].push(token.packNftId(tokenId));

      emit IApplicationEvents.NftRewardRegistered(heroToken, heroId, token, tokenId);
    } else {
      // guild reinforcement: send all rewards to guild bank
      address guildBank = IGuildController(controller.guildController()).getGuildBank(guildId);
      IERC721(token).transferFrom(address(this), guildBank, tokenId);

      emit IApplicationEvents.GuildNftRewardRegistered(heroToken, heroId, token, tokenId, guildId);
    }
  }

  function claimAll(bool isEoa, IController controller, address msgSender, address heroToken, uint heroId) internal {
    onlyNotPausedEoaOwner(isEoa, controller, msgSender, heroToken, heroId);

    _claimAllTokenRewards(heroToken, heroId, msgSender);
    _claimAllNftRewards(heroToken, heroId, msgSender);
  }

  function claimNft(
    bool isEoa,
    IController controller,
    address msgSender,
    address heroToken,
    uint heroId,
    uint countNft
  ) internal {
    onlyNotPausedEoaOwner(isEoa, controller, msgSender, heroToken, heroId);

    _claimNftRewards(heroToken, heroId, msgSender, countNft);
  }
  //endregion ------------------------ Rewards for reinforcement of any kind

  //region ------------------------ Guild reinforcement
  function stakeGuildHero(bool isEoa, IController controller, address msgSender, address heroToken, uint heroId) internal {
    _checkStakeAllowed(isEoa, controller, msgSender, heroToken, heroId);

    IReinforcementController.MainState storage s = _S();
    bytes32 packedHero = heroToken.packNftId(heroId);

    (, uint guildId) = _memberOf(controller, msgSender);

    uint lastGuildHeroWithdraw = s.lastGuildHeroWithdrawTs[packedHero];
    if (block.timestamp - HERO_COOLDOWN_PERIOD_AFTER_GUILD_HERO_WITHDRAWING < lastGuildHeroWithdraw) revert IAppErrors.GuildReinforcementCooldownPeriod();

    s.stakedGuildHeroes[packedHero] = guildId;

    // there is a chance that the hero is being used in reinforcement as result of previous staking
    uint busyByGuidId = s.busyGuildHelpers[packedHero];
    s.guildHelpers[guildId].set(packedHero, busyByGuidId);

    emit IApplicationEvents.GuildHeroStaked(heroToken, heroId, guildId);
  }

  function withdrawGuildHero(bool isEoa, IController controller, address msgSender, address heroToken, uint heroId) internal {
    _checkWithdrawAllowed(isEoa, controller, msgSender, heroToken, heroId);

    IReinforcementController.MainState storage s = _S();
    bytes32 packedHero = heroToken.packNftId(heroId);

    uint guildId = s.stakedGuildHeroes[packedHero];
    if (guildId == 0) revert IAppErrors.NotStakedInGuild();

    delete s.stakedGuildHeroes[packedHero];
    if (s.guildHelpers[guildId].contains(packedHero)) {
      s.guildHelpers[guildId].remove(packedHero);
    }

    s.lastGuildHeroWithdrawTs[packedHero] = block.timestamp;

    emit IApplicationEvents.GuildHeroWithdrawn(heroToken, heroId, guildId);
  }

  /// @param hero Assume that the hero has no reinforcement, it's checked inside ItemController
  /// @param helper Desired helper. It should be staked by a member of the user's guild.
  function askGuildHero(IController controller, address hero, uint heroId, address helper, uint helperId) internal returns (
    int32[] memory attributes
  ) {
    onlyHeroController(controller);

    address user = IERC721(hero).ownerOf(heroId);
    IReinforcementController.MainState storage s = _S();
    (IGuildController gc, uint guildId) = _memberOf(controller, user);

    onlyGuildWithShelterEnoughLevel(gc, guildId);

    // ensure that the helper is free
    bytes32 packedHelper = PackingLib.packNftId(helper, helperId);
    EnumerableMap.Bytes32ToUintMap storage guildHelpers = s.guildHelpers[guildId];
    if (!guildHelpers.contains(packedHelper)) revert IAppErrors.GuildHelperNotAvailable(guildId, helper, helperId);

    // mark the helper as busy
    guildHelpers.set(packedHelper, guildId);
    s.busyGuildHelpers[packedHelper] = guildId;

    attributes = _getReinforcementAttributes(controller, helper, helperId);

    emit IApplicationEvents.GuildHeroAsked(helper, helperId, guildId, user);
    return attributes;
  }

  function releaseGuildHero(IController controller, address helperHeroToken, uint helperHeroTokenId) internal {
    onlyHeroController(controller);

    bytes32 packedHero = helperHeroToken.packNftId(helperHeroTokenId);
    IReinforcementController.MainState storage s = _S();

    address owner;
    try IERC721(helperHeroToken).ownerOf(helperHeroTokenId) returns (address heroOwner) {
      // there is a chance that the helperId is already burnt
      // see test "use guild reinforcement - burn the helper-hero that is being used by reinforcement"
      // so, we cannot check IERC721(helperHeroToken).ownerOf(helperHeroTokenId) here without try/catch
      owner = heroOwner;
    } catch {}

    if (s.busyGuildHelpers[packedHero] == 0) revert IAppErrors.NotBusyGuildHelper();

    uint guildIdStakedIn = s.stakedGuildHeroes[packedHero];
    if (guildIdStakedIn != 0) {
      s.guildHelpers[guildIdStakedIn].set(packedHero, 0); // free for use in guild reinforcement again
    }

    s.busyGuildHelpers[packedHero] = 0;

    emit IApplicationEvents.GuildHeroReleased(helperHeroToken, helperHeroTokenId, guildIdStakedIn, owner);
  }
  //endregion ------------------------ Guild reinforcement

  //region ------------------------ Reinforcement V2
  /// @notice Stake hero in reinforcement-v2
  /// @param rewardAmount Reward required by the helper for the help.
  function stakeHeroV2(bool isEoa, IController controller, address msgSender, address heroToken, uint heroId, uint rewardAmount) internal {
    IReinforcementController.MainState storage s = _S();
    onlyNotPausedEoaOwner(isEoa, controller, msgSender, heroToken, heroId);

    if (rewardAmount == 0) revert IAppErrors.ZeroAmount();

    IHeroController heroController = _checkStakeAllowed(isEoa, controller, msgSender, heroToken, heroId);
    IStatController statController = IStatController(controller.statController());

    uint8 biome = heroController.heroBiome(heroToken, heroId);

    {
      (, , , , uint8 levelLimit) = getConfigV2();
      IStatController.ChangeableStats memory stats = statController.heroStats(heroToken, heroId);
      if (levelLimit != 0) { // levelLimit can be 0 in functional tests
        if (stats.level > levelLimit && (stats.level - levelLimit) / levelLimit > biome) revert IAppErrors.StakeHeroNotStats();
      }
      if (stats.lifeChances == 0) revert IAppErrors.ErrorHeroIsDead(heroToken, heroId);
    }

    s.heroesByBiomeV2[biome].add(heroToken.packNftId(heroId));

    s.stakedHeroesV2[heroToken.packNftId(heroId)] = IReinforcementController.HeroInfoV2({
      biome: biome,
      stakeTs: uint64(block.timestamp),
      rewardAmount: uint128(rewardAmount)
    });

    emit IApplicationEvents.HeroStakedV2(heroToken, heroId, biome, rewardAmount);
  }

  /// @notice Reverse operation for {stakeHeroV2}
  function withdrawHeroV2(bool isEoa, IController controller, address msgSender, address heroToken, uint heroId) internal {
    IReinforcementController.MainState storage s = _S();

    _checkWithdrawAllowed(isEoa, controller, msgSender, heroToken, heroId);
    bytes32 packedHero = heroToken.packNftId(heroId);

    IReinforcementController.HeroInfoV2 memory _heroInfoV2 = s.stakedHeroesV2[packedHero];
    if (_heroInfoV2.biome == 0) revert IAppErrors.NotStaked();

    s.heroesByBiomeV2[_heroInfoV2.biome].remove(packedHero);
    delete s.stakedHeroesV2[packedHero];

    emit IApplicationEvents.HeroWithdraw(heroToken, heroId);
  }

  /// @notice {hero} asks help of the {helper}
  /// Hero owner sends reward amount to the helper owner as the reward for the help.
  /// Hero owner sends fixed fee to controller using standard process-routine.
  /// Size of the fixed fee depends on total number of calls of {askHeroV2} for last 24 hours since the current moment.
  /// Durability of all equipped items of the helper are reduced.
  /// Assume, that hero owner approves rewardAmount + fixed fee to reinforcementController-contract
  /// - rewardAmount: amount required by the helper (see {heroInfoV2})
  /// - fixed fee: fee taken by controller (see {getFeeAmount})
  function askHeroV2(IController controller, address hero, uint heroId, address helper, uint helperId, uint blockTimestamp) internal returns (
    int32[] memory attributes
  ) {
    // assume that the signer (HeroController) has checked that the hero and helper are registered, controller is not paused
    uint8 heroBiome;
    {
      address heroController = onlyHeroController(controller);
      heroBiome = IHeroController(heroController).heroBiome(hero, heroId);
    }

    address gameToken = controller.gameToken();

    IReinforcementController.HeroInfoV2 memory _heroInfo = _S().stakedHeroesV2[helper.packNftId(helperId)];
    if (_heroInfo.biome != heroBiome) revert IAppErrors.HelperNotAvailableInGivenBiome();

    // calculate number of calls for the last 24 hours starting from the current moment
    uint hitsLast24h = _getHitsLast24h(heroBiome, blockTimestamp);

    // calculate fixed fee and send it to the treasury
    uint fixedFee = _getFeeAmount(gameToken, hitsLast24h, heroBiome);

    {
      address heroOwner = IERC721(hero).ownerOf(heroId);
      IERC20(gameToken).transferFrom(heroOwner, address(this), fixedFee + _heroInfo.rewardAmount);
    }

    { // send reward amount from msgSender to helper
      address helperOwner = IERC721(helper).ownerOf(helperId);
      IERC20(gameToken).transfer(helperOwner, _heroInfo.rewardAmount);
    }

    AppLib.approveIfNeeded(gameToken, fixedFee, address(controller));
    controller.process(gameToken, fixedFee, address(this));

    attributes = _getReinforcementAttributes(controller, helper, helperId);

    // reduceDurability of all equipped items of the helper
    IItemController(controller.itemController()).reduceDurability(helper, helperId, heroBiome, true);

    emit IApplicationEvents.HeroAskV2(helper, helperId, hitsLast24h, fixedFee, _heroInfo.rewardAmount);

    return attributes;
  }

  //endregion ------------------------ Reinforcement V2

  //region ------------------------ Internal logic
  /// @notice Increment counter of hits, calculate actual number of hits for 24 hours starting from the current moment
  /// @return hitsLast24h Number of calls for the last 24 hours, decimals 18
  function _getHitsLast24h(uint biome, uint blockTimestamp) internal returns (uint hitsLast24h) {
    IReinforcementController.LastWindowsV2 memory stat24h = _S().stat24hV2[biome];
    (hitsLast24h, stat24h) = getHitsNumberPerLast24Hours(blockTimestamp, BASKET_INTERVAL, stat24h);
    _S().stat24hV2[biome] = stat24h; // save updated statistics for last 24 hours
  }

  function _getReinforcementAttributes(IController controller, address heroToken, uint heroTokenId) internal view returns (
    int32[] memory attributes
  ) {
    IStatController sc = IStatController(controller.statController());
    uint[] memory indexes = new uint[](12);

    indexes[0] = uint(IStatController.ATTRIBUTES.STRENGTH);
    indexes[1] = uint(IStatController.ATTRIBUTES.DEXTERITY);
    indexes[2] = uint(IStatController.ATTRIBUTES.VITALITY);
    indexes[3] = uint(IStatController.ATTRIBUTES.ENERGY);
    indexes[4] = uint(IStatController.ATTRIBUTES.DAMAGE_MIN);
    indexes[5] = uint(IStatController.ATTRIBUTES.DAMAGE_MAX);
    indexes[6] = uint(IStatController.ATTRIBUTES.ATTACK_RATING);
    indexes[7] = uint(IStatController.ATTRIBUTES.DEFENSE);
    indexes[8] = uint(IStatController.ATTRIBUTES.BLOCK_RATING);
    indexes[9] = uint(IStatController.ATTRIBUTES.FIRE_RESISTANCE);
    indexes[10] = uint(IStatController.ATTRIBUTES.COLD_RESISTANCE);
    indexes[11] = uint(IStatController.ATTRIBUTES.LIGHTNING_RESISTANCE);

    return _generateReinforcementAttributes(sc, indexes, heroToken, heroTokenId);
  }

  /// @notice Claim all rewards from {_heroTokenRewards} to {recipient}, remove data from {_heroTokenRewards}
  function _claimAllTokenRewards(address heroToken, uint heroId, address recipient) internal {
    EnumerableMap.AddressToUintMap storage rewards = _S()._heroTokenRewards[heroToken.packNftId(heroId)];
    uint length = rewards.length();
    address[] memory tokens = new address[](length);
    for (uint i; i < length; ++i) {
      (address token, uint amount) = rewards.at(i);
      IERC20(token).transfer(recipient, amount);
      emit IApplicationEvents.ClaimedToken(heroToken, heroId, token, amount, recipient);

      tokens[i] = token;
    }

    // need to remove after the ordered reading for handle all elements, just remove the struct will not work coz contains mapping inside
    for (uint i; i < length; ++i) {
      rewards.remove(tokens[i]);
    }
  }

  function _claimAllNftRewards(address heroToken, uint heroId, address recipient) internal {
    bytes32[] storage rewards = _S()._heroNftRewards[heroToken.packNftId(heroId)];
    uint length = rewards.length;
    for (uint i; i < length; ++i) {
      (address token, uint id) = rewards[i].unpackNftId();
      IERC721(token).safeTransferFrom(address(this), recipient, id);
      emit IApplicationEvents.ClaimedItem(heroToken, heroId, token, id, recipient);
    }
    // a simple array can be just deleted
    delete _S()._heroNftRewards[heroToken.packNftId(heroId)];
  }

  /// @notice Claim last {countNft} NFTs and remove them from {_heroNftRewards}
  function _claimNftRewards(address heroToken, uint heroId, address recipient, uint countNft) internal {
    bytes32[] storage rewards = _S()._heroNftRewards[heroToken.packNftId(heroId)];

    uint length = rewards.length;
    uint indexLastToDelete = countNft >= length
      ? 0
      : length - countNft;

    while (length != indexLastToDelete) {
      (address token, uint id) = rewards[length - 1].unpackNftId();
      IERC721(token).safeTransferFrom(address(this), recipient, id);
      emit IApplicationEvents.ClaimedItem(heroToken, heroId, token, id, recipient);
      length--;

      // if we are going to remove all items we can just delete all items at the end
      // otherwise we should pop the items one by one
      if (indexLastToDelete != 0) {
        rewards.pop();
      }
    }

    if (length == 0) {
      delete _S()._heroNftRewards[heroToken.packNftId(heroId)];
    }
  }

  function _generateReinforcementAttributes(IStatController sc, uint[] memory indexes, address heroToken, uint heroId)
  internal view returns (int32[] memory attributes) {
    attributes = new int32[](uint(IStatController.ATTRIBUTES.END_SLOT));
    for (uint i; i < indexes.length; ++i) {
      attributes[indexes[i]] = CalcLib.max32(sc.heroAttribute(heroToken, heroId, indexes[i]) * _ATTRIBUTES_RATIO / 100, 1);
    }
  }
  //endregion ------------------------ Internal logic

  //region ------------------------ Fixed fee calculation V2

  /// @notice hitsLast24h Number of hits per last 24 hours, decimals 18
  function _getFeeAmount(address gameToken, uint hitsLast24h, uint8 biome) internal view returns (uint) {

    // get min-max range for the burn fee
    (uint32 minNumberHits, uint32 maxNumberHits, uint32 lowDivider, uint32 highDivider,) = getConfigV2();
    uint min = 1e18 * uint(minNumberHits);
    uint max = 1e18 * uint(maxNumberHits);

    // get max amount of the fee using original minter.amountForDungeon
    // we should pass heroBiome EXACTLY same to dungeonBiomeLevel
    // to avoid reducing base because of the difference heroBiome and dungeonBiomeLevel, see {amountForDungeon}
    IMinter minter = IMinter(IGameToken(gameToken).minter());
    uint amountForDungeon = minter.amountForDungeon(biome, biome) * 10;

    // calculate fee
    if (hitsLast24h < min) hitsLast24h = min;
    if (hitsLast24h > max) hitsLast24h = max;

    uint f = 1e18 * (hitsLast24h - min) / (max - min);
    return amountForDungeon / lowDivider + f * (amountForDungeon / highDivider - amountForDungeon / lowDivider) / 1e18;
  }

  /// @notice Process the next call of askHeroV2
  /// @return hitsLast24h Number of askHeroV2-calls for last 24 hours, decimals 18
  /// @return dest Updated last-24hours-window-statistics to be stored in the storage
  function getHitsNumberPerLast24Hours(
    uint blockTimestamp,
    uint basketInterval,
    IReinforcementController.LastWindowsV2 memory s
  ) internal pure returns (
    uint hitsLast24h,
    IReinforcementController.LastWindowsV2 memory dest
  ) {
    uint countBaskets = 24 / basketInterval;
    uint hour = blockTimestamp / 60 / 60;

    // get absolute index of basket for the current hour
    uint targetBasketIndex = hour / basketInterval;

    if (s.basketIndex == targetBasketIndex) {
      // current basket is not changed, increase the counter
      if (s.basketValue < type(uint24).max) {
        s.basketValue++;
      }
    } else {
      // current basket is changed
      // save value of previous basket to {baskets} and start counting from the zero
      s.baskets[s.basketIndex % countBaskets] = s.basketValue;

      // clear outdated baskets if some baskets were skipped (users didn't make any actions too long)
      if (targetBasketIndex >= s.basketIndex + countBaskets) {
        for (uint i; i < countBaskets; ++i) {
          s.baskets[i] = 0;
        }
      } else {
        for (uint i = s.basketIndex + 1; i < targetBasketIndex; ++i) {
          s.baskets[i % countBaskets] = 0;
        }
      }

      s.basketValue = 1;
      s.basketIndex = uint48(targetBasketIndex);
    }

    // calculate sum for last 24 hours
    uint m = 1e18 * (blockTimestamp - targetBasketIndex * basketInterval * 60 * 60) / (basketInterval * 60 * 60);
    uint bi = s.basketIndex % countBaskets;
    for (uint i; i < countBaskets; ++i) {
      if (i == bi) {
        hitsLast24h += uint(s.baskets[i]) * (1e18 - m) + m * uint(s.basketValue);
      } else {
        hitsLast24h += uint(s.baskets[i]) * 1e18;
      }
    }

    return (hitsLast24h, s);
  }
  //endregion ------------------------ Fixed fee calculation V2
}

File 31 of 41 : 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 32 of 41 : 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 33 of 41 : StringLib.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;


library StringLib {

  /// @dev Inspired by OraclizeAPI's implementation - MIT license
  ///      https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
  function toString(uint value) external pure returns (string memory) {
    return _toString(value);
  }

  function _toString(uint value) internal pure returns (string memory) {
    if (value == 0) {
      return "0";
    }
    uint temp = value;
    uint digits;
    while (temp != 0) {
      digits++;
      temp /= 10;
    }
    bytes memory buffer = new bytes(digits);
    while (value != 0) {
      digits -= 1;
      buffer[digits] = bytes1(uint8(48 + uint(value % 10)));
      value /= 10;
    }
    return string(buffer);
  }

  function toAsciiString(address x) external pure returns (string memory) {
    return _toAsciiString(x);
  }

  function _toAsciiString(address x) internal pure returns (string memory) {
    bytes memory s = new bytes(40);
    for (uint i = 0; i < 20; i++) {
      bytes1 b = bytes1(uint8(uint(uint160(x)) / (2 ** (8 * (19 - i)))));
      bytes1 hi = bytes1(uint8(b) / 16);
      bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi));
      s[2 * i] = _char(hi);
      s[2 * i + 1] = _char(lo);
    }
    return string(s);
  }

  function char(bytes1 b) external pure returns (bytes1 c) {
    return _char(b);
  }

  function _char(bytes1 b) internal pure returns (bytes1 c) {
    if (uint8(b) < 10) return bytes1(uint8(b) + 0x30);
    else return bytes1(uint8(b) + 0x57);
  }

  function concat(string memory a, string memory b) internal pure returns (string memory) {
    return string(abi.encodePacked(a, b));
  }

  function isASCIILettersOnly(string memory str) internal pure returns (bool) {
    bytes memory b = bytes(str);
    for (uint i = 0; i < b.length; i++) {
      if (uint8(b[i]) < 32 || uint8(b[i]) > 127) {
        return false;
      }
    }
    return true;
  }
}

File 34 of 41 : 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 35 of 41 : 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 36 of 41 : 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 37 of 41 : 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 38 of 41 : 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 39 of 41 : ERC2771Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (metatx/ERC2771Context.sol)

pragma solidity ^0.8.1;

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

/**
 * @dev Context variant with ERC2771 support.
 */
// based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/metatx/ERC2771Context.sol
abstract contract ERC2771Context {
  // for whitelist new relayers need to add new constants and update proxies
  address private constant GELATO_RELAY_1_BALANCE_ERC_2771 = 0xd8253782c45a12053594b9deB72d8e8aB2Fca54c;
  address private constant SACRA_RELAY = 0x52CEba41Da235Af367bFC0b0cCd3314cb901bB5F;
  address private constant SACRA_RELAY_2 = 0x102f1f556cD9C3D5f820E6920A8931657c5Da21B;

  function isTrustedForwarder(address forwarder) public view virtual returns (bool){
    return forwarder == GELATO_RELAY_1_BALANCE_ERC_2771 || forwarder == SACRA_RELAY || forwarder == SACRA_RELAY_2;
  }

  function _msgSender() internal view virtual returns (address sender) {
    if (isTrustedForwarder(msg.sender)) {
      // The assembly code is more direct than the Solidity version using `abi.decode`.
      /// @solidity memory-safe-assembly
      assembly {
        sender := shr(96, calldataload(sub(calldatasize(), 20)))
      }
      return sender;
    } else {
      return msg.sender;
    }
  }

  function _msgData() internal view virtual returns (bytes calldata) {
    if (isTrustedForwarder(msg.sender)) {
      return msg.data[: msg.data.length - 20];
    } else {
      return msg.data;
    }
  }

  /// @notice Return true if given address is not a smart contract but a wallet address.
  /// @dev It is not 100% guarantee after EIP-3074 implementation, use it as an additional check.
  /// @return true if the address is a wallet.
  function _isNotSmartContract() internal view returns (bool) {
    return isTrustedForwarder(msg.sender) || msg.sender == tx.origin;
  }

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

File 40 of 41 : LibPRNG.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Library for generating pseudorandom numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibPRNG.sol)
/// @author LazyShuffler based on NextShuffler by aschlosberg (divergencearran)
/// (https://github.com/divergencetech/ethier/blob/main/contracts/random/NextShuffler.sol)
library LibPRNG {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The initial length must be greater than zero and less than `2**32 - 1`.
    error InvalidInitialLazyShufflerLength();

    /// @dev The new length must not be less than the current length.
    error InvalidNewLazyShufflerLength();

    /// @dev The lazy shuffler has not been initialized.
    error LazyShufflerNotInitialized();

    /// @dev Cannot double initialize the lazy shuffler.
    error LazyShufflerAlreadyInitialized();

    /// @dev The lazy shuffle has finished.
    error LazyShuffleFinished();

    /// @dev The queried index is out of bounds.
    error LazyShufflerGetOutOfBounds();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The scalar of ETH and most ERC20s.
    uint256 internal constant WAD = 1e18;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STRUCTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev A pseudorandom number state in memory.
    struct PRNG {
        uint256 state;
    }

    /// @dev A lazy Fisher-Yates shuffler for a range `[0..n)` in storage.
    struct LazyShuffler {
        // Bits Layout:
        // - [0..31]    `numShuffled`
        // - [32..223]  `permutationSlot`
        // - [224..255] `length`
        uint256 _state;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         OPERATIONS                         */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Seeds the `prng` with `state`.
    function seed(PRNG memory prng, uint256 state) internal pure {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(prng, state)
        }
    }

    /// @dev Returns the next pseudorandom uint256.
    /// All bits of the returned uint256 pass the NIST Statistical Test Suite.
    function next(PRNG memory prng) internal pure returns (uint256 result) {
        // We simply use `keccak256` for a great balance between
        // runtime gas costs, bytecode size, and statistical properties.
        //
        // A high-quality LCG with a 32-byte state
        // is only about 30% more gas efficient during runtime,
        // but requires a 32-byte multiplier, which can cause bytecode bloat
        // when this function is inlined.
        //
        // Using this method is about 2x more efficient than
        // `nextRandomness = uint256(keccak256(abi.encode(randomness)))`.
        /// @solidity memory-safe-assembly
        assembly {
            result := keccak256(prng, 0x20)
            mstore(prng, result)
        }
    }

    /// @dev Returns a pseudorandom uint256, uniformly distributed
    /// between 0 (inclusive) and `upper` (exclusive).
    /// If your modulus is big, this method is recommended
    /// for uniform sampling to avoid modulo bias.
    /// For uniform sampling across all uint256 values,
    /// or for small enough moduli such that the bias is neligible,
    /// use {next} instead.
    function uniform(PRNG memory prng, uint256 upper) internal pure returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            for {} 1 {} {
                result := keccak256(prng, 0x20)
                mstore(prng, result)
                if iszero(lt(result, mod(sub(0, upper), upper))) { break }
            }
            result := mod(result, upper)
        }
    }

    /// @dev Shuffles the array in-place with Fisher-Yates shuffle.
    function shuffle(PRNG memory prng, uint256[] memory a) internal pure {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(a)
            let w := not(0)
            let mask := shr(128, w)
            if n {
                for { a := add(a, 0x20) } 1 {} {
                    // We can just directly use `keccak256`, cuz
                    // the other approaches don't save much.
                    let r := keccak256(prng, 0x20)
                    mstore(prng, r)

                    // Note that there will be a very tiny modulo bias
                    // if the length of the array is not a power of 2.
                    // For all practical purposes, it is negligible
                    // and will not be a fairness or security concern.
                    {
                        let j := add(a, shl(5, mod(shr(128, r), n)))
                        n := add(n, w) // `sub(n, 1)`.
                        if iszero(n) { break }

                        let i := add(a, shl(5, n))
                        let t := mload(i)
                        mstore(i, mload(j))
                        mstore(j, t)
                    }

                    {
                        let j := add(a, shl(5, mod(and(r, mask), n)))
                        n := add(n, w) // `sub(n, 1)`.
                        if iszero(n) { break }

                        let i := add(a, shl(5, n))
                        let t := mload(i)
                        mstore(i, mload(j))
                        mstore(j, t)
                    }
                }
            }
        }
    }

    /// @dev Shuffles the bytes in-place with Fisher-Yates shuffle.
    function shuffle(PRNG memory prng, bytes memory a) internal pure {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(a)
            let w := not(0)
            let mask := shr(128, w)
            if n {
                let b := add(a, 0x01)
                for { a := add(a, 0x20) } 1 {} {
                    // We can just directly use `keccak256`, cuz
                    // the other approaches don't save much.
                    let r := keccak256(prng, 0x20)
                    mstore(prng, r)

                    // Note that there will be a very tiny modulo bias
                    // if the length of the array is not a power of 2.
                    // For all practical purposes, it is negligible
                    // and will not be a fairness or security concern.
                    {
                        let o := mod(shr(128, r), n)
                        n := add(n, w) // `sub(n, 1)`.
                        if iszero(n) { break }

                        let t := mload(add(b, n))
                        mstore8(add(a, n), mload(add(b, o)))
                        mstore8(add(a, o), t)
                    }

                    {
                        let o := mod(and(r, mask), n)
                        n := add(n, w) // `sub(n, 1)`.
                        if iszero(n) { break }

                        let t := mload(add(b, n))
                        mstore8(add(a, n), mload(add(b, o)))
                        mstore8(add(a, o), t)
                    }
                }
            }
        }
    }

    /// @dev Returns a sample from the standard normal distribution denominated in `WAD`.
    function standardNormalWad(PRNG memory prng) internal pure returns (int256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            // Technically, this is the Irwin-Hall distribution with 20 samples.
            // The chance of drawing a sample outside 10 σ from the standard normal distribution
            // is ≈ 0.000000000000000000000015, which is insignificant for most practical purposes.
            // Passes the Kolmogorov-Smirnov test for 200k samples. Uses about 322 gas.
            result := keccak256(prng, 0x20)
            mstore(prng, result)
            let n := 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43 // Prime.
            let a := 0x100000000000000000000000000000051 // Prime and a primitive root of `n`.
            let m := 0x1fffffffffffffff1fffffffffffffff1fffffffffffffff1fffffffffffffff
            let s := 0x1000000000000000100000000000000010000000000000001
            let r1 := mulmod(result, a, n)
            let r2 := mulmod(r1, a, n)
            let r3 := mulmod(r2, a, n)
            // forgefmt: disable-next-item
            result := sub(sar(96, mul(26614938895861601847173011183,
                add(add(shr(192, mul(s, add(and(m, result), and(m, r1)))),
                shr(192, mul(s, add(and(m, r2), and(m, r3))))),
                shr(192, mul(s, and(m, mulmod(r3, a, n))))))), 7745966692414833770)
        }
    }

    /// @dev Returns a sample from the unit exponential distribution denominated in `WAD`.
    function exponentialWad(PRNG memory prng) internal pure returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            // Passes the Kolmogorov-Smirnov test for 200k samples.
            // Gas usage varies, starting from about 172+ gas.
            let r := keccak256(prng, 0x20)
            mstore(prng, r)
            let p := shl(129, r)
            let w := shl(1, r)
            if iszero(gt(w, p)) {
                let n := 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43 // Prime.
                let a := 0x100000000000000000000000000000051 // Prime and a primitive root of `n`.
                for {} 1 {} {
                    r := mulmod(r, a, n)
                    if iszero(lt(shl(129, r), w)) {
                        r := mulmod(r, a, n)
                        result := add(1000000000000000000, result)
                        w := shl(1, r)
                        p := shl(129, r)
                        if iszero(lt(w, p)) { break }
                        continue
                    }
                    w := shl(1, r)
                    if iszero(lt(w, shl(129, r))) { break }
                }
            }
            result := add(div(p, shl(129, 170141183460469231732)), result)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*       STORAGE-BASED RANGE LAZY SHUFFLING OPERATIONS        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Initializes the state for lazy-shuffling the range `[0..n)`.
    /// Reverts if `n == 0 || n >= 2**32 - 1`.
    /// Reverts if `$` has already been initialized.
    /// If you need to reduce the length after initialization, just use a fresh new `$`.
    function initialize(LazyShuffler storage $, uint256 n) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(lt(sub(n, 1), 0xfffffffe)) {
                mstore(0x00, 0x83b53941) // `InvalidInitialLazyShufflerLength()`.
                revert(0x1c, 0x04)
            }
            if sload($.slot) {
                mstore(0x00, 0x0c9f11f2) // `LazyShufflerAlreadyInitialized()`.
                revert(0x1c, 0x04)
            }
            mstore(0x00, $.slot)
            sstore($.slot, or(shl(224, n), shl(32, shr(64, keccak256(0x00, 0x20)))))
        }
    }

    /// @dev Increases the length of `$`.
    /// Reverts if `$` has not been initialized.
    function grow(LazyShuffler storage $, uint256 n) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let state := sload($.slot) // The packed value at `$`.
            // If the new length is smaller than the old length, revert.
            if lt(n, shr(224, state)) {
                mstore(0x00, 0xbed37c6e) // `InvalidNewLazyShufflerLength()`.
                revert(0x1c, 0x04)
            }
            if iszero(state) {
                mstore(0x00, 0x1ead2566) // `LazyShufflerNotInitialized()`.
                revert(0x1c, 0x04)
            }
            sstore($.slot, or(shl(224, n), shr(32, shl(32, state))))
        }
    }

    /// @dev Restarts the shuffler by setting `numShuffled` to zero,
    /// such that all elements can be drawn again.
    /// Restarting does NOT clear the internal permutation, nor changes the length.
    /// Even with the same sequence of randomness, reshuffling can yield different results.
    function restart(LazyShuffler storage $) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let state := sload($.slot)
            if iszero(state) {
                mstore(0x00, 0x1ead2566) // `LazyShufflerNotInitialized()`.
                revert(0x1c, 0x04)
            }
            sstore($.slot, shl(32, shr(32, state)))
        }
    }

    /// @dev Returns the number of elements that have been shuffled.
    function numShuffled(LazyShuffler storage $) internal view returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := and(0xffffffff, sload($.slot))
        }
    }

    /// @dev Returns the length of `$`.
    /// Returns zero if `$` is not initialized, else a non-zero value less than `2**32 - 1`.
    function length(LazyShuffler storage $) internal view returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := shr(224, sload($.slot))
        }
    }

    /// @dev Returns if `$` has been initialized.
    function initialized(LazyShuffler storage $) internal view returns (bool result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := iszero(iszero(sload($.slot)))
        }
    }

    /// @dev Returns if there are any more elements left to shuffle.
    /// Reverts if `$` is not initialized.
    function finished(LazyShuffler storage $) internal view returns (bool result) {
        /// @solidity memory-safe-assembly
        assembly {
            let state := sload($.slot) // The packed value at `$`.
            if iszero(state) {
                mstore(0x00, 0x1ead2566) // `LazyShufflerNotInitialized()`.
                revert(0x1c, 0x04)
            }
            result := eq(shr(224, state), and(0xffffffff, state))
        }
    }

    /// @dev Returns the current value stored at `index`, accounting for all historical shuffling.
    /// Reverts if `index` is greater than or equal to the `length` of `$`.
    function get(LazyShuffler storage $, uint256 index) internal view returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            let state := sload($.slot) // The packed value at `$`.
            let n := shr(224, state) // Length of `$`.
            if iszero(lt(index, n)) {
                mstore(0x00, 0x61367cc4) // `LazyShufflerGetOutOfBounds()`.
                revert(0x1c, 0x04)
            }
            let u32 := gt(n, 0xfffe)
            let s := add(shr(sub(4, u32), index), shr(64, shl(32, state))) // Bucket slot.
            let o := shl(add(4, u32), and(index, shr(u32, 15))) // Bucket slot offset (bits).
            let m := sub(shl(shl(u32, 16), 1), 1) // Value mask.
            result := and(m, shr(o, sload(s)))
            result := xor(index, mul(xor(index, sub(result, 1)), iszero(iszero(result))))
        }
    }

    /// @dev Does a single Fisher-Yates shuffle step, increments the `numShuffled` in `$`,
    /// and returns the next value in the shuffled range.
    /// `randomness` can be taken from a good-enough source, or a higher quality source like VRF.
    /// Reverts if there are no more values to shuffle, which includes the case if `$` is not initialized.
    function next(LazyShuffler storage $, uint256 randomness) internal returns (uint256 chosen) {
        /// @solidity memory-safe-assembly
        assembly {
            function _get(u32_, state_, i_) -> _value {
                let s_ := add(shr(sub(4, u32_), i_), shr(64, shl(32, state_))) // Bucket slot.
                let o_ := shl(add(4, u32_), and(i_, shr(u32_, 15))) // Bucket slot offset (bits).
                let m_ := sub(shl(shl(u32_, 16), 1), 1) // Value mask.
                _value := and(m_, shr(o_, sload(s_)))
                _value := xor(i_, mul(xor(i_, sub(_value, 1)), iszero(iszero(_value))))
            }
            function _set(u32_, state_, i_, value_) {
                let s_ := add(shr(sub(4, u32_), i_), shr(64, shl(32, state_))) // Bucket slot.
                let o_ := shl(add(4, u32_), and(i_, shr(u32_, 15))) // Bucket slot offset (bits).
                let m_ := sub(shl(shl(u32_, 16), 1), 1) // Value mask.
                let v_ := sload(s_) // Bucket slot value.
                value_ := mul(iszero(eq(i_, value_)), add(value_, 1))
                sstore(s_, xor(v_, shl(o_, and(m_, xor(shr(o_, v_), value_)))))
            }
            let state := sload($.slot) // The packed value at `$`.
            let shuffled := and(0xffffffff, state) // Number of elements shuffled.
            let n := shr(224, state) // Length of `$`.
            let remainder := sub(n, shuffled) // Number of elements left to shuffle.
            if iszero(remainder) {
                mstore(0x00, 0x51065f79) // `LazyShuffleFinished()`.
                revert(0x1c, 0x04)
            }
            mstore(0x00, randomness) // (Re)hash the randomness so that we don't
            mstore(0x20, shuffled) // need to expect guarantees on its distribution.
            let index := add(mod(keccak256(0x00, 0x40), remainder), shuffled)
            chosen := _get(gt(n, 0xfffe), state, index)
            _set(gt(n, 0xfffe), state, index, _get(gt(n, 0xfffe), state, shuffled))
            _set(gt(n, 0xfffe), state, shuffled, chosen)
            sstore($.slot, add(1, state)) // Increment the `numShuffled` by 1, and store it.
        }
    }
}

File 41 of 41 : GuildBank.sol
// SPDX-License-Identifier: BUSL-1.1
/**
            ▒▓▒  ▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓███▓▓▒     ▒▒▒▒▓▓▓▒▓▓▓▓▓▓▓██▓
             ▒██▒▓▓▓▓█▓██████████████████▓  ▒▒▒▓███████████████▒
              ▒██▒▓█████████████████████▒ ▒▓██████████▓███████
               ▒███████████▓▒                   ▒███▓▓██████▓
                 █████████▒                     ▒▓▒▓███████▒
                  ███████▓      ▒▒▒▒▒▓▓█▓▒     ▓█▓████████
                   ▒▒▒▒▒   ▒▒▒▒▓▓▓█████▒      ▓█████████▓
                         ▒▓▓▓▒▓██████▓      ▒▓▓████████▒
                       ▒██▓▓▓███████▒      ▒▒▓███▓████
                        ▒███▓█████▒       ▒▒█████▓██▓
                          ██████▓   ▒▒▒▓██▓██▓█████▒
                           ▒▒▓▓▒   ▒██▓▒▓▓████████
                                  ▓█████▓███████▓
                                 ██▓▓██████████▒
                                ▒█████████████
                                 ███████████▓
      ▒▓▓▓▓▓▓▒▓                  ▒█████████▒                      ▒▓▓
    ▒▓█▒   ▒▒█▒▒                   ▓██████                       ▒▒▓▓▒
   ▒▒█▒       ▓▒                    ▒████                       ▒▓█▓█▓▒
   ▓▒██▓▒                             ██                       ▒▓█▓▓▓██▒
    ▓█▓▓▓▓▓█▓▓▓▒        ▒▒▒         ▒▒▒▓▓▓▓▒▓▒▒▓▒▓▓▓▓▓▓▓▓▒    ▒▓█▒ ▒▓▒▓█▓
     ▒▓█▓▓▓▓▓▓▓▓▓▓▒    ▒▒▒▓▒     ▒▒▒▓▓     ▓▓  ▓▓█▓   ▒▒▓▓   ▒▒█▒   ▒▓▒▓█▓
            ▒▒▓▓▓▒▓▒  ▒▓▓▓▒█▒   ▒▒▒█▒          ▒▒█▓▒▒▒▓▓▓▒   ▓██▓▓▓▓▓▓▓███▓
 ▒            ▒▓▓█▓  ▒▓▓▓▓█▓█▓  ▒█▓▓▒          ▓▓█▓▒▓█▓▒▒   ▓█▓        ▓███▓
▓▓▒         ▒▒▓▓█▓▒▒▓█▒   ▒▓██▓  ▓██▓▒     ▒█▓ ▓▓██   ▒▓▓▓▒▒▓█▓        ▒▓████▒
 ██▓▓▒▒▒▒▓▓███▓▒ ▒▓▓▓▓▒▒ ▒▓▓▓▓▓▓▓▒▒▒▓█▓▓▓▓█▓▓▒▒▓▓▓▓▓▒    ▒▓████▓▒     ▓▓███████▓▓▒
*/
pragma solidity 0.8.23;

import "../interfaces/IAppErrors.sol";
import "../interfaces/IERC20.sol";
import "../interfaces/IERC721.sol";
import "../interfaces/IGuildBank.sol";
import "../interfaces/IGuildController.sol";
import {IApplicationEvents} from "../interfaces/IApplicationEvents.sol";

contract GuildBank is IGuildBank {

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

  //region ------------------------ Members
  IGuildController immutable public guildController;
  uint immutable public guildId;
  //endregion ------------------------ Members

  //region ------------------------ Restrictions and constructor
  function _onlyGuildController(address msgSender) internal view {
    if (msgSender != address(guildController)) revert IAppErrors.GuildControllerOnly();
  }

  constructor (address guildController_, uint guildId_) {
    guildController = IGuildController(guildController_);
    guildId = guildId_;
  }
  //endregion ------------------------ Restrictions and constructor

  //region ------------------------ ERC20
  function transfer(address token, address recipient, uint amount) external {
    _onlyGuildController(msg.sender);

    IERC20(token).transfer(recipient, amount);
    emit IApplicationEvents.GuildBankTransfer(token, recipient, amount);
  }

  function approve(address token, address spender, uint256 amount) external returns (bool) {
    _onlyGuildController(msg.sender);

    return IERC20(token).approve(spender, amount);
  }
  //endregion ------------------------ ERC20

  //region ------------------------ ERC721
  function transferNft(address to, address nft, uint256 tokenId) external {
    _onlyGuildController(msg.sender);

    IERC721(nft).transferFrom(address(this), to, tokenId);
    emit IApplicationEvents.GuildBankTransferNft(to, nft, tokenId);
  }

  function transferNftMulti(address to, address[] memory nfts, uint256[] memory tokenIds) external {
    _onlyGuildController(msg.sender);

    uint len = nfts.length;
    if (len != tokenIds.length) revert IAppErrors.LengthsMismatch();

    for (uint i; i < len; ++i) {
      IERC721(nfts[i]).transferFrom(address(this), to, tokenIds[i]);
    }
    emit IApplicationEvents.GuildBankTransferNftMulti(to, nfts, tokenIds);
  }

  function approveNft(address to, address nft, uint256 tokenId) external {
    _onlyGuildController(msg.sender);

    IERC721(nft).approve(to, tokenId);
  }

  function approveNftMulti(address to, address[] memory nfts, uint256[] memory tokenIds) external {
    _onlyGuildController(msg.sender);

    uint len = nfts.length;
    if (len != tokenIds.length) revert IAppErrors.LengthsMismatch();

    for (uint i; i < len; ++i) {
      IERC721(nfts[i]).approve(to, tokenIds[i]);
    }
  }
  //endregion ------------------------ ERC721
}

Settings
{
  "evmVersion": "istanbul",
  "optimizer": {
    "enabled": true,
    "runs": 50
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "metadata": {
    "useLiteralContent": true
  },
  "libraries": {
    "contracts/lib/GuildLib.sol": {
      "GuildLib": "0x36c1cb5bcd78541c1f70ed60adaa342ce69668bf"
    }
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[],"name":"AlreadyGuildMember","type":"error"},{"inputs":[{"internalType":"uint256","name":"positionId","type":"uint256"}],"name":"AuctionBidOpened","type":"error"},{"inputs":[],"name":"CannotRemoveGuildOwnerFromNotEmptyGuild","type":"error"},{"inputs":[],"name":"EmptyNameNotAllowed","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ErrorForbidden","type":"error"},{"inputs":[],"name":"ErrorNotShelterController","type":"error"},{"inputs":[],"name":"ErrorOnlyEoa","type":"error"},{"inputs":[],"name":"ErrorPaused","type":"error"},{"inputs":[{"internalType":"uint256","name":"right","type":"uint256"}],"name":"GuildActionForbidden","type":"error"},{"inputs":[{"internalType":"uint256","name":"level","type":"uint256"}],"name":"GuildHasMaxLevel","type":"error"},{"inputs":[{"internalType":"uint256","name":"guildSize","type":"uint256"}],"name":"GuildHasMaxSize","type":"error"},{"inputs":[],"name":"GuildRequestNotActive","type":"error"},{"inputs":[],"name":"GuildRequestNotAvailable","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"LengthsMismatch","type":"error"},{"inputs":[{"internalType":"uint8","name":"fee","type":"uint8"}],"name":"MaxFee","type":"error"},{"inputs":[{"internalType":"uint8","name":"fee","type":"uint8"}],"name":"MinFee","type":"error"},{"inputs":[],"name":"NameTaken","type":"error"},{"inputs":[],"name":"NotAdminCannotAddMemberWithNotZeroRights","type":"error"},{"inputs":[],"name":"NotGuildMember","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"SecondGuildAdminIsNotAllowed","type":"error"},{"inputs":[],"name":"TooBigName","type":"error"},{"inputs":[],"name":"TooLongDescription","type":"error"},{"inputs":[],"name":"TooLongString","type":"error"},{"inputs":[],"name":"TooLongUrl","type":"error"},{"inputs":[],"name":"WrongGuild","type":"error"},{"inputs":[],"name":"WrongSymbolsInTheName","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"guildId","type":"uint256"},{"indexed":false,"internalType":"address","name":"newUser","type":"address"}],"name":"AddToGuild","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":"uint256","name":"guildId","type":"uint256"},{"indexed":false,"internalType":"address","name":"guildBank","type":"address"}],"name":"GuildBankDeployed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"guildId","type":"uint256"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"string","name":"urlLogo","type":"string"}],"name":"GuildCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"guildId","type":"uint256"}],"name":"GuildDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"guildId","type":"uint256"},{"indexed":false,"internalType":"string","name":"newDescription","type":"string"}],"name":"GuildDescriptionChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"guildId","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"newLevel","type":"uint8"}],"name":"GuildLevelUp","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"guildId","type":"uint256"},{"indexed":false,"internalType":"string","name":"newLogoUrl","type":"string"}],"name":"GuildLogoChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"guildId","type":"uint256"},{"indexed":false,"internalType":"string","name":"newName","type":"string"}],"name":"GuildRename","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"msgSender","type":"address"},{"indexed":false,"internalType":"uint256","name":"guildId","type":"uint256"},{"indexed":false,"internalType":"string","name":"userMessage","type":"string"},{"indexed":false,"internalType":"uint256","name":"depositAmount","type":"uint256"}],"name":"GuildRequestRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"msgSender","type":"address"},{"indexed":false,"internalType":"uint256","name":"guildRequestId","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"newStatus","type":"uint8"},{"indexed":false,"internalType":"address","name":"user","type":"address"}],"name":"GuildRequestStatusChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"guildId","type":"uint256"},{"indexed":false,"internalType":"address","name":"user","type":"address"}],"name":"RemoveFromGuild","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"address","name":"oldLogic","type":"address"}],"name":"RevisionIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"guildId1","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"guildId2","type":"uint256"},{"indexed":false,"internalType":"bool","name":"peace","type":"bool"}],"name":"SetGuildRelation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"msgSender","type":"address"},{"indexed":false,"internalType":"uint256","name":"guildId","type":"uint256"},{"indexed":false,"internalType":"address","name":"guildBank","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TopUpGuildBank","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"}],"name":"TransferFromGuildBank","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"address[]","name":"nfts","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"}],"name":"TransferNftFromGuildBank","type":"event"},{"inputs":[],"name":"CONTROLLABLE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildRequestId","type":"uint256"},{"internalType":"uint256","name":"maskRights","type":"uint256"}],"name":"acceptGuildRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildId","type":"uint256"},{"internalType":"string","name":"userMessage","type":"string"}],"name":"addGuildRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildRequestId","type":"uint256"}],"name":"cancelGuildRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newDescription","type":"string"}],"name":"changeDescription","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newLogoUrl","type":"string"}],"name":"changeLogo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"maskRights","type":"uint256"}],"name":"changeRoles","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"right","type":"uint256"}],"name":"checkPermissions","outputs":[{"internalType":"uint256","name":"guildId","type":"uint256"},{"internalType":"uint256","name":"rights","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"controller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"urlLogo","type":"string"},{"internalType":"uint8","name":"toHelperRatio","type":"uint8"}],"name":"createGuild","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","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":[],"name":"getBaseFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildId","type":"uint256"}],"name":"getGuildBank","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"getGuildByName","outputs":[{"internalType":"uint256","name":"guildId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildId","type":"uint256"}],"name":"getGuildData","outputs":[{"internalType":"string","name":"guildName","type":"string"},{"internalType":"string","name":"urlLogo","type":"string"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint8","name":"guildLevel","type":"uint8"},{"internalType":"uint64","name":"pvpCounter","type":"uint64"},{"internalType":"uint256","name":"toHelperRatio","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildId","type":"uint256"}],"name":"getGuildDescription","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"paramId","type":"uint256"}],"name":"getGuildParamValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildRequestId","type":"uint256"}],"name":"getGuildRequest","outputs":[{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"guildId","type":"uint256"},{"internalType":"string","name":"userMessage","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildId","type":"uint256"}],"name":"getGuildRequestDepositAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getRights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserActiveGuildRequests","outputs":[{"internalType":"uint256[]","name":"guildRequestIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildId","type":"uint256"},{"internalType":"address","name":"user","type":"address"}],"name":"getUserPvpPoints","outputs":[{"internalType":"uint64","name":"capacityPvpPoints","type":"uint64"},{"internalType":"uint64","name":"spentPvpPoints","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"guildLevelUp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildId","type":"uint256"}],"name":"guildMembers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildId","type":"uint256"}],"name":"guildToShelter","outputs":[{"internalType":"uint256","name":"shelterId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint8","name":"rightBit","type":"uint8"}],"name":"hasPermission","outputs":[{"internalType":"bool","name":"userHasTheRight","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"oldLogic","type":"address"}],"name":"increaseRevision","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"controller_","type":"address"}],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"value_","type":"address"}],"name":"isController","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"value_","type":"address"}],"name":"isGovernance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildId","type":"uint256"},{"internalType":"uint256","name":"guildId2","type":"uint256"}],"name":"isPeacefulRelation","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"forwarder","type":"address"}],"name":"isTrustedForwarder","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildId","type":"uint256"},{"internalType":"uint8","name":"status","type":"uint8"}],"name":"listGuildRequests","outputs":[{"internalType":"uint256[]","name":"guildRequestIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"memberOf","outputs":[{"internalType":"uint256","name":"guildId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"bid","type":"uint256"}],"name":"payForAuctionBid","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"from","type":"address"}],"name":"payFromBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildId","type":"uint256"},{"internalType":"uint256","name":"shelterPrice","type":"uint256"}],"name":"payFromGuildBank","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"previousImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildRequestId","type":"uint256"}],"name":"rejectGuildRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"userToRemove","type":"address"}],"name":"removeGuildMember","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newGuildName","type":"string"}],"name":"rename","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revision","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"fee","type":"uint256"}],"name":"setBaseFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"setGuildRequestDepositAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"capacityPvpPoints","type":"uint64"},{"internalType":"address[]","name":"users","type":"address[]"}],"name":"setPvpPointsCapacity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"otherGuildId","type":"uint256"},{"internalType":"bool","name":"peace","type":"bool"}],"name":"setRelation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"shelterAuction_","type":"address"}],"name":"setShelterAuctionController","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"shelterController_","type":"address"}],"name":"setShelterController","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"value","type":"uint8"}],"name":"setToHelperRatio","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"shelterAuctionController","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"shelterController","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"topUpGuildBank","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"transferMulti","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"address[]","name":"nfts","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"transferNftMulti","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"guildId","type":"uint256"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint64","name":"priceInPvpPoints","type":"uint64"}],"name":"usePvpPoints","outputs":[],"stateMutability":"nonpayable","type":"function"}]

60806040523480156200001157600080fd5b507ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff1615906001600160401b03166000811580156200005d5750825b90506000826001600160401b031660011480156200007a5750303b155b90508115801562000089575080155b15620000a85760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b03191660011785558315620000d757845460ff60401b1916680100000000000000001785555b83156200011e57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050615b8f80620001336000396000f3fe60806040523480156200001157600080fd5b5060043610620003195760003560e01c8063865e021a11620001ab578063c3a9934211620000f2578063c3a993421462000721578063c6d577431462000738578063d0626ef9146200075e578063d3f489791462000787578063d5fd6fa2146200079e578063dc87655a14620007b5578063dee1f0e414620007ed578063df1dd9761462000804578063df70df4a146200082f578063e61204131462000846578063e79f0e96146200085d578063f2e0023d1462000874578063f2fde38b146200088b578063f77c479114620008a2578063ffa1ad7414620008ac57600080fd5b8063865e021a14620005af5780638a66c10c14620005d5578063936725ec14620005ec578063961e8bb5146200061157806398d2fb55146200062857806399f9e252146200063f5780639d0bcca01462000656578063a8b446b01462000660578063ad1036861462000677578063b429afeb146200068e578063b4a06d1f14620006a5578063b648063f14620006bc578063b8cc6a4214620006c6578063beabacc814620006f3578063c05c6776146200070a57600080fd5b80634593144c11620002705780634593144c146200047b5780634686069814620004855780634fac6ccd146200049c578063572b6c0514620004b35780635a9e5aa214620004ca5780635b7249a714620004e35780635bc8efc014620004fa5780635edee59614620005115780635f1c54df146200051b57806363e85d2d14620005325780636483b0d0146200054957806364c6afe7146200056057806366605ba4146200057757806371fb4f05146200058e5780637cc9638014620005a557600080fd5b8063075e5d32146200031e5780630909d479146200033757806311bc2ca2146200034e578063158914ac146200036557806315e812ad146200039157806319ab453c14620003aa5780631e359be814620003c157806321b741de14620003d8578063325a19f114620003ef57806333b4074214620003f95780633b4c989114620004105780633e74720314620004275780633ec2d255146200044d5780634400c1c31462000464575b600080fd5b620003356200032f3660046200433d565b620008d1565b005b62000335620003483660046200435d565b62000959565b620003356200035f3660046200444b565b6200097d565b6200037c6200037636600462004483565b6200099f565b60405190151581526020015b60405180910390f35b6200039b620009b6565b60405190815260200162000388565b62000335620003bb3660046200433d565b620009c7565b62000335620003d2366004620044a6565b62000af3565b62000335620003e93660046200433d565b62000b26565b6200039b62000b45565b6200039b6200040a366004620044a6565b62000b7c565b6200039b620004213660046200433d565b62000b89565b6200043e62000438366004620044a6565b62000b96565b60405162000388919062004514565b620003356200045e36600462004546565b62000ba3565b62000335620004753660046200433d565b62000c2c565b6200039b62000c50565b6200033562000496366004620044a6565b62000c83565b62000335620004ad3660046200433d565b62000cc7565b6200037c620004c43660046200433d565b62000de0565b620004d462000e57565b60405162000388919062004580565b62000335620004f43660046200435d565b62000e63565b620003356200050b36600462004594565b62000efe565b620004d462000f14565b6200039b6200052c366004620044a6565b62000f20565b6200037c62000543366004620045d9565b62000f2d565b620003356200055a366004620044a6565b62000f5b565b620003356200057136600462004483565b62000f84565b62000335620005883660046200444b565b62000fca565b6200039b6200059f3660046200444b565b62000fe9565b6200039b62000ff6565b620005c6620005c03660046200433d565b62001018565b60405162000388919062004651565b62000335620005e636600462004666565b62001025565b6200043e604051806040016040528060058152602001640312e302e360dc1b81525081565b620003356200062236600462004758565b6200104f565b6200033562000639366004620047a1565b620010a0565b620003356200065036600462004889565b620010c6565b620004d4620010e6565b6200033562000671366004620048b1565b62001119565b6200033562000688366004620048de565b6200117f565b6200037c6200069f3660046200433d565b620011ea565b62000335620006b636600462004483565b62001211565b6200033562001245565b620006dd620006d73660046200435d565b62001265565b6040805192835260208301919091520162000388565b6200033562000704366004620048fc565b6200129d565b6200039b6200071b366004620044a6565b620012be565b6200033562000732366004620047a1565b620012cb565b6200074f62000749366004620044a6565b620012ec565b6040516200038891906200497e565b620007756200076f366004620044a6565b620012f9565b60405162000388949392919062004993565b6200039b62000798366004620049cf565b6200131a565b620005c6620007af36600462004a42565b6200134f565b620007cc620007c636600462004594565b6200135d565b604080516001600160401b0393841681529290911660208301520162000388565b6200037c620007fe3660046200433d565b6200136c565b6200081b62000815366004620044a6565b620013f6565b604051620003889695949392919062004a68565b6200033562000840366004620044a6565b62001440565b62000335620008573660046200444b565b6200148f565b6200039b6200086e3660046200433d565b620014ae565b620004d462000885366004620044a6565b620014bb565b620003356200089c3660046200433d565b620014c8565b620004d462001533565b6200043e60405180604001604052806005815260200164312e302e3160d81b81525081565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf62920766620008f462001533565b6040516001600160e01b031960e084901b1681526001600160a01b03918216600482015290841660248201526044015b60006040518083038186803b1580156200093d57600080fd5b505af415801562000952573d6000803e3d6000fd5b5050505050565b620009796200096762001533565b6200097162001566565b84846200158b565b5050565b6200099c6200098b62001533565b6200099562001566565b83620016a6565b50565b6000620009ad838362001733565b90505b92915050565b6000620009c26200176b565b905090565b6000620009d3620017a4565b805490915060ff600160401b82041615906001600160401b0316600081158015620009fb5750825b90506000826001600160401b0316600114801562000a185750303b155b90508115801562000a27575080155b1562000a465760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831562000a7157845460ff60401b1916600160401b1785555b62000a7c86620017c8565b69021e19e0c9bab240000062000a91620017dd565b600260009081526020919091526040902055831562000aeb57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2906020015b60405180910390a15b505050505050565b62000afd62001801565b62000b1c62000b0b62001533565b62000b1562001566565b8362001838565b6200099c62001848565b6200099c62000b3462001533565b62000b3e62001566565b8362001854565b6000620009c262000b7860017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b62004af8565b5490565b6000620009b08262001c36565b6000620009b08262001cae565b6060620009b08262001cdc565b604051633ec2d25560e01b8152600481018490526001600160a01b03831660248201526001600160401b03821660448201527336c1cb5bcd78541c1f70ed60adaa342ce69668bf90633ec2d255906064015b60006040518083038186803b15801562000c0e57600080fd5b505af415801562000c23573d6000803e3d6000fd5b50505050505050565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf63c034186a620008f462001533565b6000620009c262000b7860017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f162004af8565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf63dcc9fe3362000ca762001533565b836040518363ffffffff1660e01b81526004016200092492919062004b0e565b33301462000d1a5760405162461bcd60e51b815260206004820152601b60248201527a24b731b932b0b9b2903932bb34b9b4b7b7103337b93134b23232b760291b60448201526064015b60405180910390fd5b600062000d3c62000b78600160008051602062005b3a83398151915262004af8565b62000d4990600162004b27565b905062000d6f8162000d6c600160008051602062005b3a83398151915262004af8565b55565b62000da18262000d6c60017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e462004af8565b7ff27e2ef832a4eb8ed8ec553b875eecd44764cda95b1c24170e281539e0a869c8818360405162000dd492919062004b3d565b60405180910390a15050565b60006001600160a01b03821673d8253782c45a12053594b9deb72d8e8ab2fca54c148062000e2a57506001600160a01b0382167352ceba41da235af367bfc0b0ccd3314cb901bb5f145b80620009b057506001600160a01b03821673102f1f556cd9c3d5f820e6920a8931657c5da21b1492915050565b6000620009c262001d92565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf63b630add362000e8762001533565b62000e9162001566565b6040516001600160e01b031960e085901b1681526001600160a01b03928316600482015290821660248201529085166044820152606481018490526084015b60006040518083038186803b15801562000ee957600080fd5b505af415801562000aeb573d6000803e3d6000fd5b6200097962000f0c62001533565b838362001da8565b6000620009c262001dbf565b6000620009b08262001dd5565b600062000f52838360ff16600c81111562000f4c5762000f4c62004acc565b62001e6f565b95945050505050565b62000f6562001801565b62000b1c62000f7362001533565b62000f7d62001566565b8362001edb565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf63276c0b5462000fa862001533565b84846040518463ffffffff1660e01b815260040162000ed09392919062004b54565b6200099c62000fd862001533565b62000fe262001566565b8362001f4f565b6000620009b082620020fe565b6000620009c262000b78600160008051602062005b3a83398151915262004af8565b6060620009b08262002133565b620009796200103362002165565b6200103d62001533565b6200104762001566565b858562002180565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf633817bf186200107362001533565b6200107d62001566565b85856040518563ffffffff1660e01b815260040162000ed0949392919062004b75565b620010c1620010ae62001533565b620010b862001566565b8585856200247b565b505050565b62000979620010d462001533565b620010de62001566565b848462002519565b6000620009c262000b7860017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e462004af8565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf632034b8566200113d62001533565b6040516001600160e01b031960e084901b1681526001600160a01b03909116600482015260248101869052604481018590526064810184905260840162000bf5565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf6372eb3f0f620011a362001533565b620011ad62001566565b6040516001600160e01b031960e085901b1681526001600160a01b0392831660048201529116602482015260ff8416604482015260640162000924565b6000620011f662001533565b6001600160a01b0316826001600160a01b0316149050919050565b6200121b62001801565b6200123b6200122962001533565b6200123362001566565b8484620025ab565b6200097962001848565b620012636200125362001533565b6200125d62001566565b620025bb565b565b600080620012926200127662001533565b8585600c8111156200128c576200128c62004acc565b620026bf565b915091509250929050565b620010c1620012ab62001533565b620012b562001566565b85858562002746565b6000620009b08262002767565b620010c1620012d962001533565b620012e362001566565b858585620027c8565b6060620009b08262002878565b600080600060606200130b85620028a0565b93509350935093509193509193565b6000620013476200132a62002165565b6200133462001533565b6200133e62001566565b878787620029f9565b949350505050565b6060620009ad838362002d2d565b60008062001292848462002da4565b6000816001600160a01b03166200138262001533565b6001600160a01b0316635aa6e6756040518163ffffffff1660e01b8152600401602060405180830381865afa158015620013c0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620013e6919062004bec565b6001600160a01b03161492915050565b60608060008060008060006200140c8862002e0a565b805160208201516040830151606084015160a0850151608090950151939d929c50909a50985091965060ff16945092505050565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf63f033ee966200146462001533565b6200146e62001566565b846040518463ffffffff1660e01b8152600401620009249392919062004c0c565b6200099c6200149d62001533565b620014a762001566565b8362002fe8565b6000620009b0826200307d565b6000620009b082620030ab565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf637a838ea3620014ec62001533565b620014f662001566565b6040516001600160e01b031960e085901b1681526001600160a01b0392831660048201529082166024820152908416604482015260640162000924565b6000620009c262000b7860017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c361862004af8565b6000620015733362000de0565b1562001586575060131936013560601c90565b503390565b6200159684620030d4565b8015620016a0576000620015ac84600162003158565b90506000620015ba620017dd565b60008381526001919091016020526040908190205490516323b872dd60e01b81526001600160a01b039182169250908516906323b872dd90620016069088908590889060040162004c0c565b6020604051808303816000875af115801562001626573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200164c919062004c30565b50604080516001600160a01b03808816825260208201859052831691810191909152606081018490527fb1046085ab4ac91f6ced0d4beef8a2095733fe9be5033ca39fca9c218f7d505d9060800162000ae2565b50505050565b6000620016b684846002620026bf565b509050620016c482620031ae565b81620016cf620017dd565b60008381526002919091016020526040902060010190620016f1908262004cd8565b507f6b7abe944ce1beac4e645644ff5549a6f5280e2688588c1156e296f1b02a361f81836040516200172592919062004da4565b60405180910390a150505050565b60006200173f620017dd565b6007016000620017508585620031d2565b815260208101919091526040016000205460ff169392505050565b600062001777620017dd565b600060025b600681111562001790576200179062004acc565b815260200190815260200160002054905090565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b620017d262003239565b6200099c8162003261565b7f1c4340ff8478a236ed13a5ce46f8e8b8a6037975df340a04c54725978699d10090565b60026200180d62003381565b036200182c57604051633ee5aeb560e01b815260040160405180910390fd5b62001263600262003397565b620010c1838383600080620033b7565b62001263600162003397565b6000816001600160a01b0316836001600160a01b03160362001890576200187b84620030d4565b6200188883600162003158565b905062001944565b6000620018a085856005620026bf565b9092509050620018b0836200349b565b6000806000620018c286600062001e6f565b9250925092508080620018d55750848314155b806200191c5750620018e86007620034c3565b8216151580620019045750620018ff6005620034c3565b821615155b80156200191c5750620019186000620034c3565b8416155b156200193f578560405163f652640360e01b815260040162000d11919062004580565b505050505b600062001950620017dd565b60020160008381526020019081526020016000206040518060c0016040529081600082018054620019819062004c50565b80601f0160208091040260200160405190810160405280929190818152602001828054620019af9062004c50565b801562001a005780601f10620019d45761010080835404028352916020019162001a00565b820191906000526020600020905b815481529060010190602001808311620019e257829003601f168201915b5050505050815260200160018201805462001a1b9062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462001a499062004c50565b801562001a9a5780601f1062001a6e5761010080835404028352916020019162001a9a565b820191906000526020600020905b81548152906001019060200180831162001a7c57829003601f168201915b5050509183525050600291909101546001600160a01b038082166020840152600160a01b820460ff908116604080860191909152600160a81b84049091166060850152600160b01b9092046001600160401b031660809093019290925282015191925084811691160362001b5057600162001b3062001b18620017dd565b600085815260059190910160205260409020620034e7565b111562001b5057604051630ad25aaf60e21b815260040160405180910390fd5b62001b788362001b5f620017dd565b60008581526005919091016020526040902090620034f2565b5062001b83620017dd565b6001600160a01b0384166000908152600491909101602052604081205562001baa620017dd565b6006016000846001600160a01b03166001600160a01b03168152602001908152602001600020600090557fc1a2a88b40ad3868bcda984d463d13c8a5ae7b0b3c3f56cb17ab7c34b7ad0928828460405162001c0792919062004b3d565b60405180910390a1600062001c1f62001b18620017dd565b90508060000362000aeb5762000aeb838362003509565b60008062001c43620017dd565b6000848152600b91909101602090815260409182902082518084019093525460ff811615158084526101009091046001600160c01b03169183019190915290915062001c9857670de0b6b3a764000062001ca7565b80602001516001600160c01b03165b9392505050565b600062001cba620017dd565b6001600160a01b03909216600090815260049290920160205250604090205490565b606062001ce8620017dd565b6000838152600d9190910160205260409020805462001d079062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462001d359062004c50565b801562001d865780601f1062001d5a5761010080835404028352916020019162001d86565b820191906000526020600020905b81548152906001019060200180831162001d6857829003601f168201915b50505050509050919050565b600062001d9e620017dd565b600060056200177c565b62001db2620036e8565b620010c183838362003724565b600062001dcb620017dd565b600060066200177c565b60008062001de262001d92565b90506001600160a01b0381161562001e6657604051635f1c54df60e01b8152600481018490526001600160a01b03821690635f1c54df90602401602060405180830381865afa15801562001e3a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062001e60919062004dbf565b62001ca7565b60009392505050565b600080600062001e8185600062003158565b925062001e8d620017dd565b6001600160a01b03861660009081526006919091016020526040812054925062001eb790620034c3565b821615158062001ed2575062001ecd84620034c3565b821615155b90509250925092565b600062001ee7620017dd565b6000838152600a919091016020526040902080549091506001600160a01b03848116610100909204161462001f2f5760405163475d17bf60e11b815260040160405180910390fd5b62001f3a84620030d4565b620016a08382836002015485600388620037fe565b600062001f5f84846001620026bf565b50905062001f6d8262003952565b600062001f79620017dd565b600083815260029190910160205260409020805462001f989062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462001fc69062004c50565b8015620020175780601f1062001feb5761010080835404028352916020019162002017565b820191906000526020600020905b81548152906001019060200180831162001ff957829003601f168201915b5050505050905062002028620017dd565b600301816040516200203b919062004dd9565b9081526020016040518091039020600090558262002058620017dd565b6000848152600291909101602052604090209062002077908262004cd8565b508162002083620017dd565b6003018460405162002096919062004dd9565b90815260405190819003602001902055620020bc85620020b56200176b565b8662003724565b7f957c37926a13c72eb069ef4bc862852a20cd897a1445c25acef64c285a3e3f878284604051620020ef92919062004da4565b60405180910390a15050505050565b60006200210a620017dd565b600301826040516200211d919062004dd9565b9081526020016040518091039020549050919050565b6060620009b062002143620017dd565b6001600160a01b03841660009081526009919091016020526040902062003a0e565b6000620021723362000de0565b80620009c257505032331490565b6200218b8562003a1d565b6200219684620030d4565b620021a18362003a3c565b610100815110620021c5576040516314d0397360e01b815260040160405180910390fd5b6000620021d28362001c36565b90508015620022e457846001600160a01b031663c3dfdae66040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200221a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062002240919062004bec565b6001600160a01b03166323b872dd8562002259620017dd565b60008781526001919091016020526040908190205490516001600160e01b031960e085901b1681526200229c92916001600160a01b031690869060040162004c0c565b6020604051808303816000875af1158015620022bc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620022e2919062004c30565b505b6000620022f2600362003a82565b9050620023498162002303620017dd565b60008781526008919091016020526040812090805b60038111156200232c576200232c62004acc565b815260200190815260200160002062003b2490919063ffffffff16565b506200237c8162002359620017dd565b6001600160a01b0388166000908152600991909101602052604090209062003b24565b5060408051608081018252600081526001600160a01b038716602082015290810184905260608101859052620023b1620017dd565b6000838152600a919091016020526040902081518154829060ff19166001836003811115620023e457620023e462004acc565b0217905550602082015181546001600160a01b0390911661010002610100600160a81b03199091161781556040820151600182019062002425908262004cd8565b50606082015181600201559050507fd07ce06fd1bb03672b0b44c874f02b94a1185e2c0084110a2211afa19e2c38b5858585856040516200246a949392919062004df7565b60405180910390a150505050505050565b60006200248b8686600662003b32565b83518351919250908114620024b35760405163586cb9e160e01b815260040160405180910390fd5b60005b818110156200250f5762002506838888888581518110620024db57620024db62004e31565b6020026020010151888681518110620024f857620024f862004e31565b602002602001015162003b70565b600101620024b6565b5050505050505050565b60006200252985856009620026bf565b5090508162002537620017dd565b6007016000620025488487620031d2565b81526020808201929092526040908101600020805460ff1916931515939093179092558151838152908101859052831515918101919091527fa38f02cb01028fc56d5a1c55764e771a6da2ec8205696148f0655ebcf322d8d690606001620020ef565b620016a0848484600185620033b7565b6000620025cb83836008620026bf565b5090506000620025da620017dd565b60008381526002918201602052604090200154600160a01b900460ff169050600919810162002622576040516302dc77b560e31b815260ff8216600482015260240162000d11565b60006200263182600162004e47565b9050806200263e620017dd565b600085815260029182016020526040902001805460ff60a01b1916600160a01b60ff938416021790556200268690869083166200267a6200176b565b620020b5919062004e63565b6040805184815260ff831660208201527ff59393d3d727f52fd7d452c581b5b52711e6e44063d1ddf9368817b75fb2c2159101620020ef565b600080620026cd85620030d4565b6000620026db858562001e6f565b9194509250905060008390036200270557604051632f6c853960e11b815260040160405180910390fd5b806200273d5783600c81111562002720576200272062004acc565b604051630d816ed160e01b815260040162000d1191815260200190565b50935093915050565b6000620027568686600662003b32565b905062000aeb818686868662003b70565b600062002773620017dd565b60008360068111156200278a576200278a62004acc565b60068111156200279e576200279e62004acc565b6006811115620027b257620027b262004acc565b8152602001908152602001600020549050919050565b6000620027d88686600a62003b32565b6040516361d4c9a160e11b81529091506001600160a01b0382169063c3a99342906200280d9087908790879060040162004e7d565b600060405180830381600087803b1580156200282857600080fd5b505af11580156200283d573d6000803e3d6000fd5b505050507f510bbc3f8229fd79dd4d20101e3a636ac34562b81999bc3287c0140d5e610f378584848760405162000ae2949392919062004eb7565b6060620009b062002888620017dd565b60008481526005919091016020526040902062003a0e565b600080600060606000620028b3620017dd565b6000878152600a919091016020526040908190208151608081019092528054829060ff166003811115620028eb57620028eb62004acc565b6003811115620028ff57620028ff62004acc565b8152815461010090046001600160a01b031660208201526001820180546040909201916200292d9062004c50565b80601f01602080910402602001604051908101604052809291908181526020018280546200295b9062004c50565b8015620029ac5780601f106200298057610100808354040283529160200191620029ac565b820191906000526020600020905b8154815290600101906020018083116200298e57829003601f168201915b50505050508152602001600282015481525050905080600001516003811115620029da57620029da62004acc565b8160200151826060015183604001519450945094509450509193509193565b600062002a068762003a1d565b62002a1186620030d4565b62002a1c8562003a3c565b62002a278462003952565b62002a328262003c3b565b62002a3d83620031ae565b600062002a4b600162003a82565b6040805160c081018252878152602081018790526001600160a01b038916918101919091526001606082015260ff85166080820152600060a08201529091508062002a95620017dd565b6000848152600291909101602052604090208151819062002ab7908262004cd8565b506020820151600182019062002ace908262004cd8565b506040820151600290910180546060840151608085015160a0909501516001600160401b0316600160b01b0267ffffffffffffffff60b01b1960ff968716600160a81b021668ffffffffffffffffff60a81b1996909216600160a01b026001600160a81b03199093166001600160a01b03909516949094179190911793909316929092171790558162002b60620017dd565b6003018760405162002b73919062004dd9565b9081526040519081900360200190205562002bab8762002b92620017dd565b6000858152600591909101602052604090209062003c93565b508162002bb7620017dd565b6001600160a01b0389166000908152600491909101602052604081209190915562002be290620034c3565b62002bec620017dd565b6006016000896001600160a01b03166001600160a01b031681526020019081526020016000208190555062002c86886001600160a01b03166325eb1c876040518163ffffffff1660e01b8152600401602060405180830381865afa15801562002c59573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062002c7f919062004bec565b8362003caa565b62002c90620017dd565b60008481526001919091016020526040902080546001600160a01b0319166001600160a01b039290921691909117905562002cd68862002ccf6200176b565b8962003724565b7f2d1a6a0246120b48ff69503752990307de08cdefea86af0ba0e15bb6111761c28160400151838360000151846020015160405162002d19949392919062004f04565b60405180910390a150979650505050505050565b6060620009ad62002d3d620017dd565b6000858152600891909101602052604081209060ff8516600381111562002d685762002d6862004acc565b600381111562002d7c5762002d7c62004acc565b600381111562002d905762002d9062004acc565b815260200190815260200160002062003a0e565b600080600062002db3620017dd565b6000958652600c01602090815260408087206001600160a01b03969096168752948152948490208451808601909552546001600160401b03808216808752600160401b909204169490950184905250929391925050565b6040805160c081018252606080825260208201819052600092820183905281018290526080810182905260a081019190915262002e46620017dd565b60020160008381526020019081526020016000206040518060c001604052908160008201805462002e779062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462002ea59062004c50565b801562002ef65780601f1062002eca5761010080835404028352916020019162002ef6565b820191906000526020600020905b81548152906001019060200180831162002ed857829003601f168201915b5050505050815260200160018201805462002f119062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462002f3f9062004c50565b801562002f905780601f1062002f645761010080835404028352916020019162002f90565b820191906000526020600020905b81548152906001019060200180831162002f7257829003601f168201915b5050509183525050600291909101546001600160a01b038116602083015260ff600160a01b820481166040840152600160a81b82041660608301526001600160401b03600160b01b9091041660809091015292915050565b600062002ff884846001620026bf565b5090506128008251106200301f57604051630670bc4b60e41b815260040160405180910390fd5b816200302a620017dd565b6000838152600d91909101602052604090209062003049908262004cd8565b507fcd5cc2801a54490a22d829a31ba7a860f6f4940ebf81b153388f1f9b38acdada81836040516200172592919062004da4565b600062003089620017dd565b6001600160a01b03909216600090815260069290920160205250604090205490565b6000620030b7620017dd565b60009283526001016020525060409020546001600160a01b031690565b806001600160a01b03166318d928316040518163ffffffff1660e01b8152600401602060405180830381865afa15801562003113573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062003139919062004c30565b156200099c57604051635e1633d360e11b815260040160405180910390fd5b600062003164620017dd565b6001600160a01b038416600090815260049190910160205260409020549050801580156200318f5750815b15620009b057604051632f6c853960e11b815260040160405180910390fd5b6101008151106200099c57604051630595c1cb60e01b815260040160405180910390fd5b60008183106200320c57604080516020810184905290810184905260600160405160208183030381529060405280519060200120620009ad565b50604080516020808201949094528082019290925280518083038201815260609092019052805191012090565b6200324362003d2a565b6200126357604051631afcd79f60e31b815260040160405180910390fd5b6001600160a01b038116620032ab5760405162461bcd60e51b815260206004820152600f60248201526e2d32b9379031b7b73a3937b63632b960891b604482015260640162000d11565b620032dd8162000d6c60017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c361862004af8565b6200330f4262000d6c60017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b62004af8565b620033414362000d6c60017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f162004af8565b7f1a2dd071001ebf6e03174e3df5b305795a4ad5d41d8fdb9ba41dbbe236713426814243604051620033769392919062004b54565b60405180910390a150565b60006200338d620017dd565b600060046200177c565b80620033a2620017dd565b60046000908152602091909152604090205550565b600080620033c887876004620026bf565b915091506000620033d8620017dd565b600a01600087815260200190815260200160002090508060020154831462003413576040516311f0c5d360e31b815260040160405180910390fd5b805461010090046001600160a01b0316620034438883868a8a620034395760026200343c565b60015b86620037fe565b851562003490578415801590620034655750620034616000620034c3565b8316155b156200348357604051624ac4e960e91b815260040160405180910390fd5b6200349084828762003d46565b505050505050505050565b6001600160a01b0381166200099c5760405163d92e233d60e01b815260040160405180910390fd5b600081600c811115620034da57620034da62004acc565b620009b090600262005049565b6000620009b0825490565b6000620009ad836001600160a01b0384166200403e565b62003513620017dd565b60030181600001516040516200352a919062004dd9565b90815260200160405180910390206000905562003546620017dd565b60008381526002919091016020526040812090620035658282620042bf565b62003575600183016000620042bf565b5060020180546001600160f01b031916905560006200359362001d92565b90506001600160a01b03811615620036025760405163f04802d160e01b8152600481018490526001600160a01b0382169063f04802d190602401600060405180830381600087803b158015620035e857600080fd5b505af1158015620035fd573d6000803e3d6000fd5b505050505b60006200360e62001dbf565b90506001600160a01b03811615620036b75760405163905c1e9360e01b8152600481018590526000906001600160a01b0383169063905c1e93906024016040805180830381865afa15801562003668573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200368e919062005057565b5090508015620036b557604051636a34a9f760e01b81526004810182905260240162000d11565b505b6040518481527fbdb3d45d74febd27a49d5ee308393b18fc559fcb577ecd0892ed9c2a761e82589060200162001725565b620036f262001d92565b6001600160a01b0316336001600160a01b031614620012635760405163067c9f3160e41b815260040160405180910390fd5b826001600160a01b0316633c8f1bbe846001600160a01b031663c3dfdae66040518163ffffffff1660e01b8152600401602060405180830381865afa15801562003772573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062003798919062004bec565b6040516001600160e01b031960e084901b1681526001600160a01b039182166004820152602481018690529084166044820152606401600060405180830381600087803b158015620037e957600080fd5b505af115801562000c23573d6000803e3d6000fd5b60006200380a620017dd565b6000868152600891909101602090815260408083208380529182905290912090915062003838908562004142565b6200385557604051623eeaaf60e21b815260040160405180910390fd5b60008080526020829052604090206200386f90856200415b565b506200388d8482600086600381111562002318576200231862004acc565b5085548390879060ff19166001836003811115620038af57620038af62004acc565b0217905550620038e684620038c3620017dd565b6001600160a01b038516600090815260099190910160205260409020906200415b565b507fbe8562deda43ba3d8c92975e0e1bc8dbeabc44c887e110456fdcbf78d387a10c87858560038111156200391f576200391f62004acc565b604080516001600160a01b039485168152602081019390935260ff9091169082015290841660608201526080016200246a565b6200395c620017dd565b600301816040516200396f919062004dd9565b9081526020016040518091039020546000146200399f57604051639e4b268560e01b815260040160405180910390fd5b6014815110620039c2576040516370ab3a0360e11b815260040160405180910390fd5b620039cd8162004169565b620039eb576040516389974bd960e01b815260040160405180910390fd5b80516000036200099c5760405163b6f63d6560e01b815260040160405180910390fd5b6060600062001ca783620041e3565b806200099c57604051636221cab960e01b815260040160405180910390fd5b62003a46620017dd565b6001600160a01b03821660009081526004919091016020526040902054156200099c57604051630653bdf560e01b815260040160405180910390fd5b600062003a8e620017dd565b600083600681111562003aa55762003aa562004acc565b600681111562003ab95762003ab962004acc565b815260200190815260200160002054600162003ad6919062004b27565b90508062003ae3620017dd565b600084600681111562003afa5762003afa62004acc565b600681111562003b0e5762003b0e62004acc565b8152602081019190915260400160002055919050565b6000620009ad838362004240565b60008062003b42858585620026bf565b50905062003b4f620017dd565b600091825260010160205260409020546001600160a01b0316949350505050565b8015620009525762003b82826200349b565b6040516317d5759960e31b81526001600160a01b0386169063beabacc89062003bb49086908690869060040162004c0c565b600060405180830381600087803b15801562003bcf57600080fd5b505af115801562003be4573d6000803e3d6000fd5b5050604080516001600160a01b038089168252808816602083015291810185905290851660608201527f3421522a397fda51b3adedf86b5f00270b3cf25e34ae836db9e28a78b198d29f92506080019050620020ef565b60328160ff16111562003c67576040516391866ccb60e01b815260ff8216600482015260240162000d11565b600a8160ff1610156200099c576040516361aa824960e01b815260ff8216600482015260240162000d11565b6000620009ad836001600160a01b03841662004240565b6000828260405162003cbc90620042fe565b62003cc992919062004b0e565b604051809103906000f08015801562003ce6573d6000803e3d6000fd5b5090507fd8e93ad095135e43388f8c58b0d322a6edf452a4afd51e8a512d5f9d10576f2e828260405162003d1c92919062004b3d565b60405180910390a192915050565b600062003d36620017a4565b54600160401b900460ff16919050565b600062003d52620017dd565b60020160008581526020019081526020016000206040518060c001604052908160008201805462003d839062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462003db19062004c50565b801562003e025780601f1062003dd65761010080835404028352916020019162003e02565b820191906000526020600020905b81548152906001019060200180831162003de457829003601f168201915b5050505050815260200160018201805462003e1d9062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462003e4b9062004c50565b801562003e9c5780601f1062003e705761010080835404028352916020019162003e9c565b820191906000526020600020905b81548152906001019060200180831162003e7e57829003601f168201915b5050509183525050600291909101546001600160a01b038116602083015260ff600160a01b820481166040840152600160a81b82041660608301526001600160401b03600160b01b90910416608090910152905062003efb8362003a3c565b62003f076000620034c3565b82161562003f2857604051630c8c5a2b60e11b815260040160405180910390fd5b600062003f5062003f38620017dd565b600087815260059190910160205260409020620034e7565b905062003f61826060015162004292565b810362003f85576040516323e919eb60e21b81526004810182905260240162000d11565b62003fad8462003f94620017dd565b6000888152600591909101602052604090209062003c93565b508462003fb9620017dd565b6001600160a01b038616600090815260049190910160205260409020558262003fe1620017dd565b6006016000866001600160a01b03166001600160a01b03168152602001908152602001600020819055507fedf63ee99110a1075b654570237238201693bfe9ac063acb6eef59041880a7d88585604051620020ef92919062004b3d565b60008181526001830160205260408120548015620041375760006200406560018362004af8565b85549091506000906200407b9060019062004af8565b9050808214620040e75760008660000182815481106200409f576200409f62004e31565b9060005260206000200154905080876000018481548110620040c557620040c562004e31565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080620040fb57620040fb6200507c565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050620009b0565b6000915050620009b0565b60008181526001830160205260408120541515620009ad565b6000620009ad83836200403e565b600081815b8151811015620041d95760208282815181106200418f576200418f62004e31565b016020015160f81c1080620041c05750607f828281518110620041b657620041b662004e31565b016020015160f81c115b15620041d0575060009392505050565b6001016200416e565b5060019392505050565b60608160000180548060200260200160405190810160405280929190818152602001828054801562001d8657602002820191906000526020600020905b815481526020019060010190808311620042205750505050509050919050565b60008181526001830160205260408120546200428957508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155620009b0565b506000620009b0565b60006002620042a360018462005092565b60ff16620042b2919062004e63565b620009b090600a62004b27565b508054620042cd9062004c50565b6000825580601f10620042de575050565b601f0160209004906000526020600020908101906200099c91906200430c565b610a8b80620050af83390190565b5b808211156200432357600081556001016200430d565b5090565b6001600160a01b03811681146200099c57600080fd5b6000602082840312156200435057600080fd5b813562001ca78162004327565b600080604083850312156200437157600080fd5b82356200437e8162004327565b946020939093013593505050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715620043cd57620043cd6200438c565b604052919050565b600082601f830112620043e757600080fd5b81356001600160401b038111156200440357620044036200438c565b62004418601f8201601f1916602001620043a2565b8181528460208386010111156200442e57600080fd5b816020850160208301376000918101602001919091529392505050565b6000602082840312156200445e57600080fd5b81356001600160401b038111156200447557600080fd5b6200134784828501620043d5565b600080604083850312156200449757600080fd5b50508035926020909101359150565b600060208284031215620044b957600080fd5b5035919050565b60005b83811015620044dd578181015183820152602001620044c3565b50506000910152565b6000815180845262004500816020860160208601620044c0565b601f01601f19169290920160200192915050565b602081526000620009ad6020830184620044e6565b80356001600160401b03811681146200454157600080fd5b919050565b6000806000606084860312156200455c57600080fd5b833592506020840135620045708162004327565b915062001ed26040850162004529565b6001600160a01b0391909116815260200190565b60008060408385031215620045a857600080fd5b823591506020830135620045bc8162004327565b809150509250929050565b803560ff811681146200454157600080fd5b60008060408385031215620045ed57600080fd5b8235620045fa8162004327565b91506200460a60208401620045c7565b90509250929050565b60008151808452602080850194506020840160005b83811015620046465781518752958201959082019060010162004628565b509495945050505050565b602081526000620009ad602083018462004613565b600080604083850312156200467a57600080fd5b8235915060208301356001600160401b038111156200469857600080fd5b620046a685828601620043d5565b9150509250929050565b60006001600160401b03821115620046cc57620046cc6200438c565b5060051b60200190565b600082601f830112620046e857600080fd5b8135602062004701620046fb83620046b0565b620043a2565b8083825260208201915060208460051b8701019350868411156200472457600080fd5b602086015b848110156200474d5780356200473f8162004327565b835291830191830162004729565b509695505050505050565b600080604083850312156200476c57600080fd5b620047778362004529565b915060208301356001600160401b038111156200479357600080fd5b620046a685828601620046d6565b600080600060608486031215620047b757600080fd5b8335620047c48162004327565b92506020848101356001600160401b0380821115620047e257600080fd5b620047f088838901620046d6565b945060408701359150808211156200480757600080fd5b508501601f810187136200481a57600080fd5b80356200482b620046fb82620046b0565b81815260059190911b820183019083810190898311156200484b57600080fd5b928401925b828410156200486b5783358252928401929084019062004850565b80955050505050509250925092565b80151581146200099c57600080fd5b600080604083850312156200489d57600080fd5b823591506020830135620045bc816200487a565b600080600060608486031215620048c757600080fd5b505081359360208301359350604090920135919050565b600060208284031215620048f157600080fd5b620009ad82620045c7565b6000806000606084860312156200491257600080fd5b83356200491f8162004327565b92506020840135620049318162004327565b929592945050506040919091013590565b60008151808452602080850194506020840160005b83811015620046465781516001600160a01b03168752958201959082019060010162004957565b602081526000620009ad602083018462004942565b60ff8516815260018060a01b0384166020820152826040820152608060608201526000620049c56080830184620044e6565b9695505050505050565b600080600060608486031215620049e557600080fd5b83356001600160401b0380821115620049fd57600080fd5b62004a0b87838801620043d5565b9450602086013591508082111562004a2257600080fd5b5062004a3186828701620043d5565b92505062001ed260408501620045c7565b6000806040838503121562004a5657600080fd5b823591506200460a60208401620045c7565b60c08152600062004a7d60c0830189620044e6565b828103602084015262004a918189620044e6565b6001600160a01b03979097166040840152505060ff9390931660608401526001600160401b0391909116608083015260a09091015292915050565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b81810381811115620009b057620009b062004ae2565b6001600160a01b03929092168252602082015260400190565b80820180821115620009b057620009b062004ae2565b9182526001600160a01b0316602082015260400190565b6001600160a01b039390931683526020830191909152604082015260600190565b60006080820160018060a01b038088168452602081881660208601526001600160401b03871660408601526080606086015282865180855260a08701915060208801945060005b8181101562004bdc57855185168352948301949183019160010162004bbc565b50909a9950505050505050505050565b60006020828403121562004bff57600080fd5b815162001ca78162004327565b6001600160a01b039384168152919092166020820152604081019190915260600190565b60006020828403121562004c4357600080fd5b815162001ca7816200487a565b600181811c9082168062004c6557607f821691505b60208210810362004c8657634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115620010c1576000816000526020600020601f850160051c8101602086101562004cb75750805b601f850160051c820191505b8181101562000aeb5782815560010162004cc3565b81516001600160401b0381111562004cf45762004cf46200438c565b62004d0c8162004d05845462004c50565b8462004c8c565b602080601f83116001811462004d44576000841562004d2b5750858301515b600019600386901b1c1916600185901b17855562000aeb565b600085815260208120601f198616915b8281101562004d755788860151825594840194600190910190840162004d54565b508582101562004d945787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b828152604060208201526000620013476040830184620044e6565b60006020828403121562004dd257600080fd5b5051919050565b6000825162004ded818460208701620044c0565b9190910192915050565b60018060a01b038516815283602082015260806040820152600062004e206080830185620044e6565b905082606083015295945050505050565b634e487b7160e01b600052603260045260246000fd5b60ff8181168382160190811115620009b057620009b062004ae2565b8082028115828204841417620009b057620009b062004ae2565b6001600160a01b038416815260606020820181905260009062004ea39083018562004942565b8281036040840152620049c5818562004613565b600060018060a01b0380871683526080602084015262004edb608084018762004942565b838103604085015262004eef818762004613565b92505080841660608401525095945050505050565b60018060a01b038516815283602082015260806040820152600062004f2d6080830185620044e6565b828103606084015262004f418185620044e6565b979650505050505050565b600181815b8085111562004f8d57816000190482111562004f715762004f7162004ae2565b8085161562004f7f57918102915b93841c939080029062004f51565b509250929050565b60008262004fa657506001620009b0565b8162004fb557506000620009b0565b816001811462004fce576002811462004fd95762004ff9565b6001915050620009b0565b60ff84111562004fed5762004fed62004ae2565b50506001821b620009b0565b5060208310610133831016604e8410600b84101617156200501e575081810a620009b0565b6200502a838362004f4c565b806000190482111562005041576200504162004ae2565b029392505050565b6000620009ad838362004f95565b600080604083850312156200506b57600080fd5b505080516020909101519092909150565b634e487b7160e01b600052603160045260246000fd5b60ff8281168282160390811115620009b057620009b062004ae256fe60c060405234801561001057600080fd5b50604051610a8b380380610a8b83398101604081905261002f91610045565b6001600160a01b0390911660805260a05261007f565b6000806040838503121561005857600080fd5b82516001600160a01b038116811461006f57600080fd5b6020939093015192949293505050565b60805160a0516109e26100a9600039600060d1015260008181608d01526105f601526109e26000f3fe608060405234801561001057600080fd5b50600436106100835760003560e01c806325eb1c8714610088578063990db1fc146100cc578063beabacc814610101578063c3a9934214610116578063d11eccd614610129578063d742fa751461013c578063e1f21c671461014f578063e415c88d14610172578063ffa1ad7414610185575b600080fd5b6100af7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b6100f37f000000000000000000000000000000000000000000000000000000000000000081565b6040519081526020016100c3565b61011461010f366004610665565b6101b6565b005b61011461012436600461077b565b610271565b610114610137366004610665565b610389565b61011461014a36600461077b565b610427565b61016261015d366004610665565b610502565b60405190151581526020016100c3565b610114610180366004610665565b610586565b6101a960405180604001604052806005815260200164312e302e3160d81b81525081565b6040516100c3919061084d565b6101bf336105f4565b60405163a9059cbb60e01b81526001600160a01b0384169063a9059cbb906101ed908590859060040161089c565b6020604051808303816000875af115801561020c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061023091906108b5565b507f09c8833de2bf5450474df1a78131ef9aeb8daf0255c9a29223afe0b9bc918e33838383604051610264939291906108de565b60405180910390a1505050565b61027a336105f4565b81518151811461029d5760405163586cb9e160e01b815260040160405180910390fd5b60005b81811015610347578381815181106102ba576102ba610902565b60200260200101516001600160a01b03166323b872dd30878685815181106102e4576102e4610902565b60200260200101516040518463ffffffff1660e01b815260040161030a939291906108de565b600060405180830381600087803b15801561032457600080fd5b505af1158015610338573d6000803e3d6000fd5b505050508060010190506102a0565b507f67181210645375aeed5b843d2574626f3af2a5c7a3030560e73f9c83718f783884848460405161037b93929190610918565b60405180910390a150505050565b610392336105f4565b6040516323b872dd60e01b81526001600160a01b038316906323b872dd906103c2903090879086906004016108de565b600060405180830381600087803b1580156103dc57600080fd5b505af11580156103f0573d6000803e3d6000fd5b505050507f1a9851affc88c5574504b1c83ef8fdbbba8c218707b4f7f3ca30db850ec9d78b838383604051610264939291906108de565b610430336105f4565b8151815181146104535760405163586cb9e160e01b815260040160405180910390fd5b60005b818110156104fb5783818151811061047057610470610902565b60200260200101516001600160a01b031663095ea7b38685848151811061049957610499610902565b60200260200101516040518363ffffffff1660e01b81526004016104be92919061089c565b600060405180830381600087803b1580156104d857600080fd5b505af11580156104ec573d6000803e3d6000fd5b50505050806001019050610456565b5050505050565b600061050d336105f4565b60405163095ea7b360e01b81526001600160a01b0385169063095ea7b39061053b908690869060040161089c565b6020604051808303816000875af115801561055a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061057e91906108b5565b949350505050565b61058f336105f4565b60405163095ea7b360e01b81526001600160a01b0383169063095ea7b3906105bd908690859060040161089c565b600060405180830381600087803b1580156105d757600080fd5b505af11580156105eb573d6000803e3d6000fd5b50505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316816001600160a01b03161461064657604051639d6ebf3160e01b815260040160405180910390fd5b50565b80356001600160a01b038116811461066057600080fd5b919050565b60008060006060848603121561067a57600080fd5b61068384610649565b925061069160208501610649565b9150604084013590509250925092565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff811182821017156106e0576106e06106a1565b604052919050565b600067ffffffffffffffff821115610702576107026106a1565b5060051b60200190565b600082601f83011261071d57600080fd5b8135602061073261072d836106e8565b6106b7565b8083825260208201915060208460051b87010193508684111561075457600080fd5b602086015b848110156107705780358352918301918301610759565b509695505050505050565b60008060006060848603121561079057600080fd5b61079984610649565b925060208085013567ffffffffffffffff808211156107b757600080fd5b818701915087601f8301126107cb57600080fd5b81356107d961072d826106e8565b81815260059190911b8301840190848101908a8311156107f857600080fd5b938501935b8285101561081d5761080e85610649565b825293850193908501906107fd565b96505050604087013592508083111561083557600080fd5b50506108438682870161070c565b9150509250925092565b60006020808352835180602085015260005b8181101561087b5785810183015185820160400152820161085f565b506000604082860101526040601f19601f8301168501019250505092915050565b6001600160a01b03929092168252602082015260400190565b6000602082840312156108c757600080fd5b815180151581146108d757600080fd5b9392505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b038481168252606060208084018290528551918401829052600092868201929091906080860190855b81811015610966578551851683529483019491830191600101610948565b5050858103604087015286518082529082019350915080860160005b8381101561099e57815185529382019390820190600101610982565b50929897505050505050505056fea2646970667358221220b34e2248becc21f6403f2e0362c51adb202cadce8d670d124d50d9b868b3242664736f6c6343000817003322573091f17911fb166032a3d9e0554aa73d31b7b7ddea4a4dd2995650af84bda2646970667358221220eca4a3c365f2d5ad492f5574bb2e1bcb4ccc276f40ddfc1feff59b2b1559714e64736f6c63430008170033

Deployed Bytecode

0x60806040523480156200001157600080fd5b5060043610620003195760003560e01c8063865e021a11620001ab578063c3a9934211620000f2578063c3a993421462000721578063c6d577431462000738578063d0626ef9146200075e578063d3f489791462000787578063d5fd6fa2146200079e578063dc87655a14620007b5578063dee1f0e414620007ed578063df1dd9761462000804578063df70df4a146200082f578063e61204131462000846578063e79f0e96146200085d578063f2e0023d1462000874578063f2fde38b146200088b578063f77c479114620008a2578063ffa1ad7414620008ac57600080fd5b8063865e021a14620005af5780638a66c10c14620005d5578063936725ec14620005ec578063961e8bb5146200061157806398d2fb55146200062857806399f9e252146200063f5780639d0bcca01462000656578063a8b446b01462000660578063ad1036861462000677578063b429afeb146200068e578063b4a06d1f14620006a5578063b648063f14620006bc578063b8cc6a4214620006c6578063beabacc814620006f3578063c05c6776146200070a57600080fd5b80634593144c11620002705780634593144c146200047b5780634686069814620004855780634fac6ccd146200049c578063572b6c0514620004b35780635a9e5aa214620004ca5780635b7249a714620004e35780635bc8efc014620004fa5780635edee59614620005115780635f1c54df146200051b57806363e85d2d14620005325780636483b0d0146200054957806364c6afe7146200056057806366605ba4146200057757806371fb4f05146200058e5780637cc9638014620005a557600080fd5b8063075e5d32146200031e5780630909d479146200033757806311bc2ca2146200034e578063158914ac146200036557806315e812ad146200039157806319ab453c14620003aa5780631e359be814620003c157806321b741de14620003d8578063325a19f114620003ef57806333b4074214620003f95780633b4c989114620004105780633e74720314620004275780633ec2d255146200044d5780634400c1c31462000464575b600080fd5b620003356200032f3660046200433d565b620008d1565b005b62000335620003483660046200435d565b62000959565b620003356200035f3660046200444b565b6200097d565b6200037c6200037636600462004483565b6200099f565b60405190151581526020015b60405180910390f35b6200039b620009b6565b60405190815260200162000388565b62000335620003bb3660046200433d565b620009c7565b62000335620003d2366004620044a6565b62000af3565b62000335620003e93660046200433d565b62000b26565b6200039b62000b45565b6200039b6200040a366004620044a6565b62000b7c565b6200039b620004213660046200433d565b62000b89565b6200043e62000438366004620044a6565b62000b96565b60405162000388919062004514565b620003356200045e36600462004546565b62000ba3565b62000335620004753660046200433d565b62000c2c565b6200039b62000c50565b6200033562000496366004620044a6565b62000c83565b62000335620004ad3660046200433d565b62000cc7565b6200037c620004c43660046200433d565b62000de0565b620004d462000e57565b60405162000388919062004580565b62000335620004f43660046200435d565b62000e63565b620003356200050b36600462004594565b62000efe565b620004d462000f14565b6200039b6200052c366004620044a6565b62000f20565b6200037c62000543366004620045d9565b62000f2d565b620003356200055a366004620044a6565b62000f5b565b620003356200057136600462004483565b62000f84565b62000335620005883660046200444b565b62000fca565b6200039b6200059f3660046200444b565b62000fe9565b6200039b62000ff6565b620005c6620005c03660046200433d565b62001018565b60405162000388919062004651565b62000335620005e636600462004666565b62001025565b6200043e604051806040016040528060058152602001640312e302e360dc1b81525081565b620003356200062236600462004758565b6200104f565b6200033562000639366004620047a1565b620010a0565b620003356200065036600462004889565b620010c6565b620004d4620010e6565b6200033562000671366004620048b1565b62001119565b6200033562000688366004620048de565b6200117f565b6200037c6200069f3660046200433d565b620011ea565b62000335620006b636600462004483565b62001211565b6200033562001245565b620006dd620006d73660046200435d565b62001265565b6040805192835260208301919091520162000388565b6200033562000704366004620048fc565b6200129d565b6200039b6200071b366004620044a6565b620012be565b6200033562000732366004620047a1565b620012cb565b6200074f62000749366004620044a6565b620012ec565b6040516200038891906200497e565b620007756200076f366004620044a6565b620012f9565b60405162000388949392919062004993565b6200039b62000798366004620049cf565b6200131a565b620005c6620007af36600462004a42565b6200134f565b620007cc620007c636600462004594565b6200135d565b604080516001600160401b0393841681529290911660208301520162000388565b6200037c620007fe3660046200433d565b6200136c565b6200081b62000815366004620044a6565b620013f6565b604051620003889695949392919062004a68565b6200033562000840366004620044a6565b62001440565b62000335620008573660046200444b565b6200148f565b6200039b6200086e3660046200433d565b620014ae565b620004d462000885366004620044a6565b620014bb565b620003356200089c3660046200433d565b620014c8565b620004d462001533565b6200043e60405180604001604052806005815260200164312e302e3160d81b81525081565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf62920766620008f462001533565b6040516001600160e01b031960e084901b1681526001600160a01b03918216600482015290841660248201526044015b60006040518083038186803b1580156200093d57600080fd5b505af415801562000952573d6000803e3d6000fd5b5050505050565b620009796200096762001533565b6200097162001566565b84846200158b565b5050565b6200099c6200098b62001533565b6200099562001566565b83620016a6565b50565b6000620009ad838362001733565b90505b92915050565b6000620009c26200176b565b905090565b6000620009d3620017a4565b805490915060ff600160401b82041615906001600160401b0316600081158015620009fb5750825b90506000826001600160401b0316600114801562000a185750303b155b90508115801562000a27575080155b1562000a465760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831562000a7157845460ff60401b1916600160401b1785555b62000a7c86620017c8565b69021e19e0c9bab240000062000a91620017dd565b600260009081526020919091526040902055831562000aeb57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2906020015b60405180910390a15b505050505050565b62000afd62001801565b62000b1c62000b0b62001533565b62000b1562001566565b8362001838565b6200099c62001848565b6200099c62000b3462001533565b62000b3e62001566565b8362001854565b6000620009c262000b7860017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b62004af8565b5490565b6000620009b08262001c36565b6000620009b08262001cae565b6060620009b08262001cdc565b604051633ec2d25560e01b8152600481018490526001600160a01b03831660248201526001600160401b03821660448201527336c1cb5bcd78541c1f70ed60adaa342ce69668bf90633ec2d255906064015b60006040518083038186803b15801562000c0e57600080fd5b505af415801562000c23573d6000803e3d6000fd5b50505050505050565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf63c034186a620008f462001533565b6000620009c262000b7860017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f162004af8565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf63dcc9fe3362000ca762001533565b836040518363ffffffff1660e01b81526004016200092492919062004b0e565b33301462000d1a5760405162461bcd60e51b815260206004820152601b60248201527a24b731b932b0b9b2903932bb34b9b4b7b7103337b93134b23232b760291b60448201526064015b60405180910390fd5b600062000d3c62000b78600160008051602062005b3a83398151915262004af8565b62000d4990600162004b27565b905062000d6f8162000d6c600160008051602062005b3a83398151915262004af8565b55565b62000da18262000d6c60017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e462004af8565b7ff27e2ef832a4eb8ed8ec553b875eecd44764cda95b1c24170e281539e0a869c8818360405162000dd492919062004b3d565b60405180910390a15050565b60006001600160a01b03821673d8253782c45a12053594b9deb72d8e8ab2fca54c148062000e2a57506001600160a01b0382167352ceba41da235af367bfc0b0ccd3314cb901bb5f145b80620009b057506001600160a01b03821673102f1f556cd9c3d5f820e6920a8931657c5da21b1492915050565b6000620009c262001d92565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf63b630add362000e8762001533565b62000e9162001566565b6040516001600160e01b031960e085901b1681526001600160a01b03928316600482015290821660248201529085166044820152606481018490526084015b60006040518083038186803b15801562000ee957600080fd5b505af415801562000aeb573d6000803e3d6000fd5b6200097962000f0c62001533565b838362001da8565b6000620009c262001dbf565b6000620009b08262001dd5565b600062000f52838360ff16600c81111562000f4c5762000f4c62004acc565b62001e6f565b95945050505050565b62000f6562001801565b62000b1c62000f7362001533565b62000f7d62001566565b8362001edb565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf63276c0b5462000fa862001533565b84846040518463ffffffff1660e01b815260040162000ed09392919062004b54565b6200099c62000fd862001533565b62000fe262001566565b8362001f4f565b6000620009b082620020fe565b6000620009c262000b78600160008051602062005b3a83398151915262004af8565b6060620009b08262002133565b620009796200103362002165565b6200103d62001533565b6200104762001566565b858562002180565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf633817bf186200107362001533565b6200107d62001566565b85856040518563ffffffff1660e01b815260040162000ed0949392919062004b75565b620010c1620010ae62001533565b620010b862001566565b8585856200247b565b505050565b62000979620010d462001533565b620010de62001566565b848462002519565b6000620009c262000b7860017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e462004af8565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf632034b8566200113d62001533565b6040516001600160e01b031960e084901b1681526001600160a01b03909116600482015260248101869052604481018590526064810184905260840162000bf5565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf6372eb3f0f620011a362001533565b620011ad62001566565b6040516001600160e01b031960e085901b1681526001600160a01b0392831660048201529116602482015260ff8416604482015260640162000924565b6000620011f662001533565b6001600160a01b0316826001600160a01b0316149050919050565b6200121b62001801565b6200123b6200122962001533565b6200123362001566565b8484620025ab565b6200097962001848565b620012636200125362001533565b6200125d62001566565b620025bb565b565b600080620012926200127662001533565b8585600c8111156200128c576200128c62004acc565b620026bf565b915091509250929050565b620010c1620012ab62001533565b620012b562001566565b85858562002746565b6000620009b08262002767565b620010c1620012d962001533565b620012e362001566565b858585620027c8565b6060620009b08262002878565b600080600060606200130b85620028a0565b93509350935093509193509193565b6000620013476200132a62002165565b6200133462001533565b6200133e62001566565b878787620029f9565b949350505050565b6060620009ad838362002d2d565b60008062001292848462002da4565b6000816001600160a01b03166200138262001533565b6001600160a01b0316635aa6e6756040518163ffffffff1660e01b8152600401602060405180830381865afa158015620013c0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620013e6919062004bec565b6001600160a01b03161492915050565b60608060008060008060006200140c8862002e0a565b805160208201516040830151606084015160a0850151608090950151939d929c50909a50985091965060ff16945092505050565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf63f033ee966200146462001533565b6200146e62001566565b846040518463ffffffff1660e01b8152600401620009249392919062004c0c565b6200099c6200149d62001533565b620014a762001566565b8362002fe8565b6000620009b0826200307d565b6000620009b082620030ab565b7336c1cb5bcd78541c1f70ed60adaa342ce69668bf637a838ea3620014ec62001533565b620014f662001566565b6040516001600160e01b031960e085901b1681526001600160a01b0392831660048201529082166024820152908416604482015260640162000924565b6000620009c262000b7860017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c361862004af8565b6000620015733362000de0565b1562001586575060131936013560601c90565b503390565b6200159684620030d4565b8015620016a0576000620015ac84600162003158565b90506000620015ba620017dd565b60008381526001919091016020526040908190205490516323b872dd60e01b81526001600160a01b039182169250908516906323b872dd90620016069088908590889060040162004c0c565b6020604051808303816000875af115801562001626573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200164c919062004c30565b50604080516001600160a01b03808816825260208201859052831691810191909152606081018490527fb1046085ab4ac91f6ced0d4beef8a2095733fe9be5033ca39fca9c218f7d505d9060800162000ae2565b50505050565b6000620016b684846002620026bf565b509050620016c482620031ae565b81620016cf620017dd565b60008381526002919091016020526040902060010190620016f1908262004cd8565b507f6b7abe944ce1beac4e645644ff5549a6f5280e2688588c1156e296f1b02a361f81836040516200172592919062004da4565b60405180910390a150505050565b60006200173f620017dd565b6007016000620017508585620031d2565b815260208101919091526040016000205460ff169392505050565b600062001777620017dd565b600060025b600681111562001790576200179062004acc565b815260200190815260200160002054905090565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b620017d262003239565b6200099c8162003261565b7f1c4340ff8478a236ed13a5ce46f8e8b8a6037975df340a04c54725978699d10090565b60026200180d62003381565b036200182c57604051633ee5aeb560e01b815260040160405180910390fd5b62001263600262003397565b620010c1838383600080620033b7565b62001263600162003397565b6000816001600160a01b0316836001600160a01b03160362001890576200187b84620030d4565b6200188883600162003158565b905062001944565b6000620018a085856005620026bf565b9092509050620018b0836200349b565b6000806000620018c286600062001e6f565b9250925092508080620018d55750848314155b806200191c5750620018e86007620034c3565b8216151580620019045750620018ff6005620034c3565b821615155b80156200191c5750620019186000620034c3565b8416155b156200193f578560405163f652640360e01b815260040162000d11919062004580565b505050505b600062001950620017dd565b60020160008381526020019081526020016000206040518060c0016040529081600082018054620019819062004c50565b80601f0160208091040260200160405190810160405280929190818152602001828054620019af9062004c50565b801562001a005780601f10620019d45761010080835404028352916020019162001a00565b820191906000526020600020905b815481529060010190602001808311620019e257829003601f168201915b5050505050815260200160018201805462001a1b9062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462001a499062004c50565b801562001a9a5780601f1062001a6e5761010080835404028352916020019162001a9a565b820191906000526020600020905b81548152906001019060200180831162001a7c57829003601f168201915b5050509183525050600291909101546001600160a01b038082166020840152600160a01b820460ff908116604080860191909152600160a81b84049091166060850152600160b01b9092046001600160401b031660809093019290925282015191925084811691160362001b5057600162001b3062001b18620017dd565b600085815260059190910160205260409020620034e7565b111562001b5057604051630ad25aaf60e21b815260040160405180910390fd5b62001b788362001b5f620017dd565b60008581526005919091016020526040902090620034f2565b5062001b83620017dd565b6001600160a01b0384166000908152600491909101602052604081205562001baa620017dd565b6006016000846001600160a01b03166001600160a01b03168152602001908152602001600020600090557fc1a2a88b40ad3868bcda984d463d13c8a5ae7b0b3c3f56cb17ab7c34b7ad0928828460405162001c0792919062004b3d565b60405180910390a1600062001c1f62001b18620017dd565b90508060000362000aeb5762000aeb838362003509565b60008062001c43620017dd565b6000848152600b91909101602090815260409182902082518084019093525460ff811615158084526101009091046001600160c01b03169183019190915290915062001c9857670de0b6b3a764000062001ca7565b80602001516001600160c01b03165b9392505050565b600062001cba620017dd565b6001600160a01b03909216600090815260049290920160205250604090205490565b606062001ce8620017dd565b6000838152600d9190910160205260409020805462001d079062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462001d359062004c50565b801562001d865780601f1062001d5a5761010080835404028352916020019162001d86565b820191906000526020600020905b81548152906001019060200180831162001d6857829003601f168201915b50505050509050919050565b600062001d9e620017dd565b600060056200177c565b62001db2620036e8565b620010c183838362003724565b600062001dcb620017dd565b600060066200177c565b60008062001de262001d92565b90506001600160a01b0381161562001e6657604051635f1c54df60e01b8152600481018490526001600160a01b03821690635f1c54df90602401602060405180830381865afa15801562001e3a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062001e60919062004dbf565b62001ca7565b60009392505050565b600080600062001e8185600062003158565b925062001e8d620017dd565b6001600160a01b03861660009081526006919091016020526040812054925062001eb790620034c3565b821615158062001ed2575062001ecd84620034c3565b821615155b90509250925092565b600062001ee7620017dd565b6000838152600a919091016020526040902080549091506001600160a01b03848116610100909204161462001f2f5760405163475d17bf60e11b815260040160405180910390fd5b62001f3a84620030d4565b620016a08382836002015485600388620037fe565b600062001f5f84846001620026bf565b50905062001f6d8262003952565b600062001f79620017dd565b600083815260029190910160205260409020805462001f989062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462001fc69062004c50565b8015620020175780601f1062001feb5761010080835404028352916020019162002017565b820191906000526020600020905b81548152906001019060200180831162001ff957829003601f168201915b5050505050905062002028620017dd565b600301816040516200203b919062004dd9565b9081526020016040518091039020600090558262002058620017dd565b6000848152600291909101602052604090209062002077908262004cd8565b508162002083620017dd565b6003018460405162002096919062004dd9565b90815260405190819003602001902055620020bc85620020b56200176b565b8662003724565b7f957c37926a13c72eb069ef4bc862852a20cd897a1445c25acef64c285a3e3f878284604051620020ef92919062004da4565b60405180910390a15050505050565b60006200210a620017dd565b600301826040516200211d919062004dd9565b9081526020016040518091039020549050919050565b6060620009b062002143620017dd565b6001600160a01b03841660009081526009919091016020526040902062003a0e565b6000620021723362000de0565b80620009c257505032331490565b6200218b8562003a1d565b6200219684620030d4565b620021a18362003a3c565b610100815110620021c5576040516314d0397360e01b815260040160405180910390fd5b6000620021d28362001c36565b90508015620022e457846001600160a01b031663c3dfdae66040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200221a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062002240919062004bec565b6001600160a01b03166323b872dd8562002259620017dd565b60008781526001919091016020526040908190205490516001600160e01b031960e085901b1681526200229c92916001600160a01b031690869060040162004c0c565b6020604051808303816000875af1158015620022bc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620022e2919062004c30565b505b6000620022f2600362003a82565b9050620023498162002303620017dd565b60008781526008919091016020526040812090805b60038111156200232c576200232c62004acc565b815260200190815260200160002062003b2490919063ffffffff16565b506200237c8162002359620017dd565b6001600160a01b0388166000908152600991909101602052604090209062003b24565b5060408051608081018252600081526001600160a01b038716602082015290810184905260608101859052620023b1620017dd565b6000838152600a919091016020526040902081518154829060ff19166001836003811115620023e457620023e462004acc565b0217905550602082015181546001600160a01b0390911661010002610100600160a81b03199091161781556040820151600182019062002425908262004cd8565b50606082015181600201559050507fd07ce06fd1bb03672b0b44c874f02b94a1185e2c0084110a2211afa19e2c38b5858585856040516200246a949392919062004df7565b60405180910390a150505050505050565b60006200248b8686600662003b32565b83518351919250908114620024b35760405163586cb9e160e01b815260040160405180910390fd5b60005b818110156200250f5762002506838888888581518110620024db57620024db62004e31565b6020026020010151888681518110620024f857620024f862004e31565b602002602001015162003b70565b600101620024b6565b5050505050505050565b60006200252985856009620026bf565b5090508162002537620017dd565b6007016000620025488487620031d2565b81526020808201929092526040908101600020805460ff1916931515939093179092558151838152908101859052831515918101919091527fa38f02cb01028fc56d5a1c55764e771a6da2ec8205696148f0655ebcf322d8d690606001620020ef565b620016a0848484600185620033b7565b6000620025cb83836008620026bf565b5090506000620025da620017dd565b60008381526002918201602052604090200154600160a01b900460ff169050600919810162002622576040516302dc77b560e31b815260ff8216600482015260240162000d11565b60006200263182600162004e47565b9050806200263e620017dd565b600085815260029182016020526040902001805460ff60a01b1916600160a01b60ff938416021790556200268690869083166200267a6200176b565b620020b5919062004e63565b6040805184815260ff831660208201527ff59393d3d727f52fd7d452c581b5b52711e6e44063d1ddf9368817b75fb2c2159101620020ef565b600080620026cd85620030d4565b6000620026db858562001e6f565b9194509250905060008390036200270557604051632f6c853960e11b815260040160405180910390fd5b806200273d5783600c81111562002720576200272062004acc565b604051630d816ed160e01b815260040162000d1191815260200190565b50935093915050565b6000620027568686600662003b32565b905062000aeb818686868662003b70565b600062002773620017dd565b60008360068111156200278a576200278a62004acc565b60068111156200279e576200279e62004acc565b6006811115620027b257620027b262004acc565b8152602001908152602001600020549050919050565b6000620027d88686600a62003b32565b6040516361d4c9a160e11b81529091506001600160a01b0382169063c3a99342906200280d9087908790879060040162004e7d565b600060405180830381600087803b1580156200282857600080fd5b505af11580156200283d573d6000803e3d6000fd5b505050507f510bbc3f8229fd79dd4d20101e3a636ac34562b81999bc3287c0140d5e610f378584848760405162000ae2949392919062004eb7565b6060620009b062002888620017dd565b60008481526005919091016020526040902062003a0e565b600080600060606000620028b3620017dd565b6000878152600a919091016020526040908190208151608081019092528054829060ff166003811115620028eb57620028eb62004acc565b6003811115620028ff57620028ff62004acc565b8152815461010090046001600160a01b031660208201526001820180546040909201916200292d9062004c50565b80601f01602080910402602001604051908101604052809291908181526020018280546200295b9062004c50565b8015620029ac5780601f106200298057610100808354040283529160200191620029ac565b820191906000526020600020905b8154815290600101906020018083116200298e57829003601f168201915b50505050508152602001600282015481525050905080600001516003811115620029da57620029da62004acc565b8160200151826060015183604001519450945094509450509193509193565b600062002a068762003a1d565b62002a1186620030d4565b62002a1c8562003a3c565b62002a278462003952565b62002a328262003c3b565b62002a3d83620031ae565b600062002a4b600162003a82565b6040805160c081018252878152602081018790526001600160a01b038916918101919091526001606082015260ff85166080820152600060a08201529091508062002a95620017dd565b6000848152600291909101602052604090208151819062002ab7908262004cd8565b506020820151600182019062002ace908262004cd8565b506040820151600290910180546060840151608085015160a0909501516001600160401b0316600160b01b0267ffffffffffffffff60b01b1960ff968716600160a81b021668ffffffffffffffffff60a81b1996909216600160a01b026001600160a81b03199093166001600160a01b03909516949094179190911793909316929092171790558162002b60620017dd565b6003018760405162002b73919062004dd9565b9081526040519081900360200190205562002bab8762002b92620017dd565b6000858152600591909101602052604090209062003c93565b508162002bb7620017dd565b6001600160a01b0389166000908152600491909101602052604081209190915562002be290620034c3565b62002bec620017dd565b6006016000896001600160a01b03166001600160a01b031681526020019081526020016000208190555062002c86886001600160a01b03166325eb1c876040518163ffffffff1660e01b8152600401602060405180830381865afa15801562002c59573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062002c7f919062004bec565b8362003caa565b62002c90620017dd565b60008481526001919091016020526040902080546001600160a01b0319166001600160a01b039290921691909117905562002cd68862002ccf6200176b565b8962003724565b7f2d1a6a0246120b48ff69503752990307de08cdefea86af0ba0e15bb6111761c28160400151838360000151846020015160405162002d19949392919062004f04565b60405180910390a150979650505050505050565b6060620009ad62002d3d620017dd565b6000858152600891909101602052604081209060ff8516600381111562002d685762002d6862004acc565b600381111562002d7c5762002d7c62004acc565b600381111562002d905762002d9062004acc565b815260200190815260200160002062003a0e565b600080600062002db3620017dd565b6000958652600c01602090815260408087206001600160a01b03969096168752948152948490208451808601909552546001600160401b03808216808752600160401b909204169490950184905250929391925050565b6040805160c081018252606080825260208201819052600092820183905281018290526080810182905260a081019190915262002e46620017dd565b60020160008381526020019081526020016000206040518060c001604052908160008201805462002e779062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462002ea59062004c50565b801562002ef65780601f1062002eca5761010080835404028352916020019162002ef6565b820191906000526020600020905b81548152906001019060200180831162002ed857829003601f168201915b5050505050815260200160018201805462002f119062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462002f3f9062004c50565b801562002f905780601f1062002f645761010080835404028352916020019162002f90565b820191906000526020600020905b81548152906001019060200180831162002f7257829003601f168201915b5050509183525050600291909101546001600160a01b038116602083015260ff600160a01b820481166040840152600160a81b82041660608301526001600160401b03600160b01b9091041660809091015292915050565b600062002ff884846001620026bf565b5090506128008251106200301f57604051630670bc4b60e41b815260040160405180910390fd5b816200302a620017dd565b6000838152600d91909101602052604090209062003049908262004cd8565b507fcd5cc2801a54490a22d829a31ba7a860f6f4940ebf81b153388f1f9b38acdada81836040516200172592919062004da4565b600062003089620017dd565b6001600160a01b03909216600090815260069290920160205250604090205490565b6000620030b7620017dd565b60009283526001016020525060409020546001600160a01b031690565b806001600160a01b03166318d928316040518163ffffffff1660e01b8152600401602060405180830381865afa15801562003113573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062003139919062004c30565b156200099c57604051635e1633d360e11b815260040160405180910390fd5b600062003164620017dd565b6001600160a01b038416600090815260049190910160205260409020549050801580156200318f5750815b15620009b057604051632f6c853960e11b815260040160405180910390fd5b6101008151106200099c57604051630595c1cb60e01b815260040160405180910390fd5b60008183106200320c57604080516020810184905290810184905260600160405160208183030381529060405280519060200120620009ad565b50604080516020808201949094528082019290925280518083038201815260609092019052805191012090565b6200324362003d2a565b6200126357604051631afcd79f60e31b815260040160405180910390fd5b6001600160a01b038116620032ab5760405162461bcd60e51b815260206004820152600f60248201526e2d32b9379031b7b73a3937b63632b960891b604482015260640162000d11565b620032dd8162000d6c60017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c361862004af8565b6200330f4262000d6c60017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b62004af8565b620033414362000d6c60017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f162004af8565b7f1a2dd071001ebf6e03174e3df5b305795a4ad5d41d8fdb9ba41dbbe236713426814243604051620033769392919062004b54565b60405180910390a150565b60006200338d620017dd565b600060046200177c565b80620033a2620017dd565b60046000908152602091909152604090205550565b600080620033c887876004620026bf565b915091506000620033d8620017dd565b600a01600087815260200190815260200160002090508060020154831462003413576040516311f0c5d360e31b815260040160405180910390fd5b805461010090046001600160a01b0316620034438883868a8a620034395760026200343c565b60015b86620037fe565b851562003490578415801590620034655750620034616000620034c3565b8316155b156200348357604051624ac4e960e91b815260040160405180910390fd5b6200349084828762003d46565b505050505050505050565b6001600160a01b0381166200099c5760405163d92e233d60e01b815260040160405180910390fd5b600081600c811115620034da57620034da62004acc565b620009b090600262005049565b6000620009b0825490565b6000620009ad836001600160a01b0384166200403e565b62003513620017dd565b60030181600001516040516200352a919062004dd9565b90815260200160405180910390206000905562003546620017dd565b60008381526002919091016020526040812090620035658282620042bf565b62003575600183016000620042bf565b5060020180546001600160f01b031916905560006200359362001d92565b90506001600160a01b03811615620036025760405163f04802d160e01b8152600481018490526001600160a01b0382169063f04802d190602401600060405180830381600087803b158015620035e857600080fd5b505af1158015620035fd573d6000803e3d6000fd5b505050505b60006200360e62001dbf565b90506001600160a01b03811615620036b75760405163905c1e9360e01b8152600481018590526000906001600160a01b0383169063905c1e93906024016040805180830381865afa15801562003668573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200368e919062005057565b5090508015620036b557604051636a34a9f760e01b81526004810182905260240162000d11565b505b6040518481527fbdb3d45d74febd27a49d5ee308393b18fc559fcb577ecd0892ed9c2a761e82589060200162001725565b620036f262001d92565b6001600160a01b0316336001600160a01b031614620012635760405163067c9f3160e41b815260040160405180910390fd5b826001600160a01b0316633c8f1bbe846001600160a01b031663c3dfdae66040518163ffffffff1660e01b8152600401602060405180830381865afa15801562003772573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062003798919062004bec565b6040516001600160e01b031960e084901b1681526001600160a01b039182166004820152602481018690529084166044820152606401600060405180830381600087803b158015620037e957600080fd5b505af115801562000c23573d6000803e3d6000fd5b60006200380a620017dd565b6000868152600891909101602090815260408083208380529182905290912090915062003838908562004142565b6200385557604051623eeaaf60e21b815260040160405180910390fd5b60008080526020829052604090206200386f90856200415b565b506200388d8482600086600381111562002318576200231862004acc565b5085548390879060ff19166001836003811115620038af57620038af62004acc565b0217905550620038e684620038c3620017dd565b6001600160a01b038516600090815260099190910160205260409020906200415b565b507fbe8562deda43ba3d8c92975e0e1bc8dbeabc44c887e110456fdcbf78d387a10c87858560038111156200391f576200391f62004acc565b604080516001600160a01b039485168152602081019390935260ff9091169082015290841660608201526080016200246a565b6200395c620017dd565b600301816040516200396f919062004dd9565b9081526020016040518091039020546000146200399f57604051639e4b268560e01b815260040160405180910390fd5b6014815110620039c2576040516370ab3a0360e11b815260040160405180910390fd5b620039cd8162004169565b620039eb576040516389974bd960e01b815260040160405180910390fd5b80516000036200099c5760405163b6f63d6560e01b815260040160405180910390fd5b6060600062001ca783620041e3565b806200099c57604051636221cab960e01b815260040160405180910390fd5b62003a46620017dd565b6001600160a01b03821660009081526004919091016020526040902054156200099c57604051630653bdf560e01b815260040160405180910390fd5b600062003a8e620017dd565b600083600681111562003aa55762003aa562004acc565b600681111562003ab95762003ab962004acc565b815260200190815260200160002054600162003ad6919062004b27565b90508062003ae3620017dd565b600084600681111562003afa5762003afa62004acc565b600681111562003b0e5762003b0e62004acc565b8152602081019190915260400160002055919050565b6000620009ad838362004240565b60008062003b42858585620026bf565b50905062003b4f620017dd565b600091825260010160205260409020546001600160a01b0316949350505050565b8015620009525762003b82826200349b565b6040516317d5759960e31b81526001600160a01b0386169063beabacc89062003bb49086908690869060040162004c0c565b600060405180830381600087803b15801562003bcf57600080fd5b505af115801562003be4573d6000803e3d6000fd5b5050604080516001600160a01b038089168252808816602083015291810185905290851660608201527f3421522a397fda51b3adedf86b5f00270b3cf25e34ae836db9e28a78b198d29f92506080019050620020ef565b60328160ff16111562003c67576040516391866ccb60e01b815260ff8216600482015260240162000d11565b600a8160ff1610156200099c576040516361aa824960e01b815260ff8216600482015260240162000d11565b6000620009ad836001600160a01b03841662004240565b6000828260405162003cbc90620042fe565b62003cc992919062004b0e565b604051809103906000f08015801562003ce6573d6000803e3d6000fd5b5090507fd8e93ad095135e43388f8c58b0d322a6edf452a4afd51e8a512d5f9d10576f2e828260405162003d1c92919062004b3d565b60405180910390a192915050565b600062003d36620017a4565b54600160401b900460ff16919050565b600062003d52620017dd565b60020160008581526020019081526020016000206040518060c001604052908160008201805462003d839062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462003db19062004c50565b801562003e025780601f1062003dd65761010080835404028352916020019162003e02565b820191906000526020600020905b81548152906001019060200180831162003de457829003601f168201915b5050505050815260200160018201805462003e1d9062004c50565b80601f016020809104026020016040519081016040528092919081815260200182805462003e4b9062004c50565b801562003e9c5780601f1062003e705761010080835404028352916020019162003e9c565b820191906000526020600020905b81548152906001019060200180831162003e7e57829003601f168201915b5050509183525050600291909101546001600160a01b038116602083015260ff600160a01b820481166040840152600160a81b82041660608301526001600160401b03600160b01b90910416608090910152905062003efb8362003a3c565b62003f076000620034c3565b82161562003f2857604051630c8c5a2b60e11b815260040160405180910390fd5b600062003f5062003f38620017dd565b600087815260059190910160205260409020620034e7565b905062003f61826060015162004292565b810362003f85576040516323e919eb60e21b81526004810182905260240162000d11565b62003fad8462003f94620017dd565b6000888152600591909101602052604090209062003c93565b508462003fb9620017dd565b6001600160a01b038616600090815260049190910160205260409020558262003fe1620017dd565b6006016000866001600160a01b03166001600160a01b03168152602001908152602001600020819055507fedf63ee99110a1075b654570237238201693bfe9ac063acb6eef59041880a7d88585604051620020ef92919062004b3d565b60008181526001830160205260408120548015620041375760006200406560018362004af8565b85549091506000906200407b9060019062004af8565b9050808214620040e75760008660000182815481106200409f576200409f62004e31565b9060005260206000200154905080876000018481548110620040c557620040c562004e31565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080620040fb57620040fb6200507c565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050620009b0565b6000915050620009b0565b60008181526001830160205260408120541515620009ad565b6000620009ad83836200403e565b600081815b8151811015620041d95760208282815181106200418f576200418f62004e31565b016020015160f81c1080620041c05750607f828281518110620041b657620041b662004e31565b016020015160f81c115b15620041d0575060009392505050565b6001016200416e565b5060019392505050565b60608160000180548060200260200160405190810160405280929190818152602001828054801562001d8657602002820191906000526020600020905b815481526020019060010190808311620042205750505050509050919050565b60008181526001830160205260408120546200428957508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155620009b0565b506000620009b0565b60006002620042a360018462005092565b60ff16620042b2919062004e63565b620009b090600a62004b27565b508054620042cd9062004c50565b6000825580601f10620042de575050565b601f0160209004906000526020600020908101906200099c91906200430c565b610a8b80620050af83390190565b5b808211156200432357600081556001016200430d565b5090565b6001600160a01b03811681146200099c57600080fd5b6000602082840312156200435057600080fd5b813562001ca78162004327565b600080604083850312156200437157600080fd5b82356200437e8162004327565b946020939093013593505050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715620043cd57620043cd6200438c565b604052919050565b600082601f830112620043e757600080fd5b81356001600160401b038111156200440357620044036200438c565b62004418601f8201601f1916602001620043a2565b8181528460208386010111156200442e57600080fd5b816020850160208301376000918101602001919091529392505050565b6000602082840312156200445e57600080fd5b81356001600160401b038111156200447557600080fd5b6200134784828501620043d5565b600080604083850312156200449757600080fd5b50508035926020909101359150565b600060208284031215620044b957600080fd5b5035919050565b60005b83811015620044dd578181015183820152602001620044c3565b50506000910152565b6000815180845262004500816020860160208601620044c0565b601f01601f19169290920160200192915050565b602081526000620009ad6020830184620044e6565b80356001600160401b03811681146200454157600080fd5b919050565b6000806000606084860312156200455c57600080fd5b833592506020840135620045708162004327565b915062001ed26040850162004529565b6001600160a01b0391909116815260200190565b60008060408385031215620045a857600080fd5b823591506020830135620045bc8162004327565b809150509250929050565b803560ff811681146200454157600080fd5b60008060408385031215620045ed57600080fd5b8235620045fa8162004327565b91506200460a60208401620045c7565b90509250929050565b60008151808452602080850194506020840160005b83811015620046465781518752958201959082019060010162004628565b509495945050505050565b602081526000620009ad602083018462004613565b600080604083850312156200467a57600080fd5b8235915060208301356001600160401b038111156200469857600080fd5b620046a685828601620043d5565b9150509250929050565b60006001600160401b03821115620046cc57620046cc6200438c565b5060051b60200190565b600082601f830112620046e857600080fd5b8135602062004701620046fb83620046b0565b620043a2565b8083825260208201915060208460051b8701019350868411156200472457600080fd5b602086015b848110156200474d5780356200473f8162004327565b835291830191830162004729565b509695505050505050565b600080604083850312156200476c57600080fd5b620047778362004529565b915060208301356001600160401b038111156200479357600080fd5b620046a685828601620046d6565b600080600060608486031215620047b757600080fd5b8335620047c48162004327565b92506020848101356001600160401b0380821115620047e257600080fd5b620047f088838901620046d6565b945060408701359150808211156200480757600080fd5b508501601f810187136200481a57600080fd5b80356200482b620046fb82620046b0565b81815260059190911b820183019083810190898311156200484b57600080fd5b928401925b828410156200486b5783358252928401929084019062004850565b80955050505050509250925092565b80151581146200099c57600080fd5b600080604083850312156200489d57600080fd5b823591506020830135620045bc816200487a565b600080600060608486031215620048c757600080fd5b505081359360208301359350604090920135919050565b600060208284031215620048f157600080fd5b620009ad82620045c7565b6000806000606084860312156200491257600080fd5b83356200491f8162004327565b92506020840135620049318162004327565b929592945050506040919091013590565b60008151808452602080850194506020840160005b83811015620046465781516001600160a01b03168752958201959082019060010162004957565b602081526000620009ad602083018462004942565b60ff8516815260018060a01b0384166020820152826040820152608060608201526000620049c56080830184620044e6565b9695505050505050565b600080600060608486031215620049e557600080fd5b83356001600160401b0380821115620049fd57600080fd5b62004a0b87838801620043d5565b9450602086013591508082111562004a2257600080fd5b5062004a3186828701620043d5565b92505062001ed260408501620045c7565b6000806040838503121562004a5657600080fd5b823591506200460a60208401620045c7565b60c08152600062004a7d60c0830189620044e6565b828103602084015262004a918189620044e6565b6001600160a01b03979097166040840152505060ff9390931660608401526001600160401b0391909116608083015260a09091015292915050565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b81810381811115620009b057620009b062004ae2565b6001600160a01b03929092168252602082015260400190565b80820180821115620009b057620009b062004ae2565b9182526001600160a01b0316602082015260400190565b6001600160a01b039390931683526020830191909152604082015260600190565b60006080820160018060a01b038088168452602081881660208601526001600160401b03871660408601526080606086015282865180855260a08701915060208801945060005b8181101562004bdc57855185168352948301949183019160010162004bbc565b50909a9950505050505050505050565b60006020828403121562004bff57600080fd5b815162001ca78162004327565b6001600160a01b039384168152919092166020820152604081019190915260600190565b60006020828403121562004c4357600080fd5b815162001ca7816200487a565b600181811c9082168062004c6557607f821691505b60208210810362004c8657634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115620010c1576000816000526020600020601f850160051c8101602086101562004cb75750805b601f850160051c820191505b8181101562000aeb5782815560010162004cc3565b81516001600160401b0381111562004cf45762004cf46200438c565b62004d0c8162004d05845462004c50565b8462004c8c565b602080601f83116001811462004d44576000841562004d2b5750858301515b600019600386901b1c1916600185901b17855562000aeb565b600085815260208120601f198616915b8281101562004d755788860151825594840194600190910190840162004d54565b508582101562004d945787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b828152604060208201526000620013476040830184620044e6565b60006020828403121562004dd257600080fd5b5051919050565b6000825162004ded818460208701620044c0565b9190910192915050565b60018060a01b038516815283602082015260806040820152600062004e206080830185620044e6565b905082606083015295945050505050565b634e487b7160e01b600052603260045260246000fd5b60ff8181168382160190811115620009b057620009b062004ae2565b8082028115828204841417620009b057620009b062004ae2565b6001600160a01b038416815260606020820181905260009062004ea39083018562004942565b8281036040840152620049c5818562004613565b600060018060a01b0380871683526080602084015262004edb608084018762004942565b838103604085015262004eef818762004613565b92505080841660608401525095945050505050565b60018060a01b038516815283602082015260806040820152600062004f2d6080830185620044e6565b828103606084015262004f418185620044e6565b979650505050505050565b600181815b8085111562004f8d57816000190482111562004f715762004f7162004ae2565b8085161562004f7f57918102915b93841c939080029062004f51565b509250929050565b60008262004fa657506001620009b0565b8162004fb557506000620009b0565b816001811462004fce576002811462004fd95762004ff9565b6001915050620009b0565b60ff84111562004fed5762004fed62004ae2565b50506001821b620009b0565b5060208310610133831016604e8410600b84101617156200501e575081810a620009b0565b6200502a838362004f4c565b806000190482111562005041576200504162004ae2565b029392505050565b6000620009ad838362004f95565b600080604083850312156200506b57600080fd5b505080516020909101519092909150565b634e487b7160e01b600052603160045260246000fd5b60ff8281168282160390811115620009b057620009b062004ae256fe60c060405234801561001057600080fd5b50604051610a8b380380610a8b83398101604081905261002f91610045565b6001600160a01b0390911660805260a05261007f565b6000806040838503121561005857600080fd5b82516001600160a01b038116811461006f57600080fd5b6020939093015192949293505050565b60805160a0516109e26100a9600039600060d1015260008181608d01526105f601526109e26000f3fe608060405234801561001057600080fd5b50600436106100835760003560e01c806325eb1c8714610088578063990db1fc146100cc578063beabacc814610101578063c3a9934214610116578063d11eccd614610129578063d742fa751461013c578063e1f21c671461014f578063e415c88d14610172578063ffa1ad7414610185575b600080fd5b6100af7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b6100f37f000000000000000000000000000000000000000000000000000000000000000081565b6040519081526020016100c3565b61011461010f366004610665565b6101b6565b005b61011461012436600461077b565b610271565b610114610137366004610665565b610389565b61011461014a36600461077b565b610427565b61016261015d366004610665565b610502565b60405190151581526020016100c3565b610114610180366004610665565b610586565b6101a960405180604001604052806005815260200164312e302e3160d81b81525081565b6040516100c3919061084d565b6101bf336105f4565b60405163a9059cbb60e01b81526001600160a01b0384169063a9059cbb906101ed908590859060040161089c565b6020604051808303816000875af115801561020c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061023091906108b5565b507f09c8833de2bf5450474df1a78131ef9aeb8daf0255c9a29223afe0b9bc918e33838383604051610264939291906108de565b60405180910390a1505050565b61027a336105f4565b81518151811461029d5760405163586cb9e160e01b815260040160405180910390fd5b60005b81811015610347578381815181106102ba576102ba610902565b60200260200101516001600160a01b03166323b872dd30878685815181106102e4576102e4610902565b60200260200101516040518463ffffffff1660e01b815260040161030a939291906108de565b600060405180830381600087803b15801561032457600080fd5b505af1158015610338573d6000803e3d6000fd5b505050508060010190506102a0565b507f67181210645375aeed5b843d2574626f3af2a5c7a3030560e73f9c83718f783884848460405161037b93929190610918565b60405180910390a150505050565b610392336105f4565b6040516323b872dd60e01b81526001600160a01b038316906323b872dd906103c2903090879086906004016108de565b600060405180830381600087803b1580156103dc57600080fd5b505af11580156103f0573d6000803e3d6000fd5b505050507f1a9851affc88c5574504b1c83ef8fdbbba8c218707b4f7f3ca30db850ec9d78b838383604051610264939291906108de565b610430336105f4565b8151815181146104535760405163586cb9e160e01b815260040160405180910390fd5b60005b818110156104fb5783818151811061047057610470610902565b60200260200101516001600160a01b031663095ea7b38685848151811061049957610499610902565b60200260200101516040518363ffffffff1660e01b81526004016104be92919061089c565b600060405180830381600087803b1580156104d857600080fd5b505af11580156104ec573d6000803e3d6000fd5b50505050806001019050610456565b5050505050565b600061050d336105f4565b60405163095ea7b360e01b81526001600160a01b0385169063095ea7b39061053b908690869060040161089c565b6020604051808303816000875af115801561055a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061057e91906108b5565b949350505050565b61058f336105f4565b60405163095ea7b360e01b81526001600160a01b0383169063095ea7b3906105bd908690859060040161089c565b600060405180830381600087803b1580156105d757600080fd5b505af11580156105eb573d6000803e3d6000fd5b50505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316816001600160a01b03161461064657604051639d6ebf3160e01b815260040160405180910390fd5b50565b80356001600160a01b038116811461066057600080fd5b919050565b60008060006060848603121561067a57600080fd5b61068384610649565b925061069160208501610649565b9150604084013590509250925092565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff811182821017156106e0576106e06106a1565b604052919050565b600067ffffffffffffffff821115610702576107026106a1565b5060051b60200190565b600082601f83011261071d57600080fd5b8135602061073261072d836106e8565b6106b7565b8083825260208201915060208460051b87010193508684111561075457600080fd5b602086015b848110156107705780358352918301918301610759565b509695505050505050565b60008060006060848603121561079057600080fd5b61079984610649565b925060208085013567ffffffffffffffff808211156107b757600080fd5b818701915087601f8301126107cb57600080fd5b81356107d961072d826106e8565b81815260059190911b8301840190848101908a8311156107f857600080fd5b938501935b8285101561081d5761080e85610649565b825293850193908501906107fd565b96505050604087013592508083111561083557600080fd5b50506108438682870161070c565b9150509250925092565b60006020808352835180602085015260005b8181101561087b5785810183015185820160400152820161085f565b506000604082860101526040601f19601f8301168501019250505092915050565b6001600160a01b03929092168252602082015260400190565b6000602082840312156108c757600080fd5b815180151581146108d757600080fd5b9392505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b038481168252606060208084018290528551918401829052600092868201929091906080860190855b81811015610966578551851683529483019491830191600101610948565b5050858103604087015286518082529082019350915080860160005b8381101561099e57815185529382019390820190600101610982565b50929897505050505050505056fea2646970667358221220b34e2248becc21f6403f2e0362c51adb202cadce8d670d124d50d9b868b3242664736f6c6343000817003322573091f17911fb166032a3d9e0554aa73d31b7b7ddea4a4dd2995650af84bda2646970667358221220eca4a3c365f2d5ad492f5574bb2e1bcb4ccc276f40ddfc1feff59b2b1559714e64736f6c63430008170033

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.