Overview
S Balance
S Value
$0.00More Info
Private Name Tags
ContractCreator
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Source Code Verified (Exact Match)
Contract Name:
DungeonFactory
Compiler Version
v0.8.23+commit.f704f362
Optimization Enabled:
Yes with 50 runs
Other Settings:
istanbul EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: BUSL-1.1 /** ▒▓▒ ▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓███▓▓▒ ▒▒▒▒▓▓▓▒▓▓▓▓▓▓▓██▓ ▒██▒▓▓▓▓█▓██████████████████▓ ▒▒▒▓███████████████▒ ▒██▒▓█████████████████████▒ ▒▓██████████▓███████ ▒███████████▓▒ ▒███▓▓██████▓ █████████▒ ▒▓▒▓███████▒ ███████▓ ▒▒▒▒▒▓▓█▓▒ ▓█▓████████ ▒▒▒▒▒ ▒▒▒▒▓▓▓█████▒ ▓█████████▓ ▒▓▓▓▒▓██████▓ ▒▓▓████████▒ ▒██▓▓▓███████▒ ▒▒▓███▓████ ▒███▓█████▒ ▒▒█████▓██▓ ██████▓ ▒▒▒▓██▓██▓█████▒ ▒▒▓▓▒ ▒██▓▒▓▓████████ ▓█████▓███████▓ ██▓▓██████████▒ ▒█████████████ ███████████▓ ▒▓▓▓▓▓▓▒▓ ▒█████████▒ ▒▓▓ ▒▓█▒ ▒▒█▒▒ ▓██████ ▒▒▓▓▒ ▒▒█▒ ▓▒ ▒████ ▒▓█▓█▓▒ ▓▒██▓▒ ██ ▒▓█▓▓▓██▒ ▓█▓▓▓▓▓█▓▓▓▒ ▒▒▒ ▒▒▒▓▓▓▓▒▓▒▒▓▒▓▓▓▓▓▓▓▓▒ ▒▓█▒ ▒▓▒▓█▓ ▒▓█▓▓▓▓▓▓▓▓▓▓▒ ▒▒▒▓▒ ▒▒▒▓▓ ▓▓ ▓▓█▓ ▒▒▓▓ ▒▒█▒ ▒▓▒▓█▓ ▒▒▓▓▓▒▓▒ ▒▓▓▓▒█▒ ▒▒▒█▒ ▒▒█▓▒▒▒▓▓▓▒ ▓██▓▓▓▓▓▓▓███▓ ▒ ▒▓▓█▓ ▒▓▓▓▓█▓█▓ ▒█▓▓▒ ▓▓█▓▒▓█▓▒▒ ▓█▓ ▓███▓ ▓▓▒ ▒▒▓▓█▓▒▒▓█▒ ▒▓██▓ ▓██▓▒ ▒█▓ ▓▓██ ▒▓▓▓▒▒▓█▓ ▒▓████▒ ██▓▓▒▒▒▒▓▓███▓▒ ▒▓▓▓▓▒▒ ▒▓▓▓▓▓▓▓▒▒▒▓█▓▓▓▓█▓▓▒▒▓▓▓▓▓▒ ▒▓████▓▒ ▓▓███████▓▓▒ */ pragma solidity 0.8.23; import "../proxy/Controllable.sol"; import "../lib/DungeonFactoryLib.sol"; import "../relay/ERC2771Context.sol"; import "../openzeppelin/ERC721Holder.sol"; contract DungeonFactory is Controllable, IDungeonFactory, ERC2771Context, ERC721Holder { //region ------------------------ CONSTANTS /// @notice Version of the contract string public constant override VERSION = "2.1.1"; //endregion ------------------------ CONSTANTS //region ------------------------ INITIALIZER function init(address controller_) external initializer { __Controllable_init(controller_); } //endregion ------------------------ INITIALIZER //region ------------------------ VIEWS function dungeonAttributes(uint16 dungLogicNum) external view returns (DungeonAttributes memory) { return DungeonFactoryLib.dungeonAttributes(dungLogicNum); } function dungeonStatus(uint64 dungeonId) external view returns ( uint16 dungNum, bool isCompleted, address heroToken, uint heroTokenId, uint32 currentObject, uint8 currentObjIndex, address[] memory treasuryTokens, uint[] memory treasuryTokensAmounts, bytes32[] memory treasuryItems, uint8 stages, uint32[] memory uniqObjects ) { return DungeonFactoryLib.dungeonStatus(dungeonId); } function dungeonCounter() external view returns (uint64) { return DungeonFactoryLib.dungeonCounter(); } function maxBiomeCompleted(address heroToken, uint heroTokenId) external view override returns (uint8) { return DungeonFactoryLib.maxBiomeCompleted(heroToken, heroTokenId); } function currentDungeon(address heroToken, uint heroTokenId) external view override returns (uint64) { return DungeonFactoryLib.currentDungeon(heroToken, heroTokenId); } function minLevelForTreasury(address token) external view returns (uint) { return DungeonFactoryLib.minLevelForTreasury(token); } function skillSlotsForDurabilityReduction(address heroToken, uint heroTokenId) external override view returns (uint8[] memory result) { return DungeonFactoryLib.skillSlotsForDurabilityReduction(heroToken, heroTokenId); } function freeDungeonsByLevelLength(uint biome) external view returns (uint) { return DungeonFactoryLib.freeDungeonsByLevelLength(biome); } function freeDungeonsByLevel(uint id, uint biome) external view returns (uint64) { return DungeonFactoryLib.freeDungeonsByLevel(id, biome); } function dungeonTreasuryReward( address token, uint maxAvailableBiome_, uint treasuryBalance, uint8 heroLevel, uint8 dungeonBiome, uint8 maxOpenedNgLevel, uint8 heroNgLevel ) external view returns (uint) { return DungeonLib.dungeonTreasuryReward(token, maxAvailableBiome_, treasuryBalance, heroLevel, dungeonBiome, maxOpenedNgLevel, heroNgLevel); } function getDungeonTreasuryAmount(address token, uint heroLevel, uint biome, uint heroNgLevel) external view returns ( uint totalAmount, uint amountForDungeon, uint mintAmount ) { return DungeonFactoryLib.getDungeonTreasuryAmount(IController(controller()), token, heroLevel, biome, heroNgLevel); } function getDungeonLogic(IController controller_, uint8 heroLevel, address heroToken, uint heroTokenId, uint random) external view returns (uint16) { ControllerContextLib.ControllerContext memory cc = ControllerContextLib.init(controller_); return DungeonLib.getDungeonLogic(DungeonLib._S(), cc, heroLevel, heroToken, heroTokenId, random); } function isDungeonEligibleForHero(uint16 dungeonLogic, uint8 heroLevel, address heroToken, uint heroTokenId) external view returns (bool) { return DungeonLib.isDungeonEligibleForHero( DungeonLib._S(), IStatController(IController(controller()).statController()), dungeonLogic, heroLevel, heroToken, heroTokenId ); } /// @dev Easily get info should given hero fight with boss in the current biome or not. function isBiomeBoss(address heroToken, uint heroTokenId) external view returns (bool) { return DungeonFactoryLib.isBiomeBoss(IController(controller()), heroToken, heroTokenId); } function maxAvailableBiome() external view returns (uint8) { return DungeonFactoryLib.maxAvailableBiome(); } function dungeonNgLevel(uint64 dungeonId) external view returns (uint) { return DungeonFactoryLib.dungeonNgLevel(dungeonId); } //endregion ------------------------ VIEWS //region ------------------------ ACTIONS function launch(address heroToken, uint heroTokenId, address treasuryToken) external returns (uint64 dungeonId) { return DungeonFactoryLib.launch(_isNotSmartContract(), IController(controller()), _msgSender(), heroToken, heroTokenId, treasuryToken); } function launchForNewHero(address heroToken, uint heroTokenId, address owner) external override returns (uint64 dungeonId) { return DungeonFactoryLib.launchForNewHero(IController(controller()), owner, heroToken, heroTokenId); } function setBossCompleted(uint32 objectId, address heroToken, uint heroTokenId, uint8 heroBiome) external override { DungeonFactoryLib.setBossCompleted(IController(controller()), objectId, heroToken, heroTokenId, heroBiome); } //endregion ------------------------ ACTIONS //region ------------------------ GOV ACTIONS /// @notice Register ordinal or specific dungeon /// @param biome Assume biome > 0 /// @param isSpecific The dungeon is specific, so it shouldn't be registered in dungeonsLogicByBiome /// @param specReqBiome required biome /// @param specReqHeroClass required hero class function registerDungeonLogic( uint16 dungLogicId, uint8 biome, DungeonGenerateInfo memory genInfo, uint8 specReqBiome, uint8 specReqHeroClass, bool isSpecific ) external { DungeonFactoryLib.registerDungeonLogic( IController(controller()), dungLogicId, biome, genInfo, specReqBiome, specReqHeroClass, isSpecific ); } function removeDungeonLogic(uint16 dungLogicId, uint8 specReqBiome, uint8 specReqHeroClass) external { DungeonFactoryLib.removeDungeonLogic(IController(controller()), dungLogicId, specReqBiome, specReqHeroClass); } /// @dev Set eligible hero level for treasury tokens function setMinLevelForTreasury(address token, uint heroLevel) external { DungeonFactoryLib.setMinLevelForTreasury(IController(controller()), token, heroLevel); } /// @dev Governance can drop hero from dungeon in emergency case function emergencyExit(uint64 dungId) external { DungeonFactoryLib.emergencyExit(IController(controller()), dungId); } //endregion ------------------------ GOV ACTIONS //region ------------------------ USER ACTIONS function enter(uint64 dungId, address heroToken_, uint heroTokenId_) external { DungeonFactoryLib.enter(_isNotSmartContract(), IController(controller()), _msgSender(), dungId, heroToken_, heroTokenId_); } function openObject(uint64 dungId) external { DungeonFactoryLib.openObject(_isNotSmartContract(), IController(controller()), _msgSender(), dungId); } function objectAction(uint64 dungId, bytes memory data) external { DungeonFactoryLib.objectAction(_isNotSmartContract(), IController(controller()), _msgSender(), dungId, data); } function exit(uint64 dungId, bool claim) external { DungeonFactoryLib.exit(_isNotSmartContract(), IController(controller()), _msgSender(), dungId, claim); } /// @notice Hero exists current dungeon forcibly. Life chance is reduced, life and mana are restored to default, /// items are kept untouched. It's not allowed to call this function for heroes with last life chance function exitSuicide(address hero, uint heroId) external { DungeonFactoryLib.exitSpecial(IController(controller()), hero, heroId, _msgSender(), DungeonLib.DungeonExitMode.HERO_SUICIDE_2); } //endregion ------------------------ USER ACTIONS //region ------------------------ Contracts actions /// @notice Hero exists current dungeon forcibly same as when dying but without loosing life chance /// @dev Implement logic of special consumable that allows a hero to exit current dungeon using the shelter function exitForcibly(address hero, uint heroId, address msgSender) override external { DungeonFactoryLib.exitSpecial(IController(controller()), hero, heroId, msgSender, DungeonLib.DungeonExitMode.FORCED_EXIT_1); } function reborn(address heroToken, uint heroTokenId) external override { DungeonFactoryLib.reborn(IController(controller()), heroToken, heroTokenId); } //endregion ------------------------ Contracts actions }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; /// @notice All errors of the app interface IAppErrors { //region ERC20Errors /** * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. * @param sender Address whose tokens are being transferred. * @param balance Current balance for the interacting account. * @param needed Minimum amount required to perform a transfer. */ error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); /** * @dev Indicates a failure with the token `sender`. Used in transfers. * @param sender Address whose tokens are being transferred. */ error ERC20InvalidSender(address sender); /** * @dev Indicates a failure with the token `receiver`. Used in transfers. * @param receiver Address to which tokens are being transferred. */ error ERC20InvalidReceiver(address receiver); /** * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. * @param spender Address that may be allowed to operate on tokens without being their owner. * @param allowance Amount of tokens a `spender` is allowed to operate with. * @param needed Minimum amount required to perform a transfer. */ error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); /** * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. * @param approver Address initiating an approval operation. */ error ERC20InvalidApprover(address approver); /** * @dev Indicates a failure with the `spender` to be approved. Used in approvals. * @param spender Address that may be allowed to operate on tokens without being their owner. */ error ERC20InvalidSpender(address spender); //endregion ERC20Errors //region ERC721Errors /** * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20. * Used in balance queries. * @param owner Address of the current owner of a token. */ error ERC721InvalidOwner(address owner); /** * @dev Indicates a `tokenId` whose `owner` is the zero address. * @param tokenId Identifier number of a token. */ error ERC721NonexistentToken(uint256 tokenId); /** * @dev Indicates an error related to the ownership over a particular token. Used in transfers. * @param sender Address whose tokens are being transferred. * @param tokenId Identifier number of a token. * @param owner Address of the current owner of a token. */ error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); /** * @dev Indicates a failure with the token `sender`. Used in transfers. * @param sender Address whose tokens are being transferred. */ error ERC721InvalidSender(address sender); /** * @dev Indicates a failure with the token `receiver`. Used in transfers. * @param receiver Address to which tokens are being transferred. */ error ERC721InvalidReceiver(address receiver); /** * @dev Indicates a failure with the `operator`’s approval. Used in transfers. * @param operator Address that may be allowed to operate on tokens without being their owner. * @param tokenId Identifier number of a token. */ error ERC721InsufficientApproval(address operator, uint256 tokenId); /** * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. * @param approver Address initiating an approval operation. */ error ERC721InvalidApprover(address approver); /** * @dev Indicates a failure with the `operator` to be approved. Used in approvals. * @param operator Address that may be allowed to operate on tokens without being their owner. */ error ERC721InvalidOperator(address operator); //endregion ERC721Errors error ZeroAddress(); error ZeroValueNotAllowed(); error ZeroToken(); error LengthsMismatch(); error NotEnoughBalance(); error NotEnoughAllowance(); error EmptyNameNotAllowed(); error NotInitialized(); error AlreadyInitialized(); error ReentrancyGuardReentrantCall(); error TooLongString(); error AlreadyDeployed(address deployed); error AlreadyClaimed(); //region Restrictions error ErrorNotDeployer(address sender); error ErrorNotGoc(); error NotGovernance(address sender); error ErrorOnlyEoa(); error NotEOA(address sender); error ErrorForbidden(address sender); error AdminOnly(); error ErrorNotItemController(address sender); error ErrorNotHeroController(address sender); error ErrorNotDungeonFactory(address sender); error ErrorNotObjectController(address sender); error ErrorNotStoryController(); error ErrorNotAllowedSender(); error MintNotAllowed(); error NotPvpController(); //endregion Restrictions //region PackingLib error TooHighValue(uint value); error IntValueOutOfRange(int value); error OutOfBounds(uint index, uint length); error UnexpectedValue(uint expected, uint actual); error WrongValue(uint newValue, uint actual); error IntOutOfRange(int value); error ZeroValue(); /// @notice packCustomDataChange requires an input string with two zero bytes at the beginning /// 0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0000 /// This error happens if these bytes are not zero error IncompatibleInputString(); error IncorrectOtherItemTypeKind(uint8 kind); //endregion PackingLib //region Hero error ErrorHeroIsNotRegistered(address heroToken); error ErrorHeroIsDead(address heroToken, uint heroTokenId); error ErrorHeroNotInDungeon(); error HeroInDungeon(); error ErrorNotOwner(address token, uint tokenId); error ErrorNotOwnerOrHero(address token, uint tokenId); error Staked(address heroToken, uint heroId); error NameTaken(); error TooBigName(); error WrongSymbolsInTheName(); error NoPayToken(address token, uint payTokenAmount); error AlreadyHaveReinforcement(); /// @notice SIP-001 - Reinforcement requires 3 skills error ErrorReinforcementRequiresThreeSkills(); error WrongTier(uint tier); error NotEnoughNgLevel(uint8 ngLevel); error NgpNotActive(address hero); error RebornNotAllowed(); error AlreadyPrePaidHero(); error SandboxTierForbidden(); error SandboxPrepaidOnly(); error SandboxNgZeroOnly(); error SandboxModeNotAllowed(); error SandboxUpgradeModeRequired(); error SandboxModeRequired(); error SandboxItemOutside(); error SandboxItemNotActive(); error SandboxItemNotRegistered(); error SandboxItemAlreadyEquipped(); error SandboxDifferentHeroesNotAllowed(); error HeroWasTransferredBetweenAccounts(); //endregion Hero //region Dungeon error ErrorDungeonIsFreeAlready(); error ErrorNoEligibleDungeons(); error ErrorDungeonBusy(); error ErrorNoDungeonsForBiome(uint8 heroBiome); error ErrorDungeonCompleted(); error ErrorAlreadyInDungeon(); error NotEnoughTokens(uint balance, uint expectedBalance); error DungeonAlreadySpecific(uint16 dungNum); error DungeonAlreadySpecific2(uint16 dungNum); error WrongSpecificDungeon(); error LastLifeChance(); //endregion Dungeon //region Items error ErrorItemNotEligibleForTheSlot(uint itemType, uint8 itemSlot); error ErrorItemSlotBusyHand(uint8 slot); error ErrorItemSlotBusy(); error ErrorItemNotInSlot(); error ErrorConsumableItemIsUsed(address item); error ErrorCannotRemoveItemFromMap(); error ErrorCannotRemoveDataFromMap(); error EquippedItemsExist(); error ItemEquipped(address item, uint itemId); error ZeroItemMetaType(); error NotZeroOtherItemMetaType(); error ZeroLevel(); error ItemTypeChanged(); error ItemMetaTypeChanged(); error UnknownItem(address item); error ErrorEquipForbidden(); error EquipForbiddenInDungeon(); error TakeOffForbiddenInDungeon(); error Consumable(address item); error NotConsumable(address item); error Broken(address item); error ZeroLife(); error RequirementsToItemAttributes(); error NotEquipped(address item); error ZeroDurability(); error ZeroAugmentation(); error TooHighAgLevel(uint8 augmentationLevel); error UseForbiddenZeroPayToken(); error IncorrectMinMaxAttributeRange(int32 min, int32 max); error SameIdsNotAllowed(); error ZeroFragility(); error OtherTypeItemNotRepairable(); error NotOther(); error DoubleItemUsageForbidden(uint itemIndex, address[] items); error ItemAlreadyUsedInSlot(address item, uint8 equippedSlot); error WrongWayToRegisterItem(); error UnionItemNotFound(address item); error WrongListUnionItemTokens(address item, uint countTokens, uint requiredCountTokens); error UnknownUnionConfig(uint unionConfigId); error UserHasNoKeyPass(address user, address keyPassItem); error MaxValue(uint value); error UnexpectedOtherItem(address item); error NotExist(); error ItemNotFound(address item, uint itemId); error NoFirstAugmentationInfo(); error NotAugmentationProtectiveItem(address item); //endregion Items //region Stages error ErrorWrongStage(uint stage); error ErrorNotStages(); //endregion Stages //region Level error ErrorWrongLevel(uint heroLevel); error ErrorLevelTooLow(uint heroLevel); error ErrorHeroLevelStartFrom1(); error ErrorWrongLevelUpSum(); error ErrorMaxLevel(); //endregion Level //region Treasure error ErrorNotValidTreasureToken(address treasureToken); //endregion Treasure //region State error ErrorPaused(); error ErrorNotReady(); error ErrorNotObject1(); error ErrorNotObject2(); error ErrorNotCompleted(); //endregion State //region Biome error ErrorNotBiome(); error ErrorIncorrectBiome(uint biome); error TooHighBiome(uint biome); //endregion Biome //region Misc error ErrorWrongMultiplier(uint multiplier); error ErrorNotEnoughMana(uint32 mana, uint requiredMana); error ErrorExperienceMustNotDecrease(); error ErrorNotEnoughExperience(); error ErrorNotChances(); error ErrorNotEligible(address heroToken, uint16 dungNum); error ErrorZeroKarmaNotAllowed(); //endregion Misc //region GOC error GenObjectIdBiomeOverflow(uint8 biome); error GenObjectIdSubTypeOverflow(uint subType); error GenObjectIdIdOverflow(uint id); error UnknownObjectTypeGoc1(uint8 objectType); error UnknownObjectTypeGoc2(uint8 objectType); error UnknownObjectTypeGocLib1(uint8 objectType); error UnknownObjectTypeGocLib2(uint8 objectType); error UnknownObjectTypeForSubtype(uint8 objectSubType); error FightDelay(); error ZeroChance(); error TooHighChance(uint32 chance); error TooHighRandom(uint random); error EmptyObjects(); error ObjectNotFound(); error WrongGetObjectTypeInput(); error WrongChances(uint32 chances, uint32 maxChances); //endregion GOC //region Story error PageNotRemovedError(uint pageId); error NotItem1(); error NotItem2(); error NotRandom(uint32 random); error NotHeroData(); error NotGlobalData(); error ZeroStoryIdRemoveStory(); error ZeroStoryIdStoryAction(); error ZeroStoryIdAction(); error NotEnoughAmount(uint balance, uint requiredAmount); error NotAnswer(); error AnswerStoryIdMismatch(uint16 storyId, uint16 storyIdFromAnswerHash); error AnswerPageIdMismatch(uint16 pageId, uint16 pageIdFromAnswerHash); error NotSkippableStory(); error StoryNotPassed(); error SkippingNotAllowed(); //endregion Story //region FightLib error NotMagic(); error NotAType(uint atype); //endregion FightLib //region MonsterLib error NotYourDebuffItem(); error UnknownAttackType(uint attackType); error NotYourAttackItem(); /// @notice The skill item cannot be used because it doesn't belong either to the hero or to the hero's helper error NotYourBuffItem(); //endregion MonsterLib //region GameToken error ApproveToZeroAddress(); error MintToZeroAddress(); error TransferToZeroAddress(); error TransferAmountExceedsBalance(uint balance, uint value); error InsufficientAllowance(); error BurnAmountExceedsBalance(); error NotMinter(address sender); //endregion GameToken //region NFT error TokenTransferNotAllowed(); error IdOverflow(uint id); error NotExistToken(uint tokenId); error EquippedItemIsNotAllowedToTransfer(uint tokenId); //endregion NFT //region CalcLib error TooLowX(uint x); //endregion CalcLib //region Controller error NotFutureGovernance(address sender); //endregion Controller //region Oracle error OracleWrongInput(); //endregion Oracle //region ReinforcementController error AlreadyStaked(); error MaxFee(uint8 fee); error MinFee(uint8 fee); error StakeHeroNotStats(); error NotStaked(); error NoStakedHeroes(); error GuildHelperNotAvailable(uint guildId, address helper, uint helperId); error PvpStaked(); error HelperNotAvailableInGivenBiome(); //endregion ReinforcementController //region SponsoredHero error InvalidHeroClass(); error ZeroAmount(); error InvalidProof(); error NoHeroesAvailable(); error AlreadyRegistered(); //endregion SponsoredHero //region SacraRelay error SacraRelayNotOwner(); error SacraRelayNotDelegator(); error SacraRelayNotOperator(); error SacraRelayInvalidChainId(uint callChainId, uint blockChainId); error SacraRelayInvalidNonce(uint callNonce, uint txNonce); error SacraRelayDeadline(); error SacraRelayDelegationExpired(); error SacraRelayNotAllowed(); error SacraRelayInvalidSignature(); /// @notice This error is generated when custom error is caught /// There is no info about custom error in SacraRelay /// but you can decode custom error by selector, see tests error SacraRelayNoErrorSelector(bytes4 selector, string tracingInfo); /// @notice This error is generated when custom error is caught /// There is no info about custom error in SacraRelay /// but you can decode custom error manually from {errorBytes} as following: /// if (keccak256(abi.encodeWithSignature("MyError()")) == keccak256(errorBytes)) { ... } error SacraRelayUnexpectedReturnData(bytes errorBytes, string tracingInfo); error SacraRelayCallToNotContract(address notContract, string tracingInfo); //endregion SacraRelay //region Misc error UnknownHeroClass(uint heroClass); error AbsDiff(int32 a, int32 b); //endregion Misc //region ------------------------ UserController error NoAvailableLootBox(address msgSender, uint lootBoxKind); error FameHallHeroAlreadyRegistered(uint8 openedNgLevel); //endregion ------------------------ UserController //region ------------------------ Guilds error AlreadyGuildMember(); error NotGuildMember(); error WrongGuild(); error GuildActionForbidden(uint right); error GuildHasMaxSize(uint guildSize); error GuildHasMaxLevel(uint level); error TooLongUrl(); error TooLongDescription(); error CannotRemoveGuildOwnerFromNotEmptyGuild(); error GuildControllerOnly(); error GuildAlreadyHasShelter(); error ShelterIsBusy(); error ShelterIsNotRegistered(); error ShelterIsNotOwnedByTheGuild(); error ShelterIsInUse(); error GuildHasNoShelter(); error ShelterBidIsNotAllowedToBeUsed(); error ShelterHasHeroesInside(); error SecondGuildAdminIsNotAllowed(); error NotEnoughGuildBankBalance(uint guildId); error GuildReinforcementCooldownPeriod(); error NoStakedGuildHeroes(); error NotStakedInGuild(); error ShelterHasNotEnoughLevelForReinforcement(); error NotBusyGuildHelper(); error TooLowGuildLevel(); /// @notice Target biome can be selected only once per epoch error BiomeAlreadySelected(); error NoDominationRequest(); error PvpFightIsNotPrepared(uint8 biome, uint32 week, address user); error PvpFightIsCompleted(uint8 biome, uint32 week, address user); error TooLowMaxCountTurns(); error UserTokensVaultAlreadySet(); error DifferentBiomeInPvpFight(); error PvpFightOpponentNotFound(); error PvpHeroHasInitializedFight(); error PvpHeroNotRegistered(); /// @notice User should unregister pvp-hero from prev biome and only then register it in the new biome error UserHasRegisteredPvpHeroInBiome(uint8 biome); error UserHasRegisteredPvpHero(); error UserNotAllowedForPvpInCurrentEpoch(uint week); error UnknownPvpStrategy(); error GuildRequestNotActive(); error GuildRequestNotAvailable(); error NotAdminCannotAddMemberWithNotZeroRights(); //endregion ------------------------ Guilds //region ------------------------ Shelters error ErrorNotShelterController(); error ErrorNotGuildController(); error ShelterHasNotItem(uint shelterId, address item); error MaxNumberItemsSoldToday(uint numSoldItems, uint limit); error GuildHasNotEnoughPvpPoints(uint64 pointsAvailable, uint pointRequired); error FreeShelterItemsAreNotAllowed(uint shelterId, address item); error TooLowShelterLevel(uint8 shelterLevel, uint8 allowedShelterLevel); error NotEnoughPvpPointsCapacity(address user, uint usedPoints, uint pricePvpPoints, uint64 capactiy); error IncorrectShelterLevel(uint8 shelterLevel); //endregion ------------------------ Shelters //region ------------------------ Auction error WrongAuctionPosition(); error AuctionPositionClosed(); error AuctionBidOpened(uint positionId); error TooLowAmountToBid(); error AuctionEnded(); error TooLowAmountForNewBid(); error AuctionSellerOnly(); error AuctionBuyerOnly(); error AuctionBidNotFound(); error AuctionBidClosed(); error OnlyShelterAuction(); error CannotCloseLastBid(); error AuctionNotEnded(); error NotShelterAuction(); error AuctionPositionOpened(uint positionId); error AuctionSellerCannotBid(); error AuctionGuildWithShelterCannotBid(); error AuctionBidExists(); //endregion ------------------------ Auction //region ------------------------ Pawnshop error AuctionPositionNotSupported(uint positionId); error PositionNotSupported(uint positionId); error NotNftPositionNotSupported(uint positionId); error CallFailed(bytes callResultData); error PawnShopZeroOwner(); error PawnShopZeroFeeRecipient(); error PawnShopNotOwner(); error PawnShopAlreadyAnnounced(); error PawnShopTimeLock(); error PawnShopWrongAddressValue(); error PawnShopWrongUintValue(); error PawnShopZeroAddress(); error PawnShopTooHighValue(); error PawnShopZeroAToken(); error PawnShopZeroCToken(); error PawnShopWrongAmounts(); error PawnShopPosFeeForInstantDealForbidden(); error PawnShopPosFeeAbsurdlyHigh(); error PawnShopIncorrect(); error PawnShopWrongId(); error PawnShopNotBorrower(); error PawnShopPositionClosed(); error PawnShopPositionExecuted(); error PawnShopWrongBidAmount(); error PawnShopTooLowBid(); error PawnShopNewBidTooLow(); error PawnShopBidAlreadyExists(); error PawnShopAuctionEnded(); error PawnShopNotLender(); error PawnShopTooEarlyToClaim(); error PawnShopPositionNotExecuted(); error PawnShopAlreadyClaimed(); error PawnShopAuctionNotEnded(); error PawnShopBidClosed(); error PawnShopNoBids(); error PawnShopAuctionBidNotFound(); error PawnShopWrongBid(); error PawnShopBidNotFound(); //endregion ------------------------ Pawnshop }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "./IGOC.sol"; import "./IStatController.sol"; import "./IDungeonFactory.sol"; import "./IStoryController.sol"; import "./IFightCalculator.sol"; import "./IPvpController.sol"; /// @notice All events of the app interface IApplicationEvents { //region ------------------------ Common event SetOperator(address operator, bool remove); event Salvage(address receiver, address token, uint amount); //endregion ------------------------ Common //region ------------------ StatController event HeroItemSlotChanged( address heroToken, uint heroTokenId, uint itemType, uint itemSlot, address itemToken, uint itemTokenId, bool equip, address caller ); event CurrentStatsChanged( address heroToken, uint heroTokenId, IStatController.ChangeableStats change, bool increase, address caller ); event BonusAttributesChanged( address heroToken, uint heroTokenId, bool add, bool temporally, address caller ); event TemporallyAttributesCleared(address heroToken, uint heroTokenId, address caller); event NewHeroInited(address heroToken, uint heroTokenId, IStatController.ChangeableStats stats); event LevelUp( address heroToken, uint heroTokenId, uint heroClass, IStatController.CoreAttributes change ); event ConsumableUsed(address heroToken, uint heroTokenId, address item); event RemoveConsumableUsage(address heroToken, uint heroTokenId, address item); event HeroCustomDataChanged(address token, uint tokenId, bytes32 index, uint value); event HeroCustomDataChangedNg(address token, uint tokenId, bytes32 index, uint value, uint8 ngLevel); event HeroCustomDataCleared(address token, uint tokenId); event GlobalCustomDataChanged(bytes32 index, uint value); //endregion ------------------ StatController //region ------------------ DungeonFactoryController event DungeonLaunched( uint16 dungeonLogicNum, uint64 dungeonId, address heroToken, uint heroTokenId, address treasuryToken, uint treasuryAmount ); event BossCompleted(uint32 objectId, uint biome, address hero, uint heroId); event FreeDungeonAdded(uint8 biome, uint64 dungeonId); event ObjectOpened(uint64 dungId, address hero, uint id, uint32 objId, uint iteration, uint currentStage); event Clear(uint64 dungId); event DungeonLogicRegistered(uint16 dungLogicId, IDungeonFactory.DungeonGenerateInfo info); event DungeonLogicRemoved(uint16 dungLogicId); event DungeonSpecificLogicRegistered(uint16 dungLogicId, uint biome, uint heroCls); event DungeonSpecificLogicRemoved(uint16 dungLogicId, uint heroLvl, uint heroCls); event DungeonRegistered(uint16 dungLogicId, uint64 dungeonId); event DungeonRemoved(uint16 dungLogicId, uint64 dungeonId); event MinLevelForTreasuryChanged(address token, uint level); event ObjectAction( uint64 dungId, IGOC.ActionResult result, uint currentStage, address heroToken, uint heroTokenId, uint newStage ); /// @notice On add the item to the dungeon event AddTreasuryItem(uint64 dungId, address itemAdr, uint itemId); event AddTreasuryToken(uint64 dungId, address token, uint amount); event ClaimToken(uint64 dungId, address token, uint amount); event ClaimItem(uint64 dungId, address token, uint id); event Entered(uint64 dungId, address hero, uint id); event DungeonCompleted(uint16 dungLogicNum, uint64 dungId, address hero, uint heroId); event Exit(uint64 dungId, bool claim); event ExitForcibly(uint64 dungId, address hero, uint heroId); event FreeDungeonRemoved(uint8 biome, uint64 dungeonId); event HeroCurrentDungeonChanged(address hero, uint heroId, uint64 dungeonId); //endregion ------------------ DungeonFactoryController //region ------------------ GameObjectController event EventRegistered(uint32 objectId, IGOC.EventRegInfo eventRegInfo); event StoryRegistered(uint32 objectId, uint16 storyId); event MonsterRegistered(uint32 objectId, IGOC.MonsterGenInfo monsterGenInfo); event ObjectRemoved(uint32 objectId); event ObjectResultEvent( uint64 dungeonId, uint32 objectId, IGOC.ObjectType objectType, address hero, uint heroId, uint8 stageId, uint iteration, bytes data, IGOC.ActionResult result, uint salt ); //endregion ------------------ GameObjectController //region ------------------ StoryController event SetBurnItemsMeta(uint storyId, IStoryController.AnswerBurnRandomItemMeta meta); event SetNextObjRewriteMeta(uint storyId, IStoryController.NextObjRewriteMeta meta); event SetAnswersMeta(uint storyId, uint16[] answerPageIds, uint8[] answerHeroClasses, uint16[] answerIds); event SetAnswerNextPageMeta(uint storyId, IStoryController.AnswerNextPageMeta meta); event SetAnswerAttributeRequirements(uint storyId, IStoryController.AnswerAttributeRequirementsMeta meta); event SetAnswerItemRequirements(uint storyId, IStoryController.AnswerItemRequirementsMeta meta); event SetAnswerTokenRequirementsMeta(uint storyId, IStoryController.AnswerTokenRequirementsMeta meta); event SetAnswerAttributes(uint storyId, IStoryController.AnswerAttributesMeta meta); event SetAnswerHeroCustomDataRequirementMeta(uint storyId, IStoryController.AnswerCustomDataMeta meta); event SetAnswerGlobalCustomDataRequirementMeta(uint storyId, IStoryController.AnswerCustomDataMeta meta); event SetSuccessInfo(uint storyId, IStoryController.AnswerResultMeta meta); event SetFailInfo(uint storyId, IStoryController.AnswerResultMeta meta); event SetCustomDataResult(uint storyId, IStoryController.AnswerCustomDataResultMeta meta, IStoryController.CustomDataResult _type); event StoryCustomDataRequirements(uint storyId, bytes32 requiredCustomDataIndex, uint requiredCustomDataMinValue, uint requiredCustomDataMaxValue, bool requiredCustomDataIsHero); event StoryRequiredLevel(uint storyId, uint requiredLevel); event StoryFinalized(uint32 objectId, uint storyId); event StoryRemoved(uint32 objectId, uint storyId); event ItemBurned( address heroToken, uint heroTokenId, uint64 dungeonId, uint objectId, address nftToken, uint nftId, uint stageId, uint iteration ); /// @notice Durability of the item was reduced to 0 event ItemBroken( address heroToken, uint heroTokenId, uint64 dungeonId, uint objectId, address nftToken, uint nftId, uint stageId, uint iteration ); event NotEquippedItemBurned( address heroToken, uint heroTokenId, uint64 dungeonId, uint storyId, address nftToken, uint nftId, uint stageId, uint iteration ); event StoryChangeAttributes( uint32 objectId, address heroToken, uint heroTokenId, uint64 dungeonId, uint storyId, uint stageId, uint iteration, int32[] attributes ); //endregion ------------------ StoryController //region ------------------------ HeroController event HeroRegistered(address hero, uint8 heroClass, address payToken, uint payAmount); /// @notice Deprecated, replaced by {HeroCreatedNgpSandbox}. Don't remove - it's required by subgraph event HeroCreatedNgp(address hero, uint heroId, string name, address owner, string refCode, uint8 tier, uint8 ngLevel); event HeroCreatedNgpSandbox(address hero, uint heroId, string name, address owner, string refCode, uint8 tier, uint8 ngLevel, bool sandbox); event BiomeChanged(address hero, uint heroId, uint8 biome); event LevelUp(address hero, uint heroId, address owner, IStatController.CoreAttributes change); event ReinforcementAsked(address hero, uint heroId, address helpHeroToken, uint helpHeroId); event GuildReinforcementAsked(address hero, uint heroId, address helpHeroToken, uint helpHeroId); event OtherItemGuildReinforcement(address item, uint itemId, address hero, uint heroId, address helpHeroToken, uint helpHeroId); event ReinforcementReleased(address hero, uint heroId, address helperToken, uint helperId); event GuildReinforcementReleased(address hero, uint heroId, address helperToken, uint helperId); event Killed(address hero, uint heroId, address killer, bytes32[] dropItems, uint dropTokenAmount); event Reborn(address hero, uint heroId, uint8 newNgLevel); event BossKilled(address account, address hero, uint heroId, uint8 biome, uint8 newNgLevel, bool reborn, uint rewardAmount); event TierSetup(uint8 tier, address hero, uint72 payAmount, uint8[] slots, address[][] items); event SandboxUpgraded(address hero, uint heroId); event SandboxReturnAmountToTreasury(uint64 dungId, address token, uint amount); //endregion ------------------------ HeroController //region ------------------------ FightLib event FightResultProcessed( address sender, IFightCalculator.FightInfoInternal result, IFightCalculator.FightCall callData, uint iteration ); /// @param heroA Address of the fighter A. Address of the fighter B can be detected by fightId /// @param heroIdA ID of the figher A. ID of the fighter B can be detected by fightId event PvpFightResultProcessed( uint48 fightId, address sender, IFightCalculator.FightInfoInternal result, uint turn, address heroA, uint heroIdA ); //endregion ------------------------ FightLib //region ------------------------ Oracle event Random(uint number, uint max); //endregion ------------------------ Oracle //region ------------------------ Controller event OfferGovernance(address newGov); event GovernanceAccepted(address gov); event StatControllerChanged(address value); event StoryControllerChanged(address value); event GameObjectControllerChanged(address value); event ReinforcementControllerChanged(address value); event OracleChanged(address value); event TreasuryChanged(address value); event ItemControllerChanged(address value); event HeroControllerChanged(address value); event GameTokenChanged(address value); event DungeonFactoryChanged(address value); event ProxyUpdated(address proxy, address logic); event Claimed(address token, uint amount); event TokenStatusChanged(address token, bool status); event UserControllerChanged(address value); event GuildControllerChanged(address value); event PvpControllerChanged(address value); event GameTokenPriceChanged(uint value); event RewardsPoolChanged(address value); event ItemBoxControllerChanged(address value); event Process(address token, uint amount, address from, uint toBurn, uint toTreasury, uint toGov); //endregion ------------------------ Controller //region ------------------------ ReinforcementController event HeroStaked(address heroToken, uint heroId, uint biome, uint score); event HeroStakedV2(address heroToken, uint heroId, uint biome, uint rewardAmount); event HeroWithdraw(address heroToken, uint heroId); event HeroAsk(address heroToken, uint heroId); event HeroAskV2(address heroToken, uint heroId, uint hitsLast24h, uint fixedFee, uint helperRewardAmount); event TokenRewardRegistered(address heroToken, uint heroId, address token, uint amountAdded, uint totalAmount); event GuildTokenRewardRegistered(address heroToken, uint heroId, address token, uint amountAdded, uint guildId, uint64 dungeonId); event NftRewardRegistered(address heroToken, uint heroId, address token, uint id); event GuildNftRewardRegistered(address heroToken, uint heroId, address token, uint id, uint guildId, uint64 dungeonId); event ToHelperRatioChanged(uint value); event ClaimedToken(address heroToken, uint heroId, address token, uint amount, address recipient); event ClaimedItem(address heroToken, uint heroId, address item, uint itemId, address recipient); event MinLevelChanged(uint8 value); event MinLifeChancesChanged(uint value); //endregion ------------------------ ReinforcementController //region ------------------------ Treasury, reward pool event AssetsSentToDungeon(address dungeon, address token, uint amount); event RewardSentToUser(address receiver, address token, uint rewardAmount); event NotEnoughReward(address receiver, address token, uint rewardAmountToPay); event BaseAmountChanged(uint oldValue, uint newValue); //endregion ------------------------ Treasury, reward pool //region ------------------------ EventLib event EventResult(uint64 dungeonId, address heroToken, uint heroTokenId, uint8 stageId, IStatController.ActionInternalInfo gen, uint iteration); //endregion ------------------------ EventLib //region ------------------------ Item controller and helper contracts event ItemRegistered(address item, IItemController.RegisterItemParams info); event OtherItemRegistered(address item, IItemController.ItemMeta meta, bytes packedItemMetaData); event ItemRemoved(address item); event OtherItemRemoved(address item); event NewItemMinted(address item, uint itemId, IItemController.MintInfo info); event Equipped(address item, uint itemId, address heroToken, uint heroTokenId, uint8 itemSlot); event TakenOff(address item, uint itemId, address heroToken, uint heroTokenId, uint8 itemSlot, address destination); event ItemRepaired(address item, uint itemId, uint consumedItemId, uint16 baseDurability); event FailedToRepairItem(address item, uint itemId, uint consumedItemId, uint16 itemDurability); event Augmented(address item, uint itemId, uint consumedItemId, uint8 augLevel, IItemController.AugmentInfo info); event ResetAugmentation(address item, uint itemId, uint consumedItemId, IItemController.AugmentInfo info); event NotAugmented(address item, uint itemId, uint consumedItemId, uint8 augLevel); event ReduceDurability(address item, uint itemId, uint newDurability); event Used(address item, uint tokenId, address heroToken, uint heroTokenId); event Destroyed(address item, uint itemId); event FragilityReduced(address item, uint itemId, address consumedItem, uint consumedItemId, uint fragility); event ItemControllerHelper(address helper); event SetUnionConfig(uint configId, address[] items, uint[] count, address itemToMint); event RemoveUnionConfig(uint configId); event SetUnionKeyPass(address keyPassItem); event SetAugmentationProtectiveItem(address keyPassItem); event CombineItems(address msgSender, uint configId, address[] items, uint[][] itemIds, address mintedItem, uint mintedItemId); event RegisterSandboxItem(address hero, uint heroId, address item, uint itemId, uint tsMinting); event WithdrawItemsFromSandbox(address hero, uint heroId, address[] items, uint[] itemIds); event ItemReturnedToSandbox(address hero, uint heroId, address item, uint itemId); event RegisterSandboxUpgrade(address hero, uint heroId, uint tsUpgradng); event TransferItemToHeroFromSandbox(address hero, uint heroId, address item, uint itemId); event DestroyItemInSandbox(address item, uint itemId); event NewItemSentToSandbox(address item, uint itemId); event ExitFromDungeon(address hero, uint heroId); //endregion ------------------------ Item controller and helper contracts //region ------------------------ NFT and GameToken (only custom events, not ERC20/721 standards) event ChangePauseStatus(bool value); event MinterChanged(address value); event UniqueUriChanged(uint id, string uri); event BaseUriChanged(string uri); event HeroMinted(uint heroId); event HeroBurned(uint heroId); event HeroUriByStatusChanged(string uri, uint statusLvl); event ItemMinted(uint tokenId); event ItemBurned(uint tokenId); event UriByRarityChanged(string uri, uint rarity); event SponsoredHeroCreated(address msgSender, address heroAddress, uint heroId, string heroName); //endregion ------------------------ NFT and GameToken (only custom events, not ERC20/721 standards) //region ------------------------ User controller event SetUserName(address user, string name); event SetUserAvatar(address user, string avatar); event LootBoxOpened(address user, uint lootBoxKind, address[] itemTokens, uint[] itemTokenIds); event LootBoxConfigChanged(uint lootBoxKind, address[] mintItems, uint32[] mintItemsChances, uint maxDropItems); event SetFeeRenaming(uint feeRenaming); event ActivityCompleted(address user, bool daily, bool weekly); event RegisterPassedDungeon(address user, uint32 epochWeek, uint counterPassedDungeons); event RegisterPvp(address user, uint32 epochWeek, uint counterPvp); event FameHallHeroRegistered(address hero, uint heroId, address heroOwner, uint8 openedNgLevel); event SetMinHeroLevel(uint level); event SetGuildStakingAdapter(address adapter); event AddGamePoints(address user, uint finalBalanceGamePoints); /// @param paramId See IUserController.UserControllerParam event SetUserControllerParam(uint8 paramId, uint paramValue); event UseGamePointsToSkipStory(address user, uint16 storyId, uint priceInGamePoints, uint finalBalanceGamePoints); event SetStoryPassed(address user, uint16 storyId); //endregion ------------------------ User controller //region ------------------------ Guild event GuildCreated(address owner, uint guildId, string name, string urlLogo); event AddToGuild(uint guildId, address newUser); event ChangeGuildRights(uint guildId, address user, uint rights); event RemoveFromGuild(uint guildId, address user); event GuildDeleted(uint guildId); event GuildLevelUp(uint guildId, uint8 newLevel); event GuildRename(uint guildId, string newName); event GuildLogoChanged(uint guildId, string newLogoUrl); event GuildDescriptionChanged(uint guildId, string newDescription); event SetGuildRelation(uint guildId1, uint guildId2, bool peace); event TransferFromGuildBank(address user, address token, uint amount, address recipient); event TransferNftFromGuildBank(address user, address[] nfts, uint[] tokenIds, address recipient); event GuildBankDeployed(uint guildId, address guildBank); event TransferOwnership(address prevOwner, address newOwner); event SetToHelperRatio(uint guildId, uint8 value, address user); event TopUpGuildBank(address msgSender, uint guildId, address guildBank, uint amount); event GuildRequestRegistered(address msgSender, uint guildId, string userMessage, uint depositAmount); event GuildRequestStatusChanged(address msgSender, uint guildRequestId, uint8 newStatus, address user); event SetToHelperRatio(uint guildId, address msgSender, uint8 toHelperRatio); event SetGuildRequestDepositAmount(uint guildId, address msgSender, uint amount); event SetGuildBaseFee(uint fee); event SetPvpPointsCapacity(address msgSender, uint64 capacityPvpPoints, address[] users); event SetShelterController(address shelterController); event SetShelterAuction(address shelterAuction); event PayForBidFromGuildBank(uint guildId, uint amount, uint bid); //endregion ------------------------ Guild //region ------------------------ Guild shelter event RegisterShelter(uint sheleterId, uint price); event SetShelterItems( uint shelterId, address[] items, uint64[] pricesInPvpPoints, uint128[] pricesInGameTokens, uint16[] maxItemsPerDayThresholds ); event RemoveShelterItems(uint shelterId, address[] items); event BuyShelter(uint guidlId, uint shelterId); event LeaveShelter(uint guildId, uint shelterId); event NewShelterBid(uint shelterId, uint buyerGuildId, uint amount); event RevokeShelterBid(uint shelterId); event UseShelterBid(uint shelterId, uint sellerGuildId, uint buyerGuidId, uint amount); event PurchaseShelterItem(address msgSender, address item, uint numSoldItems, uint priceInPvpPoints, uint priceInGameToken); event ChangeShelterOwner(uint shelterId, uint fromGuildId, uint toGuildId); event RestInShelter(address msgSender, address heroToken, uint heroTokenId); //endregion ------------------------ Guild shelter //region ------------------------ Guild reinforcement event GuildHeroStaked(address heroToken, uint heroId, uint guildId); event GuildHeroWithdrawn(address heroToken, uint heroId, uint guildId); event GuildHeroAsked(address heroToken, uint heroId, uint guildId, address user); /// @param user Address can be 0 if heroId was already burnt at the moment of reinforcement releasing event GuildHeroReleased(address heroToken, uint heroId, uint guildId, address user); //endregion ------------------------ Guild reinforcement //region ------------------------ Pvp event AddBiomeRequest(address user, uint8 biome, uint guildId, uint32 week); event PvpHeroAdded(address user, uint guildId, address hero, uint heroId, uint week, uint8 biome); /// @param manuallyRemoved True - removed manually by the user, false - removed automatically after the fight event PvpHeroRemoved(address user, uint guildId, uint week, uint8 biome, address hero, uint heroId, bool manuallyRemoved); event PreparePvpFight(uint48 fightId, uint32 week, address hero, uint heroId, uint heroGuildId, address opponentHero, uint opponentHeroId, uint opponentGuildId); /// @notice heroId can be detected by {fightId} and {heroes} event PvpFightCompleted( IPvpController.PvpFightResults fightResult, uint48 fightId, address[2] heroes, uint64[2] guilds, bool[2] winners, uint[2] prizes, bool technicalDefeat ); event UpdatePvpEpoch(uint8 biome, uint32 week, uint guildBiomeOwnerId); event FirstPvpEpoch(uint8 biome, uint32 week); event BiomeTaxPaid(address msgSender, uint8 biome, uint guildId, uint amount, uint taxPercent, uint taxAmount, uint64 dungeonId); event BiomeTaxPaidNft(address msgSender, uint8 biome, uint guildId, address item, uint itemId, uint taxPercent, uint64 dungeonId); event AddPvpFightItems(uint48 fightId, address[] items, uint[] itemIds); //endregion ------------------------ Pvp //region ------------------------ Guild auction event AuctionPositionOpened(uint positionId, uint shelterId, uint sellerGuildId, address msgSender, uint minAuctionPrice); event AuctionPositionClosed(uint positionId, address msgSender); event AuctionBidOpened(uint bidId, uint positionId, uint amount, address msgSender); event ApplyAuctionBid(uint bidId, address msgSender); event AuctionSetFee(uint fee); //endregion ------------------------ Guild auction //region ------------------------ Guild bank event GuildBankTransfer(address token, address recipient, uint amount); event GuildBankTransferNft(address to, address nft, uint tokenId); event GuildBankTransferNftMulti(address to, address[] nfts, uint[] tokenIds); //endregion ------------------------ Guild bank //region ------------------------ Pawnshop event PawnShopRouterDeployed(address pawnShop, address gameToken, address routerOwner, address deployed); event PawnShopRouterTransfer(address token, uint amount, address receiver); event PawnShopRouterBulkSell(address[] nfts, uint[] nftIds, uint[] prices, address nftOwner, uint[] positionIds); event PawnShopRouterClosePositions(uint[] positionIds, address receiver); event PawnShopRouterBulkBuy(uint[] positionIds, address receiver); //endregion ------------------------ Pawnshop //region ------------------------ Airdrop Distributor event AirdropDistributorSetToken(address token); event AirdropDistributorAddTree(uint week, bytes32 merkleRoot_); event AirdropDistributorRemoveTree(uint week); event AirdropDistributorClaim(uint[] _weeks, uint[] amounts, address receiver); //endregion ------------------------ Airdrop Distributor //region ------------------------ GuildStakingManager event SetStakingToken(address token); event StakeTokens(address token, uint amount, uint guildId, uint total); //endregion ------------------------ GuildStakingManager }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; interface IControllable { function VERSION() external pure returns (string memory); function revision() external view returns (uint); function previousImplementation() external view returns (address); function isController(address contract_) external view returns (bool); function isGovernance(address contract_) external view returns (bool); function created() external view returns (uint256); function createdBlock() external view returns (uint256); function controller() external view returns (address); function increaseRevision(address oldLogic) external; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; interface IController { function governance() external view returns (address); function statController() external view returns (address); function storyController() external view returns (address); function gameObjectController() external view returns (address); function reinforcementController() external view returns (address); function oracle() external view returns (address); function treasury() external view returns (address); function itemController() external view returns (address); function heroController() external view returns (address); function dungeonFactory() external view returns (address); function gameToken() external view returns (address); function validTreasuryTokens(address token) external view returns (bool); function isDeployer(address adr) external view returns (bool); function onPause() external view returns (bool); function userController() external view returns (address); function guildController() external view returns (address); function pvpController() external view returns (address); function rewardsPool() external view returns (address); function itemBoxController() external view returns (address); function gameTokenPrice() external view returns (uint); function process(address token, uint amount, address from) external; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../openzeppelin/EnumerableSet.sol"; import "../openzeppelin/EnumerableMap.sol"; interface IDungeonFactory { /// @custom:storage-location erc7201:dungeon.factory.main struct MainState { /// @dev biome => dungeonLaunchedId mapping(uint => EnumerableSet.UintSet) freeDungeons; /// @dev hero + heroId + biome (packMapObject) -> completed mapping(bytes32 => bool) bossCompleted; /// @dev hero + heroId + dungNum (packDungeonKey) -> completed mapping(bytes32 => bool) specificDungeonCompleted; /// @notice Max biome completed by the hero /// @dev hero + heroId (nftPacked) -> max biome completed mapping(bytes32 => uint8) maxBiomeCompleted; /// @notice which dungeon the hero is currently in /// @dev hero+id => current DungeonId mapping(bytes32 => uint64) heroCurrentDungeon; // --- /// @notice Specific dungeon for the given pair of hero level + hero class /// ALl specific dungeons are listed also in allSpecificDungeons /// @dev packUint8Array(specReqBiome, specReqHeroClass) => dungNum mapping(bytes32 => uint16) dungeonSpecific; /// @dev contains all specific dungNum for easy management EnumerableSet.UintSet allSpecificDungeons; /// @dev biome => dungNum mapping(uint8 => EnumerableSet.UintSet) dungeonsLogicByBiome; // --- /// @dev max available biome. auto-increment with new dung deploy uint8 maxBiome; /// @notice Address of treasure token => min hero level required /// @dev manual threshold for treasury mapping(address => uint) minLevelForTreasury; /// @notice Contains arrays for SKILL_1, SKILL_2, SKILL_3 with 0 or 1 /// i.e. [0, 1, 0] means that durability of SKILL_2 should be reduced /// @dev hero + heroId => uint8[] array where idx = slotNum mapping(bytes32 => bytes32) skillSlotsForDurabilityReduction; /// @notice Counter of dungeons, it's incremented on launch of a new dungeon uint64 dungeonCounter; /// @dev dungNum = init attributes mapping(uint16 => DungeonAttributes) dungeonAttributes; /// @dev dungeonId => status mapping(uint64 => DungeonStatus) dungeonStatuses; /// @notice NG_LEVEL of the hero that has created the given dungeon mapping(uint64 dungeonId => uint ngLevel) dungeonNgLevel; } struct ObjectGenerateInfo { /// @notice List of chamber types for each unique object /// @dev uint8 types, packed using PackingLib.packUint8Array bytes32[] objTypesByStages; /// @notice List of chances for each chamber type /// @dev uint64 chances uint32[][] objChancesByStages; } struct DungeonGenerateInfo { /// @notice List of chamber types for each unique object uint8[][] objTypesByStages; /// @notice List of chances for each chamber type uint32[][] objChancesByStages; uint32[] uniqObjects; uint8 minLevel; uint8 maxLevel; bytes32[] requiredCustomDataIndex; uint64[] requiredCustomDataMinValue; uint64[] requiredCustomDataMaxValue; bool[] requiredCustomDataIsHero; } /// @notice Attributes of the given dungeon logic struct DungeonAttributes { /// @notice Total number of stages that should be passed to complete the dungeon uint8 stages; uint8 biome; /// @notice Default list of objects that should be passed in the dungeon uint32[] uniqObjects; /// @dev min+max (packUint8Array) bytes32 minMaxLevel; bytes32[] requiredCustomDataIndex; /// @notice Packed DungeonGenerateInfo.requiredCustomData: MinValue, MaxValue, IsHero /// @dev min+max+isHero(packStoryCustomDataRequirements) bytes32[] requiredCustomDataValue; ObjectGenerateInfo info; } /// @notice Current status of the given dungeon struct DungeonStatus { uint64 dungeonId; /// @notice Dungeon logic id uint16 dungNum; /// @notice True if the dungeon is completed by the hero bool isCompleted; /// @notice Hero in the dungeon or 0 address heroToken; uint heroTokenId; /// @notice Current object that should be passed by the hero. 0 - new object is not opened uint32 currentObject; /// @notice Current stage in the dungeon that should be passed by the hero. uint8 currentStage; EnumerableMap.AddressToUintMap treasuryTokens; /// @notice All items that were minted on result of made actions bytes32[] treasuryItems; /// @notice Total number of stages that should be passed to complete the dungeon /// This value can be bigger than length of uniqObjects uint8 stages; /// @notice List of objects to be passed in the stage. The list can be dynamically changed during passing the stages uint32[] uniqObjects; } //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// function launchForNewHero(address heroToken, uint heroTokenId, address owner) external returns (uint64 dungeonId); function maxBiomeCompleted(address heroToken, uint heroTokenId) external view returns (uint8); function currentDungeon(address heroToken, uint heroTokenId) external view returns (uint64); function skillSlotsForDurabilityReduction(address heroToken, uint heroTokenId) external view returns (uint8[] memory result); function setBossCompleted(uint32 objectId, address heroToken, uint heroTokenId, uint8 heroBiome) external; /// @notice Hero exists current dungeon forcibly same as when dying but without loosing life chance function exitForcibly(address heroToken, uint heroTokenId, address msgSender) external; function maxAvailableBiome() external view returns (uint8); function reborn(address hero, uint heroId) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom( address sender, address recipient, uint256 amount ) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "./IERC165.sol"; /** * @dev Required interface of an ERC721 compliant contract. */ interface IERC721 is IERC165 { /** * @dev Emitted when `tokenId` token is transferred from `from` to `to`. */ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. */ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. */ event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /** * @dev Returns the number of tokens in ``owner``'s account. */ function balanceOf(address owner) external view returns (uint256 balance); /** * @dev Returns the owner of the `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function ownerOf(uint256 tokenId) external view returns (address owner); /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom( address from, address to, uint256 tokenId ) external; /** * @dev Transfers `tokenId` token from `from` to `to`. * * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * * Emits a {Transfer} event. */ function transferFrom( address from, address to, uint256 tokenId ) external; /** * @dev Gives permission to `to` to transfer `tokenId` token to another account. * The approval is cleared when the token is transferred. * * Only a single account can be approved at a time, so approving the zero address clears previous approvals. * * Requirements: * * - The caller must own the token or be an approved operator. * - `tokenId` must exist. * * Emits an {Approval} event. */ function approve(address to, uint256 tokenId) external; /** * @dev Returns the account approved for `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function getApproved(uint256 tokenId) external view returns (address operator); /** * @dev Approve or remove `operator` as an operator for the caller. * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. * * Requirements: * * - The `operator` cannot be the caller. * * Emits an {ApprovalForAll} event. */ function setApprovalForAll(address operator, bool _approved) external; /** * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. * * See {setApprovalForAll} */ function isApprovedForAll(address owner, address operator) external view returns (bool); /** * @dev Safely transfers `tokenId` token from `from` to `to`. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data ) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @title ERC721 token receiver interface * @dev Interface for any contract that wants to support safeTransfers * from ERC721 asset contracts. */ interface IERC721Receiver { /** * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} * by `operator` from `from`, this function is called. * * It must return its Solidity selector to confirm the token transfer. * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. * * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`. */ function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "./IStatController.sol"; import "./IItemController.sol"; interface IFightCalculator { enum AttackType { UNKNOWN, // 0 MELEE, // 1 MAGIC, // 2 SLOT_3, SLOT_4, SLOT_5, SLOT_6, SLOT_7, SLOT_8, SLOT_9, SLOT_10 } /// @notice Attacker info: suitable both for hero and monsters struct AttackInfo { /// @notice Type of the attack /// by default, if attack token presents, it's magic attack and not-magic otherwise /// but this logic can become more complicated after introducing new attack types AttackType attackType; /// @notice NFT selected by hero for attack, it should be equip on. /// If attacker is a monster, this is a special case (stub NFT with zero ID is used) address attackToken; uint attackTokenId; address[] skillTokens; uint[] skillTokenIds; } struct FighterInfo { int32[] fighterAttributes; IStatController.ChangeableStats fighterStats; AttackType attackType; address attackToken; uint attackTokenId; uint race; } struct Statuses { bool stun; bool burn; bool freeze; bool confuse; bool curse; bool poison; bool gotCriticalHit; bool missed; bool hitBlocked; } struct FightResult { int32 healthA; int32 healthB; int32 manaConsumedA; int32 manaConsumedB; } struct FightCall { FighterInfo fighterA; FighterInfo fighterB; uint64 dungeonId; uint32 objectId; address heroAdr; uint heroId; uint8 stageId; uint iteration; uint8 turn; } /// @notice Additional info passed to fight struct FightCallAdd { address msgSender; /// @notice Unique ID of the pvp-fight, 0 for not pvp fights uint48 fightId; } struct SkillSlots { bool slot1; bool slot2; bool slot3; } //region ------------------------ FightLib-internal (FightInfoInternal is required by IApplicationEvents..) struct FightInfoInternal { Fighter fighterA; Fighter fighterB; } struct Fighter { IFightCalculator.FighterInfo info; IItemController.AttackInfo magicAttack; int32 health; int32 manaConsumed; int32 damage; int32 damagePoison; int32 damageReflect; IFightCalculator.Statuses statuses; } //endregion ------------------------ FightLib-internal function fight(FightCall memory callData) external returns (FightResult memory); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "./IERC20.sol"; interface IGameToken is IERC20 { function minter() external view returns (address); function mint(address account, uint amount) external returns (bool); function burn(uint amount) external returns (bool); function setMinter(address minter_) external; function pause(bool value) external; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../openzeppelin/EnumerableSet.sol"; import "./IController.sol"; interface IGOC { enum ObjectType { UNKNOWN, // 0 EVENT, // 1 MONSTER, // 2 STORY, // 3 END_SLOT } enum ObjectSubType { UNKNOWN_0, // 0 ENEMY_NPC_1, // 1 ENEMY_NPC_SUPER_RARE_2, // 2 BOSS_3, // 3 SHRINE_4, // 4 CHEST_5, // 5 STORY_6, // 6 STORY_UNIQUE_7, // 7 SHRINE_UNIQUE_8, // 8 CHEST_UNIQUE_9, // 9 ENEMY_NPC_UNIQUE_10, // 10 STORY_ON_ROAD_11, // 11 STORY_UNDERGROUND_12, // 12 STORY_NIGHT_CAMP_13, // 13 STORY_MOUNTAIN_14, // 14 STORY_WATER_15, // 15 STORY_CASTLE_16, // 16 STORY_HELL_17, // 17 STORY_SPACE_18, // 18 STORY_WOOD_19, // 19 STORY_CATACOMBS_20, // 20 STORY_BAD_HOUSE_21, // 21 STORY_GOOD_TOWN_22, // 22 STORY_BAD_TOWN_23, // 23 STORY_BANDIT_CAMP_24, // 24 STORY_BEAST_LAIR_25, // 25 STORY_PRISON_26, // 26 STORY_SWAMP_27, // 27 STORY_INSIDE_28, // 28 STORY_OUTSIDE_29, // 29 STORY_INSIDE_RARE_30, STORY_OUTSIDE_RARE_31, ENEMY_NPC_INSIDE_32, ENEMY_NPC_INSIDE_RARE_33, ENEMY_NPC_OUTSIDE_34, ENEMY_NPC_OUTSIDE_RARE_35, END_SLOT } /// @custom:storage-location erc7201:game.object.controller.main struct MainState { /// @dev objId = biome(00) type(00) id(0000) => biome(uint8) + objType(uint8) /// Id is id of the event, story or monster. mapping(uint32 => bytes32) objectMeta; /// @dev biome(uint8) + objType(uint8) => set of object id mapping(bytes32 => EnumerableSet.UintSet) objectIds; /// @dev heroAdr180 + heroId64 + cType8 + biome8 => set of already played objects. Should be cleared periodically mapping(bytes32 => EnumerableSet.UintSet) playedObjects; /// @dev HeroAdr(160) + heroId(uint64) + objId(uint32) => iteration count. It needs for properly emit events for every new entrance. mapping(bytes32 => uint) iterations; /// @dev objId(uint32) => EventInfo mapping(uint32 => EventInfo) eventInfos; /// @dev objId(uint32) => storyId mapping(uint32 => uint16) storyIds; /// @dev objId(uint32) => MonsterInfo mapping(uint32 => MonsterInfo) monsterInfos; /// @dev hero+id => last fight action timestamp mapping(bytes32 => uint) lastHeroFightTs; /// @dev delay for user actions in fight (suppose to prevent bot actions) uint fightDelay; } struct ActionResult { bool kill; bool completed; address heroToken; address[] mintItems; int32 heal; int32 manaRegen; int32 lifeChancesRecovered; int32 damage; int32 manaConsumed; uint32 objectId; uint32 experience; uint heroTokenId; uint iteration; uint32[] rewriteNextObject; } struct EventInfo { /// @dev chance to use good or bad attributes/stats uint32 goodChance; /// @dev toBytes32ArrayWithIds bytes32[] goodAttributes; bytes32[] badAttributes; /// @dev experience(uint32) + heal(int32) + manaRegen(int32) + lifeChancesRecovered(int32) + damage(int32) + manaConsume(int32) packStatsChange bytes32 statsChange; /// @dev item+chance packItemMintInfo bytes32[] mintItems; } struct MonsterInfo { /// @dev toBytes32ArrayWithIds bytes32[] attributes; /// @dev level(uint8) + race(uint8) + experience(uint32) + maxDropItems(uint8) packMonsterStats bytes32 stats; /// @dev attackToken(160) + attackTokenId(uint64) + attackType(uint8) packAttackInfo bytes32 attackInfo; /// @dev item+chance packItemMintInfo bytes32[] mintItems; /// @dev heroAdr(160) + heroId(uint64) => iteration => GeneratedMonster packed mapping(bytes32 => mapping(uint => bytes32)) _generatedMonsters; } struct MultiplierInfo { uint8 biome; /// @notice NG_LEVEL of the hero who is going to fight with the given monster /// Use type(uint8).max for !NG+ uint8 heroNgLevel; } struct GeneratedMonster { bool generated; uint8 turnCounter; int32 hp; uint32 amplifier; } struct MonsterGenInfo { uint16 monsterId; uint8 biome; ObjectSubType subType; uint8[] attributeIds; int32[] attributeValues; uint8 level; uint8 race; uint32 experience; uint8 maxDropItems; address attackToken; uint64 attackTokenId; uint8 attackType; address[] mintItems; uint32[] mintItemsChances; } struct ActionContext { address sender; address heroToken; IController controller; uint8 biome; uint8 objectSubType; uint8 stageId; uint8 heroNgLevel; uint32 objectId; uint64 dungeonId; uint heroTokenId; uint salt; uint iteration; bytes data; } struct EventRegInfo { uint8 biome; uint16 eventId; ObjectSubType subType; uint32 goodChance; AttributeGenerateInfo goodAttributes; AttributeGenerateInfo badAttributes; uint32 experience; int32 heal; int32 manaRegen; int32 lifeChancesRecovered; int32 damage; int32 manaConsumed; address[] mintItems; uint32[] mintItemsChances; } struct AttributeGenerateInfo { uint8[] ids; int32[] values; } ////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////// /// @dev represent object registration if non zero values function getObjectMeta(uint32 objectId) external view returns (uint8 biome, uint8 objectSubType); function isBattleObject(uint32 objectId) external view returns (bool); function getRandomObject( uint8[] memory cTypes, uint32[] memory chances, uint8 biomeLevel, address heroToken, uint heroTokenId ) external returns (uint32 objectId); function open(address heroToken, uint heroTokenId, uint32 objectId) external returns (uint iteration); function action( address sender, uint64 dungeonId, uint32 objectId, address heroToken, uint heroTokenId, uint8 stageId, bytes memory data ) external returns (ActionResult memory); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../openzeppelin/EnumerableSet.sol"; interface IGuildController { enum GuildRightBits { ADMIN_0, RENAME_1, CHANGE_LOGO_2, CHANGE_SHELTER_3, ADD_MEMBER_4, REMOVE_MEMBER_5, BANK_TOKENS_OPERATION_6, CHANGE_ROLES_7, LEVEL_UP_8, SET_RELATION_KIND_9, BANK_ITEMS_OPERATION_10, SET_GUILD_PARAMS_11, CHANGE_PURCHASING_SHELTER_ITEMS_CAPACITY_12, DOMINATION_REQUEST_13 } enum GuildsParams { NONE_0, COUNTER_GUILD_IDS_1, BASE_FEE_2, COUNTER_GUILD_REQUESTS_3, REENTRANT_STATUS_4, SHELTER_CONTROLLER_5, SHELTER_AUCTION_6 // max 255 params because enum is uint8 by default } enum GuildRequestStatus { NONE_0, ACCEPTED_1, REJECTED_2, CANCELED_3 } /// @custom:storage-location erc7201:guild.controller.main struct MainState { /// @notice Mapping to store various guilds params (with global values for all guilds) mapping(GuildsParams param => uint value) guildsParam; /// @notice guildId => address of instance of GuildBank contract mapping(uint guildId => address) guildBanks; /// @notice guild id => guild data (owner, name, logo, etc) mapping(uint guildId => GuildData) guildData; /// @notice name => guild id mapping(string guildName => uint guildId) nameToGuild; /// @notice EOA => guild id, EOA can be a member of a single guild only mapping(address member => uint guildId) memberToGuild; /// @notice List of participants of guilds /// @dev Allowed number of members is 20 + 5 * guildLevel mapping(uint guildId => EnumerableSet.AddressSet listEoa) members; /// @notice Rights of the member in the guild, mask of GuildRightBits mapping(address member => uint maskRights) rights; /// @notice _getGuildsPairKey(guild1, guild2) => status (false - war, true - peace) mapping(bytes32 guildsPairKey => bool) relationsPeaceful; // ---------------------------- Request to join to the guild /// @notice Full list of requests registered for the guild mapping(uint guildId => mapping(GuildRequestStatus status => EnumerableSet.UintSet guildRequestIds)) guildRequests; /// @notice List of active requests created by the given user. /// "Active" => deposit should be returned to the user. /// All not-active requests are removed from here automatically. mapping(address user => EnumerableSet.UintSet guildRequestIds) userActiveGuildRequests; /// @notice Data of all guild requests ever created mapping(uint guildRequestId => GuildRequestData) guildRequestData; /// @notice Deposit amount required to create a guild request mapping(uint guildId => GuildRequestDeposit) guildRequestDepositAmounts; /// @notice Counter of spent pvp points + number of guild pvp-points allowed to be used by the guild member mapping(uint guildId => mapping(address member => UserPvpPoints)) userPvpPoints; /// @notice guild id => guildDescription mapping(uint guildId => string) guildDescription; } struct GuildData { /// @notice Not empty unique guild name string guildName; /// @notice URL of guild logo (empty is allowed) string urlLogo; /// @notice Creator (owner) of the guild address owner; /// @notice Guild level [1...10] uint8 guildLevel; /// @notice Percent of guild reinforcement fee Value in range [_FEE_MIN ... _TO_HELPER_RATIO_MAX], i.e. [10..50] uint8 toHelperRatio; /// @notice Global guild points counter, it's incremented on each victory in php-fight. /// @dev Assume here, that uint64 is enough to store any sums of scores uint64 pvpCounter; } struct GuildRequestData { GuildRequestStatus status; /// @notice Creator of the guild request that asks to include him to the guild address user; /// @notice Message to the guild owner from the user string userMessage; uint guildId; } struct GuildRequestDeposit { bool initialized; uint192 amount; } struct UserPvpPoints { /// @notice How many guild pvp-points the user is allowed to use uint64 capacityPvpPoints; /// @notice How many guild pvp-points the user has used uint64 spentPvpPoints; } /// ---------------------------------------------------------------------------------------------- function memberOf(address user) external view returns (uint guildId); function guildToShelter(uint guildId) external view returns (uint shelterId); function getGuildData(uint guildId) external view returns ( string memory guildName, string memory urlLogo, address owner, uint8 guildLevel, uint64 pvpCounter, uint toHelperRatio ); function getRights(address user) external view returns (uint); function getGuildBank(uint guildId) external view returns (address); function shelterController() external view returns (address); function isPeacefulRelation(uint guildId, uint guildId2) external view returns (bool); function incPvpCounter(uint guildId, uint64 value) external; function usePvpPoints(uint guildId, address user, uint64 priceInPvpPoints) external; function payFromGuildBank(uint guildId, uint shelterPrice) external; function payFromBalance(uint amount, address user) external; /// @notice Ensure that the {user} has given {right}, revert otherwise function checkPermissions(address user, uint right) external view returns (uint guildId, uint rights); function shelterAuctionController() external view returns (address); function payForAuctionBid(uint guildId, uint amount, uint bid) external; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../openzeppelin/EnumerableSet.sol"; import "../openzeppelin/EnumerableMap.sol"; interface IHeroController { /// @custom:storage-location erc7201:hero.controller.main struct MainState { /// @dev A central place for all hero tokens /// @dev Deprecated. Controller is used instead. address heroTokensVault; /// @dev heroAdr => packed tokenAdr160+ amount96 mapping(address => bytes32) payToken; /// @dev heroAdr => heroCls8 mapping(address => uint8) heroClass; // --- /// @dev hero+id => individual hero name mapping(bytes32 => string) heroName; /// @dev name => hero+id, needs for checking uniq names mapping(string => bytes32) nameToHero; // --- /// @dev hero+id => biome mapping(bytes32 => uint8) heroBiome; /// @notice Exist reinforcement of any kind for the given hero /// @dev hero+id => packed reinforcement helper+id mapping(bytes32 => bytes32) reinforcementHero; /// @dev hero+id => reinforcement packed attributes mapping(bytes32 => bytes32[]) reinforcementHeroAttributes; /// @notice packedHero (hero + id) => count of calls of beforeTokenTransfer mapping(bytes32 => uint) countHeroTransfers; // ------------------------------------ NG plus /// @notice (tier, hero address) => TierInfo, where tier = [2, 3] /// @dev For tier=1 no data is required. Amount for tier 1 is stored in {payToken}, no items are minted /// Token from {payToken} is equal for all tiers mapping(bytes32 packedTierHero => TierInfo) tiers; mapping(bytes32 packedHero => HeroInfo) heroInfo; /// @notice Max NG_LVL reached by the heroes of a given account mapping(address user => uint8 maxNgLevel) maxUserNgLevel; /// @notice When the hero has killed boss on the given biome first time /// packedBiomeNgLevel = packed (biome, NG_LEVEL) mapping(bytes32 packedHero => mapping (bytes32 packedBiomeNgLevel => uint timestamp)) killedBosses; /// @notice Max NG_LEVEL reached by any user uint maxOpenedNgLevel; /// @notice Sandbox mode for heroes, see SCR-1153 mapping(bytes32 packedHero => SandboxMode sandboxMode) sandbox; } /// @notice Tier = hero creation cost option /// There are 3 tiers: /// 1: most chip option, just pay fixed amount {payTokens} - new hero is created /// 2: pay bigger amount - random skill is equipped on the newly created hero /// 3: pay even more amount - random sill + some random items are equipped on the newly created hero struct TierInfo { /// @notice Cost of the hero creation using the given tier in terms of the token stored in {payToken} /// This amount is used for tiers 2, 3. For tier 1 the amount is taken from {payToken} uint amount; /// @notice All slots for which items-to-mint are registered in {itemsToMint} EnumerableSet.UintSet slots; /// @notice slot => items that can be minted and equipped on the hero to the given {slot} after hero creation mapping(uint8 slot => address[] items) itemsToMint; } /// @notice Current NG+-related values struct HeroInfo { /// @notice Hero tier = [0..3]. 0 - the hero is post-paid, it can be changed by upgrading the hero to pre-paid uint8 tier; /// @notice NG_LVL of the hero uint8 ngLevel; /// @notice True if hero has passed last biome on current NG+ and so NG_LEVEL can be incremented (reborn is allowed) bool rebornAllowed; /// @notice Amount paid for the hero on creation OR on upgrade to NG+ /// Amount paid for creation of the hero in terms of game token (!NG+) is NOT stored here. /// @dev uint72 is used here to pack the whole struct to single slot /// Zero for sandbox-heroes uint72 paidAmount; /// @notice Pay token used to pay {paidAmount} /// Zero for sandbox-heroes address paidToken; } /// @notice Input data to create new hero struct HeroCreationData { /// @notice Desired NG_LVL of the hero uint8 ngLevel; /// @notice Desired tire of the newly created hero. Allowed values: [1..3] uint8 tier; /// @notice Enter to the dungeon after creation bool enter; /// @notice Desired hero name string heroName; /// @notice Optional: user account for which the hero is created address targetUserAccount; /// @notice Optional: ref-code to be passed to the hero-creation-related event string refCode; /// @notice SCR-1153: create not-paid hero with limited rights bool sandboxMode; } enum SandboxMode { /// @notice The hero is created in normal (not sandbox) mode NORMAL_MODE_0, /// @notice The hero was created in sandbox mode and wasn't upgraded. SANDBOX_MODE_1, /// @notice The hero was created in sandbox mode and was upgraded to the normal mode UPGRADED_TO_NORMAL_2 } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function heroClass(address hero) external view returns (uint8); function heroBiome(address hero, uint heroId) external view returns (uint8); function payTokenInfo(address hero) external view returns (address token, uint amount); function heroReinforcementHelp(address hero, uint heroId) external view returns (address helperHeroToken, uint helperHeroId); function score(address hero, uint heroId) external view returns (uint); function isAllowedToTransfer(address hero, uint heroId) external view returns (bool); function beforeTokenTransfer(address hero, uint heroId) external returns (bool); // --- function create(address hero, string memory heroName_, bool enter) external returns (uint); function kill(address hero, uint heroId) external returns (bytes32[] memory dropItems); function releaseReinforcement(address hero, uint heroId) external returns (address helperToken, uint helperId); function resetLifeAndMana(address hero, uint heroId) external; function countHeroTransfers(address hero, uint heroId) external view returns (uint); function askGuildReinforcement(address hero, uint heroId, address helper, uint helperId) external; function getHeroInfo(address hero, uint heroId) external view returns (IHeroController.HeroInfo memory data); function registerKilledBoss(address hero, uint heroId, uint32 objectId) external; function maxOpenedNgLevel() external view returns (uint); function sandboxMode(address hero, uint heroId) external view returns (uint8); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../openzeppelin/EnumerableSet.sol"; import "../openzeppelin/EnumerableMap.sol"; interface IItemBoxController { /// @custom:storage-location erc7201:ItemBox.controller.main struct MainState { mapping(bytes32 packedHero => HeroData) heroData; /// @notice Owners of all items minted in sandbox mode mapping(bytes32 packedItem => bytes32 packedHero) heroes; } struct HeroData { /// @notice Moment of upgrading sandbox-hero to normal-hero uint tsUpgraded; /// @notice List of all items registered for the hero EnumerableSet.AddressSet items; /// @notice item => (itemId => packedItemBoxItemInfo) /// @dev Ids are never deleted from the map, so the order of ids is never changed mapping(address item => EnumerableMap.UintToUintMap) states; } struct ItemBoxItemInfo { /// @notice True if the item was withdrawn from balance /// It can happens in follow cases: /// 1) the hero was upgraded and the item was withdrawn on hero owner balance /// 2) the item is used by ItemController: /// 2.1) the item is equipped on the hero and so it's transferred to the hero balance /// 2.2) the consumable item is used /// 3) the item is burnt /// @dev Status is required to avoid deletion (and so changing order) of the {items} bool withdrawn; /// @notice The moment of the initial item minting uint64 timestamp; } enum ItemState { /// @notice The item was never registered in the sandbox NOT_REGISTERED_0, /// @notice The item is not active (outdated) and cannot be used anymore NOT_AVAILABLE_1, /// @notice The item is active and located inside the sandbox INSIDE_2, /// @notice The item is either withdrawn or equipped OUTSIDE_3 } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function firstActiveItemOfHeroByIndex(address hero, uint heroId, address item) external view returns (uint itemId); function registerItems(address hero, uint heroId, address[] memory items, uint[] memory itemIds, uint countValidItems) external; function itemState(address hero, uint heroId, address item, uint itemId) external view returns (IItemBoxController.ItemState); function itemHero(address item, uint itemId) external view returns (address hero, uint heroId); function registerSandboxUpgrade(bytes32 packedHero) external; function transferToHero(address hero, uint heroId, address item, uint itemId) external; function destroyItem(address item, uint itemId) external; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "./IStatController.sol"; import "./IGOC.sol"; import "../openzeppelin/EnumerableSet.sol"; interface IItemController { enum GlobalParam { UNKNOWN_0, /// @notice Address of ItemControllerHelper ITEM_CONTROLLER_HELPER_ADDRESS_1 } /// @custom:storage-location erc7201:item.controller.main struct MainState { ////////////////// GENERATE ////////////////// EnumerableSet.AddressSet items; /// @dev itemAdr => itemMetaType8 + itemLvl8 + itemType8 + baseDurability16 + defaultRarity8 + minAttr8 + maxAttr8 + manaCost32 + req(packed core 128) mapping(address => bytes32) itemMeta; /// @dev itemAdr => packed tokenAdr160+ amount96 mapping(address => bytes32) augmentInfo; // --- common attr --- /// @dev itemAdr => id8 + min(int32) + max(int32) + chance32 mapping(address => bytes32[]) generateInfoAttributes; // --- consumable --- /// @dev itemAdr => ids+values (toBytes32ArrayWithIds) mapping(address => bytes32[]) _itemConsumableAttributes; /// @dev itemAdr => IStatController.ChangeableStats packed int32[] mapping(address => bytes32) itemConsumableStats; // --- buff --- /// @dev itemAdr => id8 + min(int32) + max(int32) + chance32 mapping(address => bytes32[]) generateInfoCasterAttributes; /// @dev itemAdr => id8 + minDmg(int32) + maxDmg(int32) + chance32 mapping(address => bytes32[]) generateInfoTargetAttributes; // --- attack --- /// @dev itemAdr => packed AttackInfo: attackType8 + min32 + max32 + factors(packed core 128) mapping(address => bytes32) generateInfoAttack; ////////////////// ITEMS INFO ////////////////// /// @dev itemAdr+id => itemRarity8 + augmentationLevel8 + itemDurability16 mapping(bytes32 => bytes32) itemInfo; /// @dev itemAdr+id => heroAdr+id mapping(bytes32 => bytes32) equippedOn; // --- common attr --- /// @dev itemAdr+Id => ids+values (toBytes32ArrayWithIds) mapping(bytes32 => bytes32[]) _itemAttributes; // --- consumable --- // consumable stats unchangeable, get them by address // --- buff --- /// @dev itemAdr+Id => ids+values (toBytes32ArrayWithIds) mapping(bytes32 => bytes32[]) _itemCasterAttributes; /// @dev itemAdr+Id => ids+values (toBytes32ArrayWithIds) mapping(bytes32 => bytes32[]) _itemTargetAttributes; // --- attack --- /// @dev itemAdr+Id => packed AttackInfo: attackType8 + min32 + max32 + factors(packed core 128) mapping(bytes32 => bytes32) _itemAttackInfo; ////////////////// Additional generate info ////////////////// /// @notice (itemAdr) => Bitmask of ConsumableActionBits mapping(address => uint) _consumableActionMask; /// --------------------------------- SIP-003: Item fragility /// @notice itemAdr + id => item fragility counter that displays the chance of an unsuccessful repair /// @dev [0...100_000], decimals 3 mapping(bytes32 packedItem => uint fragility) itemFragility; /// @notice Universal mapping to store various addresses and numbers (params of the contract) mapping (GlobalParam param => uint value) globalParam; /// @notice Item address => packedMetadata /// {packedMetaData} is encoded using abi.encode/abi.decode /// Read first byte, detect meta data type by the byte value, apply proper decoder from PackingLib mapping(address item => bytes packedMetaData) packedItemMetaData; /// --------------------------------- SCR-1263: Reverse-augmentation /// @notice Item attributes values before first augmentation. /// @dev SCR-1263: The values are required in augmentation if protective item is used and the augmentation is failed. mapping(bytes32 packedItem => ResetAugmentationData) _resetAugmentation; } struct RegisterItemParams { ItemMeta itemMeta; address augmentToken; uint augmentAmount; ItemGenerateInfo commonAttributes; IGOC.AttributeGenerateInfo consumableAttributes; IStatController.ChangeableStats consumableStats; ItemGenerateInfo casterAttributes; ItemGenerateInfo targetAttributes; AttackInfo genAttackInfo; /// @notice Bit mask of ConsumableActionBits uint consumableActionMask; } /// @notice Possible actions that can be triggered by using the consumable item enum ConsumableActionBits { CLEAR_TEMPORARY_ATTRIBUTES_0 // other items are used instead this mask } struct ItemGenerateInfo { /// @notice Attribute ids uint8[] ids; /// @notice Min value of the attribute, != 0 int32[] mins; /// @notice Max value of the attribute, != 0 int32[] maxs; /// @notice Chance of the selection [0..MAX_CHANCES] uint32[] chances; } struct ItemMeta { uint8 itemMetaType; // Level in range 1-99. Reducing durability in low level dungeons. lvl/5+1 = biome uint8 itemLevel; IItemController.ItemType itemType; uint16 baseDurability; uint8 defaultRarity; uint32 manaCost; // it doesn't include positions with 100% chance uint8 minRandomAttributes; uint8 maxRandomAttributes; IStatController.CoreAttributes requirements; } // Deprecated. Todo - remove enum FeeType { UNKNOWN, REPAIR, AUGMENT, STORY, END_SLOT } enum ItemRarity { UNKNOWN, // 0 NORMAL, // 1 MAGIC, // 2 RARE, // 3 SET, // 4 UNIQUE, // 5 END_SLOT } enum ItemType { NO_SLOT, // 0 HEAD, // 1 BODY, // 2 GLOVES, // 3 BELT, // 4 AMULET, // 5 RING, // 6 OFF_HAND, // 7 BOOTS, // 8 ONE_HAND, // 9 TWO_HAND, // 10 SKILL, // 11 OTHER, // 12 END_SLOT } enum ItemMetaType { UNKNOWN, // 0 COMMON, // 1 ATTACK, // 2 BUFF, // 3 CONSUMABLE, // 4 END_SLOT } enum AttackType { UNKNOWN, // 0 FIRE, // 1 COLD, // 2 LIGHTNING, // 3 CHAOS, // 4 END_SLOT } struct AttackInfo { AttackType aType; int32 min; int32 max; // if not zero - activate attribute factor for the attribute IStatController.CoreAttributes attributeFactors; } struct ItemInfo { ItemRarity rarity; uint8 augmentationLevel; uint16 durability; } /// @dev The struct is used in events, so it's moved here from the lib struct MintInfo { IItemController.ItemMeta meta; uint8[] attributesIds; int32[] attributesValues; IItemController.ItemRarity itemRarity; IItemController.AttackInfo attackInfo; uint8[] casterIds; int32[] casterValues; uint8[] targetIds; int32[] targetValues; } /// @dev The struct is used in events, so it's moved here from the lib struct AugmentInfo { uint8[] attributesIds; int32[] attributesValues; IItemController.AttackInfo attackInfo; uint8[] casterIds; int32[] casterValues; uint8[] targetIds; int32[] targetValues; } ///region ------------------------ Item type "Other" /// @notice Possible kinds of "Other" items /// Each "Other" item has each own structure for metadata, see OtherItemXXX enum OtherSubtypeKind { UNKNOWN_0, /// @notice Item to reduce fragility, see SCB-1014. Metadata is {OtherItemReduceFragility} REDUCE_FRAGILITY_1, /// @notice This item allows asking guild reinforcement to the guild member USE_GUILD_REINFORCEMENT_2, /// @notice Exit from dungeon (shelter of level 3 is required) EXIT_FROM_DUNGEON_3, /// @notice OTHER_5 Rest in the shelter: restore of hp & mp, clear temporally attributes, clear used consumables (shelter of level 3 is required) /// @dev It's OTHER_5 in deploy script, but internally it has subtype 4, see gen_others.ts REST_IN_SHELTER_4, /// @notice OTHER_4 Stub item that has no logic in contracts, but it has correct (not empty) packedMetaData /// @dev It's OTHER_4 in deploy script, but internally it has subtype 5, see gen_others.ts EMPTY_NO_LOGIC_5, END_SLOT } struct OtherItemReduceFragility { /// @notice "Other" item kind. It MUST BE first field in the struct. uint8 kind; /// @notice Value on which the fragility will be reduced. /// @dev [0...100%], decimals 3, so the value is in the range [0...10_000] uint248 value; } ///endregion ------------------------ Item type "Other" struct AugmentOptParams { /// @notice Optional protective item /// @dev SCR-1263: If the protective item specified /// than failed augmentation doesn't destroy main item but reduces its augmentation level to the zero instead. /// Protective item is configured in ItemControllerHelper. address protectiveItem; uint protectiveItemId; } struct ResetAugmentationData { /// @notice Moment of the first augmentation if any uint tsFirstAugmentation; /// @notice Values of the item attributes before the first augmentation /// @dev Use PackingLib.toInt32ArrayWithIds to decode attribute ids and values bytes32[] itemAttributes; /// @notice Values of the caster attributes before the first augmentation /// @dev Use PackingLib.toInt32ArrayWithIds to decode attribute ids and values bytes32[] itemCasterAttributes; /// @notice Values of the target attributes before the first augmentation /// @dev Use PackingLib.toInt32ArrayWithIds to decode attribute ids and values bytes32[] itemTargetAttributes; /// @notice packed AttackInfo: attackType8 + min32 + max32 + factors(packed core 128) bytes32 itemAttackInfo; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// function itemMeta(address item) external view returns (ItemMeta memory meta); function augmentInfo(address item) external view returns (address token, uint amount); function genAttributeInfo(address item) external view returns (ItemGenerateInfo memory info); function genCasterAttributeInfo(address item) external view returns (ItemGenerateInfo memory info); function genTargetAttributeInfo(address item) external view returns (ItemGenerateInfo memory info); function genAttackInfo(address item) external view returns (AttackInfo memory info); function itemInfo(address item, uint itemId) external view returns (ItemInfo memory info); function equippedOn(address item, uint itemId) external view returns (address hero, uint heroId); function itemAttributes(address item, uint itemId) external view returns (int32[] memory values, uint8[] memory ids); function consumableAttributes(address item) external view returns (int32[] memory values, uint8[] memory ids); function consumableStats(address item) external view returns (IStatController.ChangeableStats memory stats); function casterAttributes(address item, uint itemId) external view returns (int32[] memory values, uint8[] memory ids); function targetAttributes(address item, uint itemId) external view returns (int32[] memory values, uint8[] memory ids); function itemAttackInfo(address item, uint itemId) external view returns (AttackInfo memory info); function score(address item, uint tokenId) external view returns (uint); function isAllowedToTransfer(address item, uint tokenId) external view returns (bool); // --- function mint(address item, address recipient) external returns (uint itemId); function reduceDurability(address hero, uint heroId, uint8 biome, bool reduceDurabilityAllSkills) external; function destroy(address item, uint tokenId) external; function takeOffDirectly( address item, uint itemId, address hero, uint heroId, uint8 itemSlot, address destination, bool broken ) external; /// @notice SIP-003: item fragility counter that displays the chance of an unsuccessful repair. /// @dev [0...100%], decimals 3, so the value is in the range [0...10_000] function itemFragility(address item, uint itemId) external view returns (uint); /// @notice SIP-003: The quest mechanic that previously burned the item will increase its fragility by 1% function incBrokenItemFragility(address item, uint itemId) external; function equip( address hero, uint heroId, address[] calldata items, uint[] calldata itemIds, uint8[] calldata itemSlots ) external; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; interface IMinter { function amountForDungeon(uint dungeonBiomeLevel, uint heroLevel) external view returns (uint); function mintDungeonReward(uint64 dungeonId, uint dungeonBiomeLevel, uint heroLevel) external returns (uint amount); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; interface IOracle { function getRandomNumber(uint max, uint seed) external returns (uint); function getRandomNumberInRange(uint min, uint max, uint seed) external returns (uint); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../openzeppelin/EnumerableSet.sol"; import "../openzeppelin/EnumerableMap.sol"; import "./IFightCalculator.sol"; interface IPvpController { enum PvpParams { NONE_0, /// @notice Hero can be pvp-staked if his level is greater of equal to the given min level MIN_HERO_LEVEL_1, /// @notice Address of IGuildStakingAdapter, can be not initialized GUILD_STAKING_ADAPTER_2, /// @notice Unique ID of the pvp-fight (each pvp-fight consists from multiple turns) FIGHT_COUNTER_3 // max 255 params because enum is uint8 by default } /// @custom:storage-location erc7201:pvp.controller.main struct MainState { /// @notice Mapping to store various params of PvpController mapping(PvpParams param => uint value) pvpParam; /// @notice Current states of biomes mapping(uint8 biome => BiomeData) biomeState; /// @notice Biomes owned by the guilds mapping(uint guildId => uint8 biome) ownedBiome; mapping(uint epochWeek => EpochData) epochData; } struct EpochData { /// @notice Current state of the user in the current epoch mapping (address user => PvpUserState) pvpUserState; /// @notice biome data for the given epoch mapping(uint8 biome => EpochBiomeData) epochBiomeData; /// @notice All prepared pvp-fights for the given user /// Index of currently active fight is stored in {pvpUserState.activeFightIndex1} mapping (address user => PvpFightData[]) fightData; /// @notice All currently registered packed-heroes EnumerableSet.UintSet stakedHeroes; /// @notice Weekly request of the guild to dominate at the given biome starting from the next week mapping(uint guildId => uint8 biome) targetBiome; /// @notice All guilds pretend for the given biome mapping(uint8 biome => EnumerableSet.UintSet guildIds) biomeGuilds; } /// @notice Current state of the user. Possible states: user has or hasn't staked a hero in pvp. /// Each user is able to stake pvp-heroes multiple times per epoch /// but the user is able to stake only 1 pvp-hero at any moment. /// @dev Implementation assumes that the struct occupies single slot, the struct is read as a whole struct PvpUserState { /// @notice Domination biome at the moment of staking /// @dev not 0 if the user has pvp-staked hero uint8 biome; /// @notice 1-based index of currently active fight in {fightData} (the fight is either prepared or in-progress). /// 0 - there is no active fight uint32 activeFightIndex1; /// @notice How many times the user has staked heroes for PVP /// @dev Max possible value is limited by MAX_NUMBER_STAKES_FOR_USER_PER_EPOCH uint32 numHeroesStaked; /// @notice User's guild at the moment of staking /// 0 if user has no hero staked in pvp currently uint64 guildId; /// @notice Total number of pvp-fights performed since the last call of addPvpHero. /// @dev All pvp-fights are won here because looser is auto removed. uint8 countFights; /// @notice Max number of pvp-fights allowed by the user per single call of addPvpHero, 0 - no limits uint8 maxFights; /// @notice Unique id of the current pvp-fight (the fight with activeFightIndex1) uint48 fightId; } struct BiomeData { /// @notice Biome owner - the guild that dominates in the biome at the given epoch. He has a right to get a tax /// @dev Assume here that uint64 is enough to store any guildId. It allows us to store whole struct in a single slot uint64 guildBiomeOwnerId; /// @notice Current epoch (last epoch for which pvp-battle was made) /// 0 if epoch was never started uint32 startedEpochWeek; /// @notice Number of consecutive epochs during which {guildBiomeOwnerId} wasn't changed uint16 dominationCounter; } struct EpochBiomeData { /// @notice List of guilds asked for domination in the biome => total points scored by the guilds in the given epoch /// @dev guildId => count points EnumerableMap.UintToUintMap guildPoints; /// @notice All users free for pvp-fight /// User is added here on registration and removed as soon as the fight for the user is initialized. mapping(uint guildId => EnumerableSet.AddressSet) freeUsers; /// @notice All users (from the {guilds}) provided heroes for pvp /// @dev guildId => (user address => packedHero (hero + heroId)) mapping(uint guildId => EnumerableMap.AddressToUintMap) registeredHeroes; /// @notice The skills and attack type selected in advance mapping(bytes32 packedHero => bytes) pvpStrategy; } enum PvpFightStatus { /// @notice No fight, the hero doesn't have selected opponent NOT_INITIALIZED_0, /// @notice The hero has opponent, the fight is not started PREPARED_1, /// @notice The fight is started but not completed FIGHTING_2, /// @notice The fight is completed, the hero is the winner WINNER_3, /// @notice The fight is completed, the hero is the looser LOSER_4 } /// @notice Current state of the fight /// @dev Implementation assumes that the struct occupies single slot, the struct is read as a whole /// @dev We don't store biome and guildId here. This info is stored in user state and can be lost after fight ending. struct PvpFightData { /// @notice address of user whose hero is the fight opponent address fightOpponent; /// @notice Current status of PVP-fight PvpFightStatus fightStatus; /// @notice Current value of the health (only when fightStatus is FIGHTING_2) uint32 health; /// @notice Current value of the mana (only when fightStatus is FIGHTING_2) uint32 mana; /// @notice Number of moves made (only when fightStatus is FIGHTING_2) uint8 countTurns; } /// @dev Implementation assumes that the struct occupies single slot, the struct is read as a whole struct PvpFightResults { bool completed; uint8 totalCountFights; uint32 healthHero; uint32 healthOpponent; uint32 manaConsumedHero; uint32 manaConsumedOpponent; } /// @notice Strategy how to use attack info enum PvpBehaviourStrategyKinds { /// @notice Use all skills, use magic attack if it's available /// @dev {PvpStrategyDefault} is used as data in {addPvpHero} DEFAULT_STRATEGY_0 // new strategies are able to use different structures to store data passed to {addPvpHero} } /// @notice The data provided by user at the staking with {DEFAULT_STRATEGY_0} struct PvpStrategyDefault { /// @notice Should be equal to DEFAULT_STRATEGY_0 uint behaviourStrategyKind; IFightCalculator.AttackInfo attackInfo; } struct HeroData { address hero; uint heroId; bytes pvpStrategy; } /// ------------------------------------------------------------------------------------------------------------------ /// ------------------------------------------------------------------------------------------------------------------ /// ------------------------------------------------------------------------------------------------------------------ /// @notice Update epoch if necessary and return actual biome owner and tax /// @return guildId Owner of the biome /// @return taxPercent Tax percent , [0...100_000], decimals 3 function refreshBiomeTax(uint8 biome) external returns (uint guildId, uint taxPercent); function isHeroStakedCurrently(address hero, uint heroId) external view returns (bool staked); function onGuildDeletion(uint guildId) external; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "./IStatController.sol"; import "../openzeppelin/EnumerableMap.sol"; /// @notice Terms /// Reinforcement v1: helper is selected randomly in askHero, fixed part of rewards (tokens and NFT) is sent to the helper. /// Guild reinforcement: helper is selected from guild heroes. Rewards are sent to guild bank. /// Reinforcement v2: helper is selected manually in askHeroV2, helper receives fixed amount. interface IReinforcementController { enum ConfigParams { /// @notice Packed MinMaxBoardV2 V2_MIN_MAX_BOARD_0 } /// @custom:storage-location erc7201:reinforcement.controller.main struct MainState { // ------------------------ Reinforcement v1 /// @dev minLvl8 + minLifeChances8 bytes32 config; /// @dev hero token + hero id => heroInfo(biome8 + score128 + fee8 + stakeTs64) mapping(bytes32 => bytes32) _stakedHeroes; /// @dev biome => helperAdr+id mapping(uint => EnumerableSet.Bytes32Set) _internalIdsByBiomes; /// @dev biome => score // The field is deprecated and not updated any more mapping(uint => uint) maxScore; /// @dev heroAdr+id => itemAdr+id mapping(bytes32 => bytes32[]) _heroNftRewards; /// @dev heroAdr+id => tokenAdr and amount map mapping(bytes32 => EnumerableMap.AddressToUintMap) _heroTokenRewards; // ------------------------ Guild reinforcement /// @notice All staked guild heroes for the given guild /// @dev helper (hero token + hero id) => guild mapping(bytes32 packedHero => uint guildId) stakedGuildHeroes; /// @notice All guild heroes that are currently in use by guild reinforcement /// It's allowed to withdraw a hero before reinforcement releasing, /// so it's possible to have !0 in {guildBusyHelpers} and 0 in {stakedGuildHeroes} simultaneously. /// @dev helper (hero token + hero id) => guildId (guild at the moment of askGuildReinforcement) mapping(bytes32 packedHero => uint guildId) busyGuildHelpers; /// @notice All (free and busy) staked guild heroes per guild. /// guild => (packed helper => guild where the helper is busy currently) /// @dev There is a chance that guilds are different here /// i.e. hero can be: /// 1) added to G1 2) staked in G1 3) asked for help 4) withdrawn 5) G1=>G2 6) staked in G2 /// In such case guildHelpers[G2][hero] = G1, guildHelpers[G1][hero] = 0 /// After releasing guildHelpers[G2][hero] = 0 mapping(uint guildId => EnumerableMap.Bytes32ToUintMap) guildHelpers; /// @notice Moment of withdrawing the hero from staking. Next staking is possible in 1 day since withdrawing mapping(bytes32 packedHero => uint lastWithdrawTimestamp) lastGuildHeroWithdrawTs; // ------------------------ Reinforcement v2 /// @notice Map to store various config params mapping(ConfigParams paramId => uint) configParams; mapping(bytes32 packedHero => HeroInfoV2) stakedHeroesV2; /// @notice biome => set of packedHero. All staked heroes (they can be busy of free currently) mapping(uint biome => EnumerableSet.Bytes32Set) heroesByBiomeV2; mapping(uint biome => LastWindowsV2) stat24hV2; } /// @notice Deprecated. Reinforcement v1 struct HeroInfo { uint8 biome; uint score; // stored in 128 but easy to use 256 /// @notice To helper ratio uint8 fee; uint64 stakeTs; } struct HeroInfoV2 { uint8 biome; uint64 stakeTs; /// @notice Amount of game token that is paid to the helper at the moment of the call {askHeroV2} uint128 rewardAmount; } /// @notice Statistic of askHeroV2 calls per last 24 hours at the moment of the last call struct LastWindowsV2 { /// @notice 24 hours are divided on 8 intervals, each interval is 3 hour /// Current basket has index {basketIndex} /// {baskets[current basket]} contains "old" value. /// New value for the current basket is collected in {basketValue}. /// The value for the current basket is calculated as weighted average of old and new values. /// New value replaces the old value at the moment of changing current basket index. uint24[8] baskets; /// @notice New value (hits counter) for current basket uint24 basketValue; /// @notice Abs. index of the current basket (abs. hour / 3) uint48 basketIndex; } /// @dev 1 slot struct ConfigReinforcementV2 { /// @notice if Number-of-askHeroV2-calls is below given value then burn fee has min value uint32 minNumberHits; /// @notice if Number-of-askHeroV2-calls is above given value then burn fee has max value uint32 maxNumberHits; /// @notice Lowest fee = amountForDungeon / given value, i.e. 100 => amountForDungeon/100 as lower fee uint32 lowDivider; /// @notice Highest fee = amountForDungeon / given value, i.e. 2 => amountForDungeon/2 as highest fee uint32 highDivider; /// @notice Limit for min level of the staked hero /// In practice we need following limitation: (stats.level < 5 || (stats.level - 5) / 5 < biome) /// so, levelLimit should be equal 5 /// In tests we need to be able to disable such limitation, so levelLimit = 0 allow to disable that constraint uint8 levelLimit; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function toHelperRatio(address heroToken, uint heroId) external view returns (uint); function isStaked(address heroToken, uint heroId) external view returns (bool); function registerTokenReward(address heroToken, uint heroId, address token, uint amount, uint64 dungeonId) external; function registerNftReward(address heroToken, uint heroId, address token, uint tokenId, uint64 dungeonId) external; function askHeroV2(address hero, uint heroId, address helper, uint helperId) external returns (int32[] memory attributes); function askGuildHero(address hero, uint heroId, address helper, uint helperId) external returns (int32[] memory attributes); /// @notice Return the guild in which the hero is currently asked for guild reinforcement function busyGuildHelperOf(address heroToken, uint heroId) external view returns (uint guildId); function releaseGuildHero(address helperHeroToken, uint helperHeroTokenId) external; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; interface IRewardsPool { /// @custom:storage-location erc7201:rewards.pool.main struct MainState { mapping(address token => uint baseAmountValue) baseAmounts; } function balanceOfToken(address token) external view returns (uint); function rewardAmount(address token, uint maxBiome, uint maxNgLevel, uint biome, uint heroNgLevel) external view returns (uint); function sendReward(address token, uint rewardAmount_, address receiver) external; function lostProfitPercent(uint maxBiome, uint maxNgLevel, uint heroNgLevel) external view returns (uint percent); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../openzeppelin/EnumerableSet.sol"; import "../openzeppelin/EnumerableMap.sol"; interface IStatController { /// @custom:storage-location erc7201:stat.controller.main struct MainState { mapping(bytes32 => bytes32[]) heroTotalAttributes; /// @dev heroAdr+heroId => int32 packed strength, dexterity, vitality, energy mapping(bytes32 => bytes32) _heroCore; mapping(bytes32 => bytes32[]) heroBonusAttributes; mapping(bytes32 => bytes32[]) heroTemporallyAttributes; /// @dev heroAdr+heroId => uint32 packed level, experience, life, mana, lifeChances mapping(bytes32 => bytes32) heroStats; /// @dev heroAdr+heroId+itemSlot => itemAdr + itemId mapping(bytes32 => bytes32) heroSlots; /// @dev heroAdr+heroId => busy slots uint8[] packed mapping(bytes32 => bytes32) heroBusySlots; mapping(bytes32 => EnumerableSet.AddressSet) usedConsumables; /// @dev heroCustomDataV2 is used instead mapping(bytes32 => mapping(bytes32 => uint)) _deprecated_heroCustomData; mapping(bytes32 => uint) globalCustomData; /// @notice packNftIdWithValue(hero, heroId, ngLevel) => hero custom data map /// @dev initially it was packedHero => hero custom data map mapping(bytes32 => EnumerableMap.Bytes32ToUintMap) heroCustomDataV2; } enum ATTRIBUTES { // core STRENGTH, // 0 DEXTERITY, // 1 VITALITY, // 2 ENERGY, // 3 // attributes DAMAGE_MIN, // 4 DAMAGE_MAX, // 5 ATTACK_RATING, // 6 DEFENSE, // 7 BLOCK_RATING, // 8 LIFE, // 9 MANA, // 10 // resistance FIRE_RESISTANCE, // 11 COLD_RESISTANCE, // 12 LIGHTNING_RESISTANCE, // 13 // dmg against DMG_AGAINST_HUMAN, // 14 DMG_AGAINST_UNDEAD, // 15 DMG_AGAINST_DAEMON, // 16 DMG_AGAINST_BEAST, // 17 // defence against DEF_AGAINST_HUMAN, // 18 DEF_AGAINST_UNDEAD, // 19 DEF_AGAINST_DAEMON, // 20 DEF_AGAINST_BEAST, // 21 // --- unique, not augmentable // hero will not die until have positive chances LIFE_CHANCES, // 22 // increase chance to get an item MAGIC_FIND, // 23 // decrease chance to get an item DESTROY_ITEMS, // 24 // percent of chance x2 dmg CRITICAL_HIT, // 25 // dmg factors MELEE_DMG_FACTOR, // 26 FIRE_DMG_FACTOR, // 27 COLD_DMG_FACTOR, // 28 LIGHTNING_DMG_FACTOR, // 29 // increase attack rating on given percent AR_FACTOR, // 30 // percent of damage will be converted to HP LIFE_STOLEN_PER_HIT, // 31 // amount of mana restored after each battle MANA_AFTER_KILL, // 32 // reduce all damage on percent after all other reductions DAMAGE_REDUCTION, // 33 // -- statuses // chance to stun an enemy, stunned enemy skip next hit STUN, // 34 // chance burn an enemy, burned enemy will loss 50% of defence BURN, // 35 // chance freeze an enemy, frozen enemy will loss 50% of MELEE damage FREEZE, // 36 // chance to reduce enemy's attack rating on 50% CONFUSE, // 37 // chance curse an enemy, cursed enemy will loss 50% of resistance CURSE, // 38 // percent of dmg return to attacker REFLECT_DAMAGE_MELEE, // 39 REFLECT_DAMAGE_MAGIC, // 40 // chance to poison enemy, poisoned enemy will loss 10% of the current health POISON, // 41 // reduce chance get any of uniq statuses RESIST_TO_STATUSES, // 42 END_SLOT // 43 } // possible // HEAL_FACTOR struct CoreAttributes { int32 strength; int32 dexterity; int32 vitality; int32 energy; } struct ChangeableStats { uint32 level; uint32 experience; uint32 life; uint32 mana; uint32 lifeChances; } enum ItemSlots { UNKNOWN, // 0 HEAD, // 1 BODY, // 2 GLOVES, // 3 BELT, // 4 AMULET, // 5 BOOTS, // 6 RIGHT_RING, // 7 LEFT_RING, // 8 RIGHT_HAND, // 9 LEFT_HAND, // 10 TWO_HAND, // 11 SKILL_1, // 12 SKILL_2, // 13 SKILL_3, // 14 END_SLOT // 15 } struct NftItem { address token; uint tokenId; } enum Race { UNKNOWN, // 0 HUMAN, // 1 UNDEAD, // 2 DAEMON, // 3 BEAST, // 4 END_SLOT // 5 } struct ChangeAttributesInfo { address heroToken; uint heroTokenId; int32[] changeAttributes; bool add; bool temporally; } struct BuffInfo { address heroToken; uint heroTokenId; uint32 heroLevel; address[] buffTokens; uint[] buffTokenIds; } /// @dev This struct is used inside event, so it's moved here from lib struct ActionInternalInfo { int32[] posAttributes; int32[] negAttributes; uint32 experience; int32 heal; int32 manaRegen; int32 lifeChancesRecovered; int32 damage; int32 manaConsumed; address[] mintedItems; } function initNewHero(address token, uint tokenId, uint heroClass) external; function heroAttributes(address token, uint tokenId) external view returns (int32[] memory); function heroAttribute(address token, uint tokenId, uint index) external view returns (int32); function heroAttributesLength(address token, uint tokenId) external view returns (uint); function heroBaseAttributes(address token, uint tokenId) external view returns (CoreAttributes memory); function heroCustomData(address token, uint tokenId, bytes32 index) external view returns (uint); function globalCustomData(bytes32 index) external view returns (uint); function heroStats(address token, uint tokenId) external view returns (ChangeableStats memory); function heroItemSlot(address token, uint64 tokenId, uint8 itemSlot) external view returns (bytes32 nftPacked); function heroItemSlots(address heroToken, uint heroTokenId) external view returns (uint8[] memory); function isHeroAlive(address heroToken, uint heroTokenId) external view returns (bool); function levelUp(address token, uint tokenId, uint heroClass, CoreAttributes memory change) external returns (uint newLvl); function changeHeroItemSlot( address heroToken, uint64 heroTokenId, uint itemType, uint8 itemSlot, address itemToken, uint itemTokenId, bool equip ) external; function changeCurrentStats( address token, uint tokenId, ChangeableStats memory change, bool increase ) external; function changeBonusAttributes(ChangeAttributesInfo memory info) external; function registerConsumableUsage(address heroToken, uint heroTokenId, address item) external; function clearUsedConsumables(address heroToken, uint heroTokenId) external; function clearTemporallyAttributes(address heroToken, uint heroTokenId) external; function buffHero(BuffInfo memory info) external view returns (int32[] memory attributes, int32 manaConsumed); function setHeroCustomData(address token, uint tokenId, bytes32 index, uint value) external; function setGlobalCustomData(bytes32 index, uint value) external; /// @notice Restore life and mana during reinforcement /// @dev Life and mana will be increased on ((current life/mana attr value) - (prev life/mana attr value)) /// @param prevAttributes Hero attributes before reinforcement function restoreLifeAndMana(address heroToken, uint heroTokenId, int32[] memory prevAttributes) external; function reborn(address heroToken, uint heroTokenId, uint heroClass) external; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../interfaces/IGOC.sol"; import "../interfaces/IItemController.sol"; import "../interfaces/IStatController.sol"; import "../lib/ControllerContextLib.sol"; import "../openzeppelin/EnumerableSet.sol"; import "./IController.sol"; import "./IHeroController.sol"; import "./IOracle.sol"; interface IStoryController { enum AnswerResultId { UNKNOWN, // 0 SUCCESS, // 1 ATTRIBUTE_FAIL, // 2 RANDOM_FAIL, // 3 DELAY_FAIL, // 4 HERO_CUSTOM_DATA_FAIL, // 5 GLOBAL_CUSTOM_DATA_FAIL, // 6 END_SLOT } enum CustomDataResult { UNKNOWN, // 0 HERO_SUCCESS, // 1 HERO_FAIL, // 2 GLOBAL_SUCCESS, // 3 GLOBAL_FAIL, // 4 END_SLOT } /// @custom:storage-location erc7201:story.controller.main struct MainState { // --- STORY REG INFO --- /// @dev Uniq story identification. mapping(uint32 => uint16) storyIds; /// @dev Revers mapping for stories for using in the next object rewrite logic. mapping(uint16 => uint32) idToStory; /// @dev Store used ids for stories. mapping(uint16 => bool) _usedStoryIds; /// @dev Prevent register the story twice mapping(uint32 => bool) registeredStories; // --- ANSWER MAPPING --- /// @dev storyId => all story pages. We need to have this mapping for properly remove meta info mapping(uint16 => EnumerableSet.UintSet) allStoryPages; /// @dev storyId => all possible answers. We need to have this mapping for properly remove meta info mapping(uint16 => EnumerableSet.Bytes32Set) allStoryAnswers; /// @dev storyId + pageId + heroClass (zero is default answers) => storyId + pageId + heroClass (zero is default answers) + answerId mapping(bytes32 => bytes32[]) answers; /// @dev answerUnPackedId + answerResultId => nextPageIds (will be chosen randomly from this array) /// where answerResultId is: /// 0 - unknown, /// 1 - success, /// 2 - attr fail /// 3 - random fail /// 4 - delay fail /// 5 - hero custom data fail /// 6 - global custom data fail /// see COUNT_ANSWER_RESULT_IDS mapping(bytes32 => uint16[]) nextPageIds; /// @dev story + pageId + heroClass (zero is default answers) => random nextObjs (adr + id, like packed nft id) mapping(bytes32 => uint32[]) nextObjectsRewrite; /// @dev answerPackedId => packed array of uint32[] /// 0 - random requirement(uint32, 1 - 99% success of this action, zero means no check) /// 1 - delay requirement(uint32, if time since the last call more than this value the check is fail, zero means no check) /// 2 - isFinalAnswer(uint8) mapping(bytes32 => bytes32) answerAttributes; // --- ANSWER REQUIREMENTS --- /// @dev answerPackedId => array of AttributeRequirementsPacked mapping(bytes32 => bytes32[]) attributeRequirements; /// @dev answerPackedId=> array of ItemRequirementsPacked mapping(bytes32 => bytes32[]) itemRequirements; /// @dev answerPackedId => array of TokenRequirementsPacked mapping(bytes32 => bytes32[]) tokenRequirements; /// @dev answerPackedId => custom data for hero mapping(bytes32 => CustomDataRequirementPacked[]) heroCustomDataRequirement; /// @dev answerPackedId => global custom data mapping(bytes32 => CustomDataRequirementPacked[]) globalCustomDataRequirement; // --- ANSWER RESULTS --- /// @dev answerPackedId => change attributes mapping(bytes32 => bytes32[]) successInfoAttributes; /// @dev answerPackedId => change stats mapping(bytes32 => bytes32) successInfoStats; /// @dev answerPackedId => mint items mapping(bytes32 => bytes32[]) successInfoMintItems; /// @dev answerPackedId => change attributes mapping(bytes32 => bytes32[]) failInfoAttributes; /// @dev answerPackedId => change stats mapping(bytes32 => bytes32) failInfoStats; /// @dev answerPackedId => mint items mapping(bytes32 => bytes32[]) failInfoMintItems; /// @dev answerUnPackedId + CustomDataResult => custom data array change /// where CustomDataResult is /// 1 - hero success /// 2 - hero fail /// 3 - global success /// 4 - global fail /// see COUNT_CUSTOM_DATA_RESULT_IDS mapping(bytes32 => bytes32[]) customDataResult; /// @notice answerPackedId => slot+chance+stopIfBurnt /// @dev Since SIP-003 the items are not burn but broke mapping(bytes32 => bytes32[]) burnItem; // --- GENERAL STORY REQUIREMENTS --- /// @dev story => Custom hero data requirements for a story. If exist and hero is not eligible should be not chose in a dungeon. mapping(uint => CustomDataRequirementRangePacked[]) storyRequiredHeroData; /// @dev story => Minimal level for the history. 0 means no requirements. mapping(uint => uint) storyRequiredLevel; // --- HERO STATES --- /// @dev hero + heroId + storyId => pageId + heroLastActionTS mapping(bytes32 => bytes32) heroState; // --- OTHER --- /// @dev storyId => build hash for the last update mapping(uint16 => uint) storyBuildHash; /// @notice Number of already minted items by the user within the given iteration of the story. /// Only minting of the given number of items is allowed per iteration (see MAX_MINTED_ITEMS_PER_ITERATION). /// @dev hero, heroId, story => mintedInIteration /// This map is not cleared: storyId:objectId is 1:1, each object has own sequence of iterations without duplicates mapping(bytes32 => mapping(uint iteration => uint countMintedItems)) mintedInIteration; /// @notice True if the story is allowed to be skipped, see SCR-1248 EnumerableSet.UintSet skippableStory; } /// @dev We need to have flat structure coz Solidity can not handle arrays of structs properly struct StoryMetaInfo { uint16 storyId; // --- story reqs bytes32[] requiredCustomDataIndex; uint64[] requiredCustomDataMinValue; uint64[] requiredCustomDataMaxValue; bool[] requiredCustomDataIsHero; uint minLevel; // --- answer reqs AnswersMeta answersMeta; AnswerNextPageMeta answerNextPage; AnswerAttributeRequirementsMeta answerAttributeRequirements; AnswerItemRequirementsMeta answerItemRequirements; AnswerTokenRequirementsMeta answerTokenRequirements; AnswerAttributesMeta answerAttributes; AnswerCustomDataMeta answerHeroCustomDataRequirement; AnswerCustomDataMeta answerGlobalCustomDataRequirement; // --- answer results AnswerBurnRandomItemMeta answerBurnRandomItemMeta; NextObjRewriteMeta nextObjRewriteMeta; // --- story results AnswerResultMeta successInfo; AnswerResultMeta failInfo; AnswerCustomDataResultMeta successHeroCustomData; AnswerCustomDataResultMeta failHeroCustomData; AnswerCustomDataResultMeta successGlobalCustomData; AnswerCustomDataResultMeta failGlobalCustomData; } struct NextObjRewriteMeta { uint16[] nextObjPageIds; uint8[] nextObjHeroClasses; uint32[][] nextObjIds; } struct AnswersMeta { uint16[] answerPageIds; uint8[] answerHeroClasses; uint16[] answerIds; } struct AnswerNextPageMeta { uint16[] pageId; uint8[] heroClass; uint16[] answerId; uint8[] answerResultIds; uint16[][] answerNextPageIds; } struct AnswerAttributeRequirementsMeta { uint16[] pageId; uint8[] heroClass; uint16[] answerId; bool[][] cores; uint8[][] ids; int32[][] values; } struct AnswerItemRequirementsMeta { uint16[] pageId; uint8[] heroClass; uint16[] answerId; address[][] requireItems; bool[][] requireItemBurn; bool[][] requireItemEquipped; } struct AnswerTokenRequirementsMeta { uint16[] pageId; uint8[] heroClass; uint16[] answerId; address[][] requireToken; uint88[][] requireAmount; bool[][] requireTransfer; } struct AnswerAttributesMeta { uint16[] pageId; uint8[] heroClass; uint16[] answerId; uint32[] randomRequirements; uint32[] delayRequirements; bool[] isFinalAnswer; } struct AnswerCustomDataMeta { uint16[] pageId; uint8[] heroClass; uint16[] answerId; bytes32[][] dataIndexes; bool[][] mandatory; uint64[][] dataValuesMin; uint64[][] dataValuesMax; } struct AnswerResultMeta { uint16[] pageId; uint8[] heroClass; uint16[] answerId; uint8[][] attributeIds; /// @dev Max value is limitied by int24, see toBytes32ArrayWithIds impl int32[][] attributeValues; uint32[] experience; int32[] heal; int32[] manaRegen; int32[] lifeChancesRecovered; int32[] damage; int32[] manaConsumed; address[][] mintItems; uint32[][] mintItemsChances; } struct AnswerCustomDataResultMeta { uint16[] pageId; uint8[] heroClass; uint16[] answerId; bytes32[][] dataIndexes; int16[][] dataValues; } struct AnswerBurnRandomItemMeta { uint16[] pageId; uint8[] heroClass; uint16[] answerId; /// @notice 0 - random slot uint8[][] slots; /// @notice typical chances are [0..100] (no decimals here) uint64[][] chances; /// @notice Since SIP-003 the burning is replaced by breaking bu the name is kept as is bool[][] isStopIfBurnt; } struct CustomDataRequirementPacked { bytes32 index; /// @dev min(uint64) + max(uint64) + mandatory(uint8) bytes32 data; } struct CustomDataRequirementRangePacked { bytes32 index; /// @dev min(uint64) + max(uint64) + isHeroData(uint8) bytes32 data; } struct StatsChange { uint32 experience; int32 heal; int32 manaRegen; int32 lifeChancesRecovered; int32 damage; int32 manaConsumed; } struct StoryActionContext { uint stageId; uint iteration; bytes32 answerIdHash; bytes32 answerAttributes; address sender; address heroToken; IController controller; IOracle oracle; uint8 heroClassFromAnswerHash; uint8 biome; uint16 storyId; uint16 storyIdFromAnswerHash; uint16 pageIdFromAnswerHash; uint16 answerNumber; uint16 pageId; uint32 objectId; uint64 dungeonId; uint40 heroLastActionTS; uint80 heroTokenId; IStatController.ChangeableStats heroStats; } // --- WRITE --- function storyAction( address sender, uint64 dungeonId, uint32 objectId, uint stageId, address heroToken, uint heroTokenId, uint8 biome, uint iteration, bytes memory data ) external returns (IGOC.ActionResult memory); // --- READ --- function isStoryAvailableForHero(uint32 objectId, address heroToken, uint heroTokenId) external view returns (bool); function idToStory(uint16 id) external view returns (uint32 objectId); function heroPage(address hero, uint80 heroId, uint16 storyId) external view returns (uint16 pageId); function storyIds(uint32 objectId) external view returns (uint16); function registeredStories(uint32 objectId) external view returns (bool); function skippableStory(uint16 storyId) external view returns (bool); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "./IItemController.sol"; interface ITreasury { function balanceOfToken(address token) external view returns (uint); function sendToDungeon(address dungeon, address token, uint amount) external; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../openzeppelin/EnumerableSet.sol"; interface IUserController { //region ------------------------ Data types enum LootBoxKind { /// @notice small loot box - reward for the daily activity DAILY_0, /// @notice large loot box - reward for the weekly activity (daily activity is passed each ot of the 7 days) WEEKLY_1, END_SLOT } /// @dev registerPassedDungeon assumes that the whole struct takes single slot only, not more struct UserActivity { /// @notice A day for which the daily activity is calculated (see counterXXX below) /// The number of days since 1970-01-01 uint32 epochDay; /// @notice A week for which total count of daily activities were calculated /// The number of weeks since (1970-01-01 Thursday) - 3 days = (1969-12-29 Monday) uint32 epochWeek; /// @notice Count of dungeons passed during the day uint32 counterPassedDungeons; /// @notice Count of PvP during the day uint32 counterPvp; /// @notice Count of daily activities completed per the week uint16 dailyActivities; /// @notice Daily activity is completed and small loot box is added to the earned loot boxes bool dailyLootBoxReceived; /// @notice Weekly activity is completed and large loot box is added to the earned loot boxes bool weeklyLootBoxReceived; } struct EarnedLootBoxes { /// @notice Count of loot boxes earned by daily activity uint32 dailyCounter; /// @notice Count of loot boxes earned by weekly activity uint32 weeklyCounter; } struct LootBoxConfig { address[] mintItems; uint32[] mintItemsChances; uint maxDropItems; } enum UserControllerParam { /// @notice Price of story skipping in game points PRICE_STORY_SKIPPING_1 } /// @custom:storage-location erc7201:user.controller.main struct MainState { /// @notice Amount of sacra required to rename user account uint feeRenaming; /// @dev user EOA => account name mapping(address => string) userAccountName; /// @dev name => user EOA, needs for checking uniq names mapping(string => address) nameToUserAccount; /// @notice user => daily activity info mapping(address => UserActivity) userActivity; /// @notice user => earned loot boxes mapping(address => EarnedLootBoxes) counterLootBoxes; /// @notice Configs of loot boxes of various kinds mapping(LootBoxKind => LootBoxConfig) lootBoxConfig; /// @dev Deprecated, controller is used instead. address userTokensVault; /// @dev user EOA => account avatar mapping(address => string) userAvatar; // @notice Hall of Fame: ngLevel [1...99] => who opened the NG_LEVEL first mapping(uint8 ngLevel => FameHallData) fameHall; /// @notice Points earned for passing dungeons mapping(address user => uint gamePoints) gamePoints; /// @notice List of objects (currently only stories) passed by the given account /// @dev hashes of the stories are as encodePacked("STORY_{ID}") mapping(address user => EnumerableSet.Bytes32Set hashes) passedObjects; /// @notice Values of various params, see {UserControllerParam} mapping(UserControllerParam paramId => uint value) userControllerParams; } struct FameHallData { // ------------ slot 1 /// @notice The hero who opened given the NG_LEVEL first address hero; uint64 heroId; // ------------ slot 2 /// @notice The owner of the hero address heroOwner; /// @notice Timestamp of the moment of the opening given NG_LEVEL uint64 tsOpen; } //endregion ------------------------ Data types /// @notice Register daily activity - a dungeon was passed /// @param user Owner of the hero who has passed the dungeon function registerPassedDungeon(address user) external; /// @notice Register daily activity - PvP was made /// @param user Owner of the hero who has taken participation in the PvP function registerPvP(address user, bool isWinner) external; function registerFameHallHero(address hero, uint heroId, uint8 openedNgLevel) external; function useGamePointsToSkipStore(address user, uint16 storyId) external; function setStoryPassed(address user, uint16 storyId) external; function isStoryPassed(address user, uint16 storyId) external view returns (bool); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../interfaces/IERC20.sol"; /// @notice Common internal utils, shared constants library AppLib { /// @notice Biome owner has the right to receive 1% tax on any income in the biome. Decimals 3. /// The final value of tax is in the range [1..10]%, it depends on total liquidity staked by the guild uint internal constant BIOME_TAX_PERCENT_MIN = 1_000; // 1% /// @notice Max possible value of biome owner tax percent, decimals 3. uint internal constant BIOME_TAX_PERCENT_MAX = 10_000; // 10% /// @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; } }
// 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; } } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../interfaces/IController.sol"; import "../interfaces/IOracle.sol"; import "../interfaces/IStatController.sol"; import "../interfaces/IStoryController.sol"; import "../interfaces/ITreasury.sol"; import "../interfaces/IDungeonFactory.sol"; import "../interfaces/IReinforcementController.sol"; import "../interfaces/IGameToken.sol"; import "../interfaces/IGOC.sol"; import "../interfaces/IItemController.sol"; import "../interfaces/IHeroController.sol"; import "../interfaces/IUserController.sol"; import "../interfaces/IGuildController.sol"; import "../interfaces/IRewardsPool.sol"; import "../interfaces/IPvpController.sol"; import "../interfaces/IItemBoxController.sol"; /// @notice Provide context-struct with all controller addresses and routines for lazy init /// Usage: /// Create an instance of the structure /// cc = ControllerContextLib.init(controller); /// access controller directly /// cc.controller.xxx(); /// access other contracts indirectly /// sc = ControllerContextLib.statController(cc); library ControllerContextLib { //region ----------------- Data types enum CacheIndex { STAT_CONTROLLER_0, STORY_CONTROLLER_1, ORACLE_2, TREASURY_3, DUNGEON_FACTORY_4, GOC_5, REINFORCEMENT_CONTROLLER_6, ITEM_CONTROLLER_7, HERO_CONTROLLER_8, GAME_TOKEN_9, USER_CONTROLLER_10, GUILD_CONTROLLER_11, PVP_CONTROLLER_12, REWARDS_POOL_13, ITEM_BOX_CONTROLLER_14 } uint constant private CACHE_SIZE = 15; struct ControllerContext { /// @notice Direct access to the controller IController controller; /// @notice All lazy-initialized addresses in order of {CacheIndex} address[CACHE_SIZE] cache; } //endregion ----------------- Data types //region ----------------- Initialization and _lazyInit function init(IController controller) internal pure returns (ControllerContext memory cc) { cc.controller = controller; return cc; } function _lazyInit( ControllerContext memory cc, CacheIndex index, function () external view returns(address) getter ) internal view returns (address) { address a = cc.cache[uint(index)]; if (a != address(0)) return a; cc.cache[uint(index)] = getter(); return cc.cache[uint(index)]; } //endregion ----------------- Initialization and _lazyInit //region ----------------- Access with lazy initialization function statController(ControllerContext memory cc) internal view returns (IStatController) { return IStatController(_lazyInit(cc, CacheIndex.STAT_CONTROLLER_0, cc.controller.statController)); } function storyController(ControllerContext memory cc) internal view returns (IStoryController) { return IStoryController(_lazyInit(cc, CacheIndex.STORY_CONTROLLER_1, cc.controller.storyController)); } function oracle(ControllerContext memory cc) internal view returns (IOracle) { return IOracle(_lazyInit(cc, CacheIndex.ORACLE_2, cc.controller.oracle)); } function treasury(ControllerContext memory cc) internal view returns (ITreasury) { return ITreasury(_lazyInit(cc, CacheIndex.TREASURY_3, cc.controller.treasury)); } function dungeonFactory(ControllerContext memory cc) internal view returns (IDungeonFactory) { return IDungeonFactory(_lazyInit(cc, CacheIndex.DUNGEON_FACTORY_4, cc.controller.dungeonFactory)); } function gameObjectController(ControllerContext memory cc) internal view returns (IGOC) { return IGOC(_lazyInit(cc, CacheIndex.GOC_5, cc.controller.gameObjectController)); } function reinforcementController(ControllerContext memory cc) internal view returns (IReinforcementController) { return IReinforcementController(_lazyInit(cc, CacheIndex.REINFORCEMENT_CONTROLLER_6, cc.controller.reinforcementController)); } function itemController(ControllerContext memory cc) internal view returns (IItemController) { return IItemController(_lazyInit(cc, CacheIndex.ITEM_CONTROLLER_7, cc.controller.itemController)); } function heroController(ControllerContext memory cc) internal view returns (IHeroController) { return IHeroController(_lazyInit(cc, CacheIndex.HERO_CONTROLLER_8, cc.controller.heroController)); } function gameToken(ControllerContext memory cc) internal view returns (IGameToken) { return IGameToken(_lazyInit(cc, CacheIndex.GAME_TOKEN_9, cc.controller.gameToken)); } function userController(ControllerContext memory cc) internal view returns (IUserController) { return IUserController(_lazyInit(cc, CacheIndex.USER_CONTROLLER_10, cc.controller.userController)); } function guildController(ControllerContext memory cc) internal view returns (IGuildController) { return IGuildController(_lazyInit(cc, CacheIndex.GUILD_CONTROLLER_11, cc.controller.guildController)); } function pvpController(ControllerContext memory cc) internal view returns (IPvpController) { return IPvpController(_lazyInit(cc, CacheIndex.PVP_CONTROLLER_12, cc.controller.pvpController)); } function rewardsPool(ControllerContext memory cc) internal view returns (IRewardsPool) { return IRewardsPool(_lazyInit(cc, CacheIndex.REWARDS_POOL_13, cc.controller.rewardsPool)); } function itemBoxController(ControllerContext memory cc) internal view returns (IItemBoxController) { return IItemBoxController(_lazyInit(cc, CacheIndex.ITEM_BOX_CONTROLLER_14, cc.controller.itemBoxController)); } //endregion ----------------- Access with lazy initialization }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../interfaces/IERC721.sol"; import "../interfaces/IOracle.sol"; import "../interfaces/ITreasury.sol"; import "../interfaces/IStatController.sol"; import "../interfaces/IDungeonFactory.sol"; import "../interfaces/IGameToken.sol"; import "../interfaces/IMinter.sol"; import "../interfaces/IReinforcementController.sol"; import "../interfaces/IGOC.sol"; import "../interfaces/IAppErrors.sol"; import "../interfaces/IApplicationEvents.sol"; import "../proxy/Controllable.sol"; import "../lib/StringLib.sol"; import "../lib/DungeonLib.sol"; import "../relay/ERC2771Context.sol"; import "../lib/ControllerContextLib.sol"; library DungeonFactoryLib { using EnumerableSet for EnumerableSet.UintSet; using EnumerableMap for EnumerableMap.AddressToUintMap; using PackingLib for bytes32; using PackingLib for uint16; using PackingLib for uint8; using PackingLib for address; using PackingLib for uint32[]; using PackingLib for uint32; using PackingLib for uint64; using PackingLib for int32[]; using PackingLib for int32; //region ------------------------ Data types struct ObjectActionLocal { bool isCompleted; bool needClear; uint32 currentObjectId; uint32 newCurrentObjectId; uint newCurrentStage; } struct LaunchContext { IStatController.ChangeableStats stats; uint16 dungNum; uint treasuryAmount; IHeroController heroController; uint maxOpenedNgLevel; uint heroNgLevel; } struct OpenObjectContext { address dungHero; uint dungHeroId; IGOC goc; uint currentStage; uint32 objectId; } //endregion ------------------------ Data types uint private constant BOSS_1_EVENT = 1280132; uint private constant BOSS_2_EVENT = 2290232; uint private constant BOSS_3_EVENT = 3290332; uint private constant BOSS_4_EVENT = 4290432; //region ------------------------ RESTRICTIONS function onlyDeployer(IController controller) internal view { if (!controller.isDeployer(msg.sender)) revert IAppErrors.ErrorNotDeployer(msg.sender); } function _onlyEoa(bool isEoa) internal pure { if (!isEoa) revert IAppErrors.ErrorOnlyEoa(); } function onlyHeroController(IController controller) internal view { if (controller.heroController() != msg.sender) revert IAppErrors.ErrorNotHeroController(msg.sender); } function onlyItemController(ControllerContextLib.ControllerContext memory cc) internal view { IItemController itemController = ControllerContextLib.itemController(cc); if (address(itemController) != msg.sender) revert IAppErrors.ErrorNotItemController(msg.sender); } function _checkOwnerRegisteredNotPaused( address heroToken, uint heroTokenId, address msgSender, ControllerContextLib.ControllerContext memory cc ) internal view { if (IERC721(heroToken).ownerOf(heroTokenId) != msgSender) revert IAppErrors.ErrorNotOwner(heroToken, heroTokenId); if (ControllerContextLib.heroController(cc).heroClass(heroToken) == 0) revert IAppErrors.ErrorHeroIsNotRegistered(heroToken); if (cc.controller.onPause()) revert IAppErrors.ErrorPaused(); } function _onlySameLevels(uint dungeonNgLevel_, uint8 heroNgLevel_) internal pure { if (dungeonNgLevel_ != heroNgLevel_) revert IAppErrors.ErrorWrongLevel(heroNgLevel_); } //endregion ------------------------ RESTRICTIONS //region ------------------------ VIEWS function _S() internal pure returns (IDungeonFactory.MainState storage s) { return DungeonLib._S(); } function dungeonAttributes(uint16 dungNum) internal view returns (IDungeonFactory.DungeonAttributes memory) { return _S().dungeonAttributes[dungNum]; } function dungeonStatus(uint64 dungeonId) internal view returns ( uint16 dungNum, bool isCompleted, address heroToken, uint heroTokenId, uint32 currentObject, uint8 currentStage, address[] memory treasuryTokens_, uint[] memory treasuryTokensAmounts_, bytes32[] memory treasuryItems, uint8 stages, uint32[] memory uniqObjects ) { IDungeonFactory.DungeonStatus storage dungStatus = _S().dungeonStatuses[dungeonId]; dungNum = dungStatus.dungNum; isCompleted = dungStatus.isCompleted; heroToken = dungStatus.heroToken; heroTokenId = dungStatus.heroTokenId; currentObject = dungStatus.currentObject; currentStage = dungStatus.currentStage; treasuryItems = dungStatus.treasuryItems; stages = dungStatus.stages; uniqObjects = dungStatus.uniqObjects; uint tokensLength = dungStatus.treasuryTokens.length(); treasuryTokens_ = new address[](tokensLength); treasuryTokensAmounts_ = new uint[](tokensLength); for (uint i; i < tokensLength; ++i) { (treasuryTokens_[i], treasuryTokensAmounts_[i]) = dungStatus.treasuryTokens.at(i); } } function dungeonCounter() internal view returns (uint64) { return _S().dungeonCounter; } function maxBiomeCompleted(address heroToken, uint heroTokenId) internal view returns (uint8) { return _S().maxBiomeCompleted[heroToken.packNftId(heroTokenId)]; } function currentDungeon(address heroToken, uint heroTokenId) internal view returns (uint64) { return _S().heroCurrentDungeon[heroToken.packNftId(heroTokenId)]; } function minLevelForTreasury(address token) internal view returns (uint) { return _S().minLevelForTreasury[token]; } function skillSlotsForDurabilityReduction(address heroToken, uint heroTokenId) internal view returns ( uint8[] memory result ) { return _S().skillSlotsForDurabilityReduction[heroToken.packNftId(heroTokenId)].unpackUint8Array(); } /// @return Length of the items in freeDungeons map for the given {biome} function freeDungeonsByLevelLength(uint biome) internal view returns (uint) { return _S().freeDungeons[biome].length(); } /// @param index Index of the free dungeon inside freeDungeons map /// @return dungeonId function freeDungeonsByLevel(uint index, uint biome) internal view returns (uint64) { return uint64(_S().freeDungeons[biome].at(index)); } function getDungeonTreasuryAmount(IController controller, address token, uint heroVirtualLevel, uint biome, uint heroNgLevel) external view returns ( uint totalAmount, uint amountForDungeon, uint mintAmount ) { totalAmount = ITreasury(controller.treasury()).balanceOfToken(token); mintAmount = IMinter(IGameToken(controller.gameToken()).minter()).amountForDungeon(biome, heroVirtualLevel); IHeroController heroController = IHeroController(controller.heroController()); amountForDungeon = DungeonLib.dungeonTreasuryReward( token, uint(_S().maxBiome), totalAmount, uint8(heroVirtualLevel), uint8(biome), heroController.maxOpenedNgLevel(), heroNgLevel ); } /// @notice Check if biome boss completed by the hero. /// @dev isBiomeBossCompleted would be more correct title, but isBiomeBoss is already used function isBiomeBoss(IController controller, address heroToken, uint heroTokenId) internal view returns (bool) { uint8 heroBiome = IHeroController(controller.heroController()).heroBiome(heroToken, heroTokenId); return _S().bossCompleted[heroToken.packMapObject(uint64(heroTokenId), heroBiome)]; } function maxAvailableBiome() internal view returns (uint8) { return _S().maxBiome; } function dungeonNgLevel(uint64 dungeonId) internal view returns (uint) { return _S().dungeonNgLevel[dungeonId]; } //endregion ------------------------ VIEWS //region ------------------------ ACTIONS function launch( bool isEoa, IController controller, address msgSender, address heroToken, uint heroTokenId, address treasuryToken ) external returns (uint64 dungeonId) { _onlyEoa(isEoa); return _launch(controller, msgSender, heroToken, heroTokenId, treasuryToken); } function launchForNewHero( IController controller, address msgSender, address heroToken, uint heroTokenId ) external returns (uint64 dungeonId) { onlyHeroController(controller); return _launch(controller, msgSender, heroToken, heroTokenId, controller.gameToken()); } /// @notice Create new dungeon and enter to it. Treasury reward is sent by treasury to the dungeon. function _launch( IController controller, address msgSender, address heroToken, uint heroTokenId, address treasuryToken ) internal returns (uint64 dungeonId) { IDungeonFactory.MainState storage s = _S(); ControllerContextLib.ControllerContext memory cc = ControllerContextLib.init(controller); LaunchContext memory ctx; ctx.heroController = ControllerContextLib.heroController(cc); ctx.maxOpenedNgLevel = ctx.heroController.maxOpenedNgLevel(); ctx.heroNgLevel = ctx.heroController.getHeroInfo(heroToken, heroTokenId).ngLevel; // check part of restrictions; other part is checked inside DungeonLib._enter _checkOwnerRegisteredNotPaused(heroToken, heroTokenId, msgSender, cc); if (!controller.validTreasuryTokens(treasuryToken)) revert IAppErrors.ErrorNotValidTreasureToken(treasuryToken); // select a logic for new dungeon ctx.stats = ControllerContextLib.statController(cc).heroStats(heroToken, heroTokenId); ctx.dungNum = DungeonLib.getDungeonLogic( s, cc, uint8(ctx.stats.level), heroToken, heroTokenId, ControllerContextLib.oracle(cc).getRandomNumber(1e18, 0) ); // register new dungeon dungeonId = s.dungeonCounter + 1; s.dungeonCounter = dungeonId; IDungeonFactory.DungeonAttributes storage dungAttr = s.dungeonAttributes[ctx.dungNum]; IDungeonFactory.DungeonStatus storage dungStatus = s.dungeonStatuses[dungeonId]; if (dungStatus.isCompleted) revert IAppErrors.ErrorDungeonCompleted(); dungStatus.dungeonId = dungeonId; dungStatus.dungNum = ctx.dungNum; dungStatus.stages = dungAttr.stages; dungStatus.uniqObjects = dungAttr.uniqObjects; s.dungeonNgLevel[dungeonId] = ctx.heroNgLevel; emit IApplicationEvents.DungeonRegistered(ctx.dungNum, dungeonId); // enter to the dungeon DungeonLib._enter(cc, dungStatus, dungAttr, ctx.dungNum, dungeonId, heroToken, heroTokenId); // when entered, open the first object for reduce txs _openObject(cc, msgSender, dungeonId); // send treasury to the dungeon // if we have reduced drop then do not mint token at all if (StatLib.mintDropChanceDelta(ctx.stats.experience, uint(ctx.stats.level), dungAttr.biome) == 0) { ctx.treasuryAmount = DungeonLib.dungeonTreasuryReward( treasuryToken, uint(_S().maxBiome), ControllerContextLib.treasury(cc).balanceOfToken(treasuryToken), StatLib.getVirtualLevel( ctx.stats.experience, StatLib.getVirtualLevel(ctx.stats.experience, uint(ctx.stats.level), true), true ), dungAttr.biome, ctx.maxOpenedNgLevel, ctx.heroNgLevel ); } if (ctx.treasuryAmount != 0) { ControllerContextLib.treasury(cc).sendToDungeon(address(this), treasuryToken, ctx.treasuryAmount); DungeonLib._registerTreasuryToken(treasuryToken, s.dungeonStatuses[dungeonId].treasuryTokens, ctx.treasuryAmount); } emit IApplicationEvents.DungeonLaunched(ctx.dungNum, dungeonId, heroToken, heroTokenId, treasuryToken, ctx.treasuryAmount); } /// @notice Set boss completed for the given hero and given biome. /// @dev Set custom data for the hero: BOSS_COMPLETED_ = 1 function setBossCompleted(IController controller, uint32 objectId, address heroToken, uint heroTokenId, uint8 heroBiome) external { if (controller.gameObjectController() != msg.sender) revert IAppErrors.ErrorNotGoc(); IDungeonFactory.MainState storage s = _S(); bytes32 packMapObject = heroToken.packMapObject(uint64(heroTokenId), heroBiome); if (!s.bossCompleted[packMapObject]) { s.bossCompleted[packMapObject] = true; } bytes32 packedHero = heroToken.packNftId(heroTokenId); if (s.maxBiomeCompleted[packedHero] < heroBiome) { s.maxBiomeCompleted[packedHero] = heroBiome; } bytes32 index = _getBossCompletedIndex(heroBiome); IStatController(controller.statController()).setHeroCustomData(heroToken, heroTokenId, index, 1); IHeroController(controller.heroController()).registerKilledBoss(heroToken, heroTokenId, objectId); emit IApplicationEvents.BossCompleted(objectId, heroBiome, heroToken, heroTokenId); } //endregion ------------------------ ACTIONS //region ------------------------ DUNGEON LOGIC - GOV ACTIONS /// @notice Register ordinal or specific dungeon /// @dev can be called for exist dungeon - will rewrite dungeon data /// @param dungNum Dungeon logic id /// @param biome Assume biome > 0 /// @param isSpecific The dungeon is specific, so it shouldn't be registered in dungeonsLogicByBiome function registerDungeonLogic( IController controller, uint16 dungNum, uint8 biome, IDungeonFactory.DungeonGenerateInfo memory genInfo, uint8 specReqBiome, uint8 specReqHeroClass, bool isSpecific ) internal { onlyDeployer(controller); IDungeonFactory.MainState storage s = _S(); uint len = genInfo.objChancesByStages.length; if (len != genInfo.objTypesByStages.length || len != genInfo.uniqObjects.length) revert IAppErrors.ErrorNotStages(); for (uint i; i < len; ++i) { if (genInfo.objChancesByStages[i].length != genInfo.objTypesByStages[i].length) revert IAppErrors.ErrorNotChances(); } IDungeonFactory.DungeonAttributes storage info = s.dungeonAttributes[dungNum]; if (biome > s.maxBiome) { s.maxBiome = biome; } info.stages = uint8(len); // info.stages can be increased later by chamber story info.biome = biome; info.uniqObjects = genInfo.uniqObjects; info.minMaxLevel = DungeonLib._toUint8PackedArray(genInfo.minLevel, genInfo.maxLevel); info.requiredCustomDataIndex = genInfo.requiredCustomDataIndex; bytes32[] storage requiredCustomDataValue = info.requiredCustomDataValue; for (uint i; i < genInfo.requiredCustomDataMinValue.length; ++i) { requiredCustomDataValue.push( PackingLib.packCustomDataRequirements( genInfo.requiredCustomDataMinValue[i], genInfo.requiredCustomDataMaxValue[i], genInfo.requiredCustomDataIsHero[i] ) ); } for (uint i; i < len; ++i) { info.info.objTypesByStages.push(PackingLib.packUint8Array(genInfo.objTypesByStages[i])); info.info.objChancesByStages.push(genInfo.objChancesByStages[i]); } if (isSpecific) { bytes32 packedId = DungeonLib._toUint8PackedArray(specReqBiome, specReqHeroClass); if (s.dungeonSpecific[packedId] != 0) revert IAppErrors.DungeonAlreadySpecific(dungNum); s.dungeonSpecific[packedId] = dungNum; if (s.allSpecificDungeons.contains(dungNum)) revert IAppErrors.DungeonAlreadySpecific2(dungNum); s.allSpecificDungeons.add(dungNum); emit IApplicationEvents.DungeonSpecificLogicRegistered(dungNum, specReqBiome, specReqHeroClass); } else { s.dungeonsLogicByBiome[info.biome].add(dungNum); } emit IApplicationEvents.DungeonLogicRegistered(dungNum, genInfo); } /// @dev Remove the dungeon logic (both ordinal and specific logics are supported) /// @param dungNum Dungeon logic id function removeDungeonLogic(IController controller, uint16 dungNum, uint8 specReqBiome, uint8 specReqHeroClass) internal { onlyDeployer(controller); IDungeonFactory.MainState storage s = _S(); uint8 biome = s.dungeonAttributes[dungNum].biome; delete s.dungeonAttributes[dungNum]; if (s.dungeonsLogicByBiome[biome].contains(dungNum)) { s.dungeonsLogicByBiome[biome].remove(dungNum); emit IApplicationEvents.DungeonLogicRemoved(dungNum); } if (s.allSpecificDungeons.contains(dungNum)) { bytes32 packedId = DungeonLib._toUint8PackedArray(specReqBiome, specReqHeroClass); if (s.dungeonSpecific[packedId] != dungNum) revert IAppErrors.WrongSpecificDungeon(); delete s.dungeonSpecific[packedId]; s.allSpecificDungeons.remove(dungNum); emit IApplicationEvents.DungeonSpecificLogicRemoved(dungNum, specReqBiome, specReqHeroClass); } } /// @dev Set eligible hero level for treasury tokens function setMinLevelForTreasury(IController controller, address token, uint heroLevel) internal { onlyDeployer(controller); if (heroLevel < DungeonLib.MIN_LEVEL_FOR_TREASURY_DEFAULT) { revert IAppErrors.ErrorLevelTooLow(heroLevel); } _S().minLevelForTreasury[token] = heroLevel; emit IApplicationEvents.MinLevelForTreasuryChanged(token, heroLevel); } /// @dev Governance can drop hero from dungeon in emergency case function emergencyExit(IController controller, uint64 dungId) internal { onlyDeployer(controller); DungeonLib.emergencyExit(controller, dungId); } //endregion ------------------------ DUNGEON LOGIC - GOV ACTIONS //region ------------------------ DUNGEON LOGIC - USER ACTIONS /// @notice Enter to the exist dungeon function enter(bool isEoa, IController controller, address msgSender, uint64 dungId, address heroToken, uint heroTokenId) external { ControllerContextLib.ControllerContext memory cc = ControllerContextLib.init(controller); IDungeonFactory.DungeonStatus storage dungStatus = _S().dungeonStatuses[dungId]; // check part of restrictions; other part is checked inside DungeonLib._enter _onlyEoa(isEoa); _checkOwnerRegisteredNotPaused(heroToken, heroTokenId, msgSender, cc); if (dungStatus.isCompleted) revert IAppErrors.ErrorDungeonCompleted(); _onlySameLevels(_S().dungeonNgLevel[dungId], ControllerContextLib.heroController(cc).getHeroInfo(heroToken, heroTokenId).ngLevel); // enter to the dungeon uint16 dungNum = dungStatus.dungNum; DungeonLib._enter(cc, dungStatus, _S().dungeonAttributes[dungNum], dungNum, dungId, heroToken, heroTokenId); // when entered, open the first object for reduce txs _openObject(cc, msgSender, dungId); } function openObject(bool isEoa, IController controller, address msgSender, uint64 dungId) internal { _onlyEoa(isEoa); ControllerContextLib.ControllerContext memory cc = ControllerContextLib.init(controller); _openObject(cc, msgSender, dungId); } /// @notice Set new current object for the dungeon function _openObject(ControllerContextLib.ControllerContext memory cc, address msgSender, uint64 dungId) internal { IDungeonFactory.MainState storage s = _S(); IDungeonFactory.DungeonStatus storage dungStatus = s.dungeonStatuses[dungId]; IDungeonFactory.DungeonAttributes storage dungAttributes = s.dungeonAttributes[dungStatus.dungNum]; OpenObjectContext memory ctx; ctx.goc = ControllerContextLib.gameObjectController(cc); // check restrictions if (dungStatus.currentObject != 0) revert IAppErrors.ErrorNotReady(); (ctx.dungHero, ctx.dungHeroId) = _checkCurrentHero(dungStatus, msgSender, cc); // select new object and set it as current object in the dungeon ctx.currentStage = dungStatus.currentStage; ctx.objectId = _generateObject(dungAttributes, dungStatus, ctx.currentStage, ctx.goc, ctx.dungHero, ctx.dungHeroId, ControllerContextLib.statController(cc)); if (ctx.objectId == 0) revert IAppErrors.ErrorNotObject1(); dungStatus.currentObject = ctx.objectId; // generate some info for UI uint iteration = ctx.goc.open(ctx.dungHero, ctx.dungHeroId, ctx.objectId); emit IApplicationEvents.ObjectOpened(dungId, ctx.dungHero, ctx.dungHeroId, ctx.objectId, iteration, ctx.currentStage); } /// @notice Do action and handle results /// @param data AttackInfo struct encoded using abi.encode function objectAction(bool isEoa, IController controller, address msgSender, uint64 dungId, bytes memory data) internal { IDungeonFactory.MainState storage s = _S(); ControllerContextLib.ControllerContext memory cc = ControllerContextLib.init(controller); ObjectActionLocal memory v; IDungeonFactory.DungeonStatus storage dungStatus = s.dungeonStatuses[dungId]; IDungeonFactory.DungeonAttributes storage dungAttributes = s.dungeonAttributes[dungStatus.dungNum]; // check restrictions, some restrictions are checked inside objectAction _onlyEoa(isEoa); _checkCurrentHero(dungStatus, msgSender, cc); v.currentObjectId = dungStatus.currentObject; (v.isCompleted, v.newCurrentStage, v.newCurrentObjectId, v.needClear) = DungeonLib.objectAction( dungStatus, dungAttributes, dungId, msgSender, data, controller, // we pass controller, not cc, because objectAction is external v.currentObjectId ); if (v.isCompleted) { dungStatus.isCompleted = true; } if (v.newCurrentStage != 0) { dungStatus.currentStage = uint8(v.newCurrentStage); } if (v.newCurrentObjectId != v.currentObjectId) { dungStatus.currentObject = v.newCurrentObjectId; } if (v.needClear) { _clear(dungStatus, dungAttributes.biome, dungId); } // if dungeon is not ended and current object is empty we can open next object for reduce users transactions if (!v.isCompleted && dungStatus.currentObject == 0 && !v.needClear) { _openObject(cc, msgSender, dungId); } } /// @notice Exit from completed dungeon function exit(bool isEoa, IController controller, address msgSender, uint64 dungId, bool claim) internal { _onlyEoa(isEoa); DungeonLib.exitDungeon(controller, dungId, claim, msgSender); } /// @notice Hero exists current dungeon forcibly same as when dying but without loosing life chance function exitSpecial(IController controller, address hero, uint heroId, address msgSender, DungeonLib.DungeonExitMode exitMode) external { ControllerContextLib.ControllerContext memory cc = ControllerContextLib.init(controller); if (exitMode == DungeonLib.DungeonExitMode.FORCED_EXIT_1) { onlyItemController(cc); } else { // DungeonLib.DungeonExitMode.HERO_SUICIDE_2 // it's is checked below that msgSender is the hero owner } IDungeonFactory.MainState storage s = _S(); uint64 dungId = currentDungeon(hero, heroId); IDungeonFactory.DungeonStatus storage dungStatus = s.dungeonStatuses[dungId]; _checkCurrentHero(dungStatus, msgSender, cc); IDungeonFactory.DungeonAttributes storage dungAttributes = s.dungeonAttributes[dungStatus.dungNum]; // Extract hero from the current dungeon, clear hero state. DungeonLib.exitSpecial(hero, heroId, dungStatus, dungAttributes, cc, exitMode); // clear dungeon state _clear(dungStatus, dungAttributes.biome, dungId); } function reborn(IController controller, address hero, uint heroId) external { onlyHeroController(controller); uint8 maxBiome = _S().maxBiome; for (uint8 biome; biome <= maxBiome; ++biome) { delete _S().bossCompleted[PackingLib.packMapObject(hero, uint64(heroId), biome)]; } delete _S().maxBiomeCompleted[PackingLib.packNftId(hero, heroId)]; } //endregion ------------------------ DUNGEON LOGIC - USER ACTIONS //region ------------------------ DUNGEON LOGIC - INTERNAL LOGIC /// @notice Generate object for the current stage /// @return objectId Either uniqObj or randomly generated object if uniqObj is not specified for the stage function _generateObject( IDungeonFactory.DungeonAttributes storage dungAttributes, IDungeonFactory.DungeonStatus storage dungStatus, uint currentStage, IGOC goc, address heroToken, uint heroTokenId, IStatController sc ) internal returns (uint32 objectId) { if (currentStage >= dungStatus.stages) revert IAppErrors.ErrorWrongStage(currentStage); //////////// if we have specific dungeon (rewrite from stories for ex., it has highest priority objectId = dungStatus.uniqObjects[currentStage]; if (objectId != 0) { return objectId; } /////////// if a hero play long enough on this biome he should meet a boss IStatController.ChangeableStats memory stats = sc.heroStats(heroToken, heroTokenId); if ( StatLib.mintDropChanceDelta(stats.experience, stats.level, dungAttributes.biome) != 0 && !_S().bossCompleted[heroToken.packMapObject(uint64(heroTokenId), dungAttributes.biome)] && stats.level >= dungAttributes.biome * 5 ) { if (dungAttributes.biome == 1) { return uint32(BOSS_1_EVENT); } if (dungAttributes.biome == 2) { return uint32(BOSS_2_EVENT); } if (dungAttributes.biome == 3) { return uint32(BOSS_3_EVENT); } if (dungAttributes.biome == 4) { return uint32(BOSS_4_EVENT); } } ////////// normal search an object IDungeonFactory.ObjectGenerateInfo memory info = dungAttributes.info; return goc.getRandomObject( DungeonLib._toUint8ArrayWithoutZeroes(info.objTypesByStages[currentStage]), info.objChancesByStages[currentStage], dungAttributes.biome, heroToken, heroTokenId ); } /// @notice Clear hero info in dungeon status, add dungeon to the list of free dungeons function _clear(IDungeonFactory.DungeonStatus storage dungStatus, uint8 biome, uint64 dungId) internal { delete dungStatus.heroToken; delete dungStatus.heroTokenId; delete dungStatus.currentObject; delete dungStatus.currentStage; _addFreeDungeon(biome, dungId); emit IApplicationEvents.Clear(dungId); } /// @notice Check: hero is registered, not dead, in the dungeon, sender is the owner, the dungeon is not completed, /// controller is not paused /// @return heroToken Token of the hero who is in the dungeon /// @return heroTokenId Token ID of the hero who is in the dungeon function _checkCurrentHero( IDungeonFactory.DungeonStatus storage dungStatus, address msgSender, ControllerContextLib.ControllerContext memory cc ) internal view returns (address heroToken, uint heroTokenId) { heroToken = dungStatus.heroToken; heroTokenId = dungStatus.heroTokenId; if (dungStatus.isCompleted) revert IAppErrors.ErrorDungeonCompleted(); _checkOwnerRegisteredNotPaused(heroToken, heroTokenId, msgSender, cc); if (!ControllerContextLib.statController(cc).isHeroAlive(heroToken, heroTokenId)) revert IAppErrors.ErrorHeroIsDead(heroToken, heroTokenId); if (currentDungeon(heroToken, heroTokenId) != dungStatus.dungeonId) revert IAppErrors.ErrorHeroNotInDungeon(); } /// @notice Add the {dungeonId} to the list of free dungeons (available to pass) of the given {biome} function _addFreeDungeon(uint8 biome, uint64 dungeonId) internal { if (!_S().freeDungeons[biome].add(dungeonId)) revert IAppErrors.ErrorDungeonIsFreeAlready(); emit IApplicationEvents.FreeDungeonAdded(biome, dungeonId); } //endregion ------------------------ DUNGEON LOGIC - INTERNAL LOGIC //region ------------------------ Utils /// @dev We need separate utility function for tests function _getBossCompletedIndex(uint8 heroBiome) internal pure returns (bytes32) { return bytes32(abi.encodePacked("BOSS_COMPLETED_", StringLib._toString(heroBiome))); } //endregion ------------------------ Utils }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../interfaces/IAppErrors.sol"; import "../interfaces/IApplicationEvents.sol"; import "../interfaces/IController.sol"; import "../interfaces/IDungeonFactory.sol"; import "../interfaces/IERC20.sol"; import "../interfaces/IERC721.sol"; import "../interfaces/IFightCalculator.sol"; import "../interfaces/IGOC.sol"; import "../interfaces/IGameToken.sol"; import "../interfaces/IHeroController.sol"; import "../interfaces/IItemController.sol"; import "../interfaces/IMinter.sol"; import "../interfaces/IReinforcementController.sol"; import "../interfaces/IStatController.sol"; import "../interfaces/IStoryController.sol"; import "../interfaces/IUserController.sol"; import "../openzeppelin/EnumerableMap.sol"; import "./AppLib.sol"; import "./CalcLib.sol"; import "./ControllerContextLib.sol"; import "./PackingLib.sol"; import "./RewardsPoolLib.sol"; import "./StatControllerLib.sol"; import "./StatLib.sol"; import "./AppLib.sol"; library DungeonLib { using EnumerableMap for EnumerableMap.AddressToUintMap; using EnumerableSet for EnumerableSet.UintSet; using CalcLib for int32; using PackingLib for bytes32; using PackingLib for uint16; using PackingLib for uint8; using PackingLib for address; using PackingLib for uint8[]; using PackingLib for uint32[]; using PackingLib for uint32; using PackingLib for uint64; using PackingLib for int32[]; using PackingLib for int32; /// @dev keccak256(abi.encode(uint256(keccak256("dungeon.factory.main")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant DUNGEON_FACTORY_STORAGE_LOCATION = 0xae5971282b317bbed599861775fe0712755bb3b2f655bfe8fb14280d8429f600; /// @notice Treasure reward is available starting from level 5. We need some initial gap as protection against bots uint public constant MIN_LEVEL_FOR_TREASURY_DEFAULT = 5; /// @notice Max possible default minLevelForTreasury (= 95) uint internal constant MAX_NIM_LEVEL_FOR_TREASURE = StatLib.MAX_LEVEL - StatLib.BIOME_LEVEL_STEP + 1; //region ------------------------ Data types struct ObjectActionInternalData { address msgSender; address heroToken; IStatController statController; bytes data; uint stages; uint biome; uint heroTokenId; uint64 dungId; uint32 objectId; uint8 currentStage; bool isBattleObj; IGOC.ActionResult result; IStatController.ChangeableStats stats; } /// @notice Lazy initialization data for _claimAll struct ClaimContext { address helpHeroToken; address heroPayToken; address msgSender; address guildBank; address[] tokens; /// @notice list of items sent to ItemBox address[] items; uint8 biome; uint8 sandboxMode; uint64 dungId; /// @dev Limited by ReinforcementController._TO_HELPER_RATIO_MAX uint toHelperRatio; uint itemLength; uint tokenLength; uint helpHeroId; /// @notice Percent of tax that is taken if favor of biome owner, decimals 3 uint taxPercent; uint guildId; uint[] amounts; /// @notice list of items sent to ItemBox uint[] itemIds; /// @notice Actual count of items sent to the ItemBox uint countItems; } /// @notice Various cases of using _onHeroKilled enum DungeonExitMode { ACTION_ENDED_0, /// @notice Exit using special item. Life => 1, mana => 0, keep items FORCED_EXIT_1, /// @notice SCR-1446: Exit with suicide. Loose life chance, restore life and mana, keep items HERO_SUICIDE_2 } //endregion ------------------------ Data types //region ------------------------ Common function _S() internal pure returns (IDungeonFactory.MainState storage s) { assembly { s.slot := DUNGEON_FACTORY_STORAGE_LOCATION } return s; } /// @notice Calculate amount of treasure reward that a hero can count on /// @param token Treasury token /// @param maxAvailableBiome Max deployed biome /// @param treasuryBalance Total treasury of the dungeon /// @param lvlForMint Current level of the hero /// @param dungeonBiome Biome to which the dungeon belongs /// @param maxOpenedNgLevel Max NG_LEVEL reached by any user /// @param heroNgLevel Current NG_LEVEL of the user function dungeonTreasuryReward( address token, uint maxAvailableBiome, uint treasuryBalance, uint lvlForMint, uint dungeonBiome, uint maxOpenedNgLevel, uint heroNgLevel ) internal view returns (uint) { if (dungeonBiome < maxAvailableBiome || heroNgLevel < maxOpenedNgLevel) { return 0; } uint customMinLevel = _S().minLevelForTreasury[token]; if (customMinLevel != 0 && lvlForMint < customMinLevel) { return 0; } if (lvlForMint > StatLib.MAX_LEVEL) revert IAppErrors.ErrorWrongLevel(lvlForMint); if (dungeonBiome > StatLib.MAX_POSSIBLE_BIOME) revert IAppErrors.ErrorIncorrectBiome(dungeonBiome); uint biomeLevel = dungeonBiome * StatLib.BIOME_LEVEL_STEP; // CalcLib.log2((StatLib.MAX_LEVEL + 1) * 1e18); uint maxMultiplier = 6643856189774724682; uint multiplier = (maxMultiplier - CalcLib.log2((StatLib.MAX_LEVEL - biomeLevel + 1) * 1e18)) / 100; if (multiplier >= 1e18) revert IAppErrors.ErrorWrongMultiplier(multiplier); uint base = treasuryBalance * multiplier / 1e18; if (biomeLevel < lvlForMint) { // reduce base on biome difference base = base / 2 ** (lvlForMint - biomeLevel + 10); } return base; } //endregion ------------------------ Common //region ------------------------ Restrictions function onlyOwner(address hero, uint heroId, address msgSender) internal view { if (IERC721(hero).ownerOf(heroId) != msgSender) revert IAppErrors.ErrorNotOwner(hero, heroId); } //region ------------------------ Restrictions //region ------------------------ Main logic /// @notice Make an action with object, update hero params according results function objectAction( IDungeonFactory.DungeonStatus storage dungStatus, IDungeonFactory.DungeonAttributes storage dungAttributes, uint64 dungId, address msgSender, bytes memory data, IController controller, uint32 currentObject_ ) external returns ( bool isCompleted, uint currentStage, uint32 currentObject, bool clear ) { IGOC.ActionResult memory a; ControllerContextLib.ControllerContext memory cc = ControllerContextLib.init(controller); return _objectAction( ObjectActionInternalData({ dungId: dungId, msgSender: msgSender, data: data, heroToken: dungStatus.heroToken, heroTokenId: dungStatus.heroTokenId, objectId: currentObject_, currentStage: dungStatus.currentStage, biome: uint(dungAttributes.biome), statController: ControllerContextLib.statController(cc), result: a, stats: IStatController.ChangeableStats(0, 0, 0, 0, 0), stages: uint(dungStatus.stages), isBattleObj: false }), dungStatus, dungAttributes, cc ); } /// @notice Make an action with object, update hero params according results /// @param c Context /// @return isCompleted The dungeon is completed (there is no new stage to pass) /// @return newStage Next stage (0 if the dungeon is completed) /// @return currentObject Id of the current object. It's always 0 if new stage is selected (new object is not opened) /// @return clear True if dungStatus of the hero should be cleared and the dungeon should be added to free dungeon list function _objectAction( ObjectActionInternalData memory c, IDungeonFactory.DungeonStatus storage dungStatus, IDungeonFactory.DungeonAttributes storage dungAttributes, ControllerContextLib.ControllerContext memory cc ) internal returns ( bool isCompleted, uint newStage, uint32 currentObject, bool clear ) { // isCompleted = false; currentObject = c.objectId; // newStage = 0; // check restrictions, most of them are checked by the caller if (c.objectId == 0) revert IAppErrors.ErrorNotObject2(); c.isBattleObj = ControllerContextLib.gameObjectController(cc).isBattleObject(c.objectId); c.result = ControllerContextLib.gameObjectController(cc).action( c.msgSender, c.dungId, c.objectId, c.heroToken, c.heroTokenId, c.currentStage, c.data ); if (c.isBattleObj) { _markSkillSlotsForDurabilityReduction( _S(), c.statController, ControllerContextLib.itemController(cc), c.data, c.heroToken, c.heroTokenId ); } c.stats = c.statController.heroStats(c.heroToken, c.heroTokenId); if (c.stats.mana < c.result.manaConsumed.toUint()) { revert IAppErrors.ErrorNotEnoughMana(c.stats.mana, c.result.manaConsumed.toUint()); } if (c.result.kill || c.stats.life <= c.result.damage.toUint()) { c.result.kill = true; _exitActionEnd(c, dungStatus, dungAttributes, cc); // scb-994: increment death count counter uint deathCounter = c.statController.heroCustomData(c.heroToken, c.heroTokenId, StatControllerLib.DEATH_COUNT_HASH); c.statController.setHeroCustomData(c.heroToken, c.heroTokenId, StatControllerLib.DEATH_COUNT_HASH, deathCounter + 1); clear = true; } else { _increaseChangeableStats(c.statController, c.heroToken, c.heroTokenId, c.result); _decreaseChangeableStats(c.statController, c.heroToken, c.heroTokenId, c.result); _mintItems(c, cc, dungStatus.treasuryItems); if (c.result.completed) { _afterObjCompleteForSurvivedHero(c.heroToken, c.heroTokenId, c.biome, c.isBattleObj, cc, address(c.statController)); (isCompleted, newStage, currentObject) = _nextRoomOrComplete(c, cc, dungStatus, c.stages, dungStatus.treasuryTokens); } // clear = false; } emit IApplicationEvents.ObjectAction(c.dungId, c.result, c.currentStage, c.heroToken, c.heroTokenId, newStage); return (isCompleted, newStage, currentObject, clear); } /// @notice Hero exists current dungeon forcibly /// @dev Dungeon state is cleared outside function exitSpecial( address hero, uint heroId, IDungeonFactory.DungeonStatus storage dungStatus, IDungeonFactory.DungeonAttributes storage dungAttributes, ControllerContextLib.ControllerContext memory cc, DungeonLib.DungeonExitMode exitMode ) internal { IStatController statController = ControllerContextLib.statController(cc); _onExitDungeon( hero, heroId, statController, statController.heroStats(hero, heroId), dungStatus, dungAttributes, cc, exitMode, 0, // not used 0, // not used false // not used ); } //endregion ------------------------ Main logic //region ------------------------ Main logic - auxiliary functions function _exitActionEnd( ObjectActionInternalData memory c, IDungeonFactory.DungeonStatus storage dungStatus, IDungeonFactory.DungeonAttributes storage dungAttributes, ControllerContextLib.ControllerContext memory cc ) internal { _onExitDungeon( c.heroToken, c.heroTokenId, c.statController, c.stats, dungStatus, dungAttributes, cc, DungeonExitMode.ACTION_ENDED_0, c.dungId, c.biome, c.isBattleObj ); } function _onExitDungeon( address hero, uint heroId, IStatController statController, IStatController.ChangeableStats memory stats, IDungeonFactory.DungeonStatus storage dungStatus, IDungeonFactory.DungeonAttributes storage dungAttributes, ControllerContextLib.ControllerContext memory cc, DungeonExitMode mode, uint64 dungId, uint biome, bool isBattleObj ) internal { _changeCurrentDungeon(_S(), hero, heroId, 0); IHeroController hc = ControllerContextLib.heroController(cc); hc.releaseReinforcement(hero, heroId); // in case of death we need to remove rewrote objects and reset initial stages _resetUniqueObjects(dungStatus, dungAttributes); bool heroDied; if (mode == DungeonExitMode.ACTION_ENDED_0) { // no need to release if we completed the dungeon - we will never back on the same _releaseSkillSlotsForDurabilityReduction(_S(), hero, heroId); heroDied = stats.lifeChances <= 1; if (heroDied) { // it was the last life chance - kill the hero _killHero(hc, dungId, hero, heroId, dungStatus.treasuryItems); } else { _afterObjCompleteForSurvivedHero(hero, heroId, biome, isBattleObj, cc, address(0) // don't call clearTemporallyAttributes, it will be called below anyway ); _reduceLifeChances(statController, hero, heroId, stats.life, stats.mana); } } else if (mode == DungeonExitMode.FORCED_EXIT_1) { // life => 1, mana => 0, lifeChance is NOT changed, hero is NOT burnt, items are kept equipped. hc.resetLifeAndMana(hero, heroId); } else if (mode == DungeonExitMode.HERO_SUICIDE_2) { if (stats.lifeChances <= 1) revert IAppErrors.LastLifeChance(); // equipped items are NOT taken off, life chance reduced, life and mana are restored to default values // death count counter is not incremented in this case _reduceLifeChances(statController, hero, heroId, stats.life, stats.mana); } if (!heroDied) { // scb-1000: soft death resets used consumables statController.clearUsedConsumables(hero, heroId); // also soft death reset all buffs statController.clearTemporallyAttributes(hero, heroId); } } /// @notice If hero has dead in the dungeon, it's necessary to restore initial set of unique objects, /// in other words, all changes introduces by {_nextRoomOrComplete} should be thrown away. function _resetUniqueObjects( IDungeonFactory.DungeonStatus storage dungStatus, IDungeonFactory.DungeonAttributes storage dungAttributes ) internal { dungStatus.stages = dungAttributes.stages; delete dungStatus.uniqObjects; uint32[] memory uniqObjects = dungAttributes.uniqObjects; for (uint i; i < uniqObjects.length; ++i) { dungStatus.uniqObjects.push(uniqObjects[i]); } } /// @notice Kill the hero, take hero's tokens and items function _killHero( IHeroController heroController, uint64 dungId, address heroToken, uint heroTokenId, bytes32[] storage treasuryItems ) internal { (bytes32[] memory drop) = heroController.kill(heroToken, heroTokenId); _putHeroItemToDungeon(dungId, drop, treasuryItems); } /// @notice All hero's items are taken by the dungeon function _putHeroItemToDungeon(uint64 dungId, bytes32[] memory drop, bytes32[] storage treasuryItems) internal { uint dropLength = drop.length; for (uint i; i < dropLength; ++i) { treasuryItems.push(drop[i]); (address itemAdr, uint itemId) = drop[i].unpackNftId(); emit IApplicationEvents.AddTreasuryItem(dungId, itemAdr, itemId); } } /// @notice If battle object: reduce equipped items durability and clear temporally attributes /// @dev Not necessary to call if a hero is dead /// @param statController Pass 0 to avoid calling of clearTemporallyAttributes function _afterObjCompleteForSurvivedHero( address hero, uint heroId, uint biome, bool isBattleObj, ControllerContextLib.ControllerContext memory cc, address statController ) internal { if (isBattleObj) { // reduce equipped items durability ControllerContextLib.itemController(cc).reduceDurability(hero, heroId, uint8(biome), false); if (statController != address(0)) { // clear temporally attributes IStatController(statController).clearTemporallyAttributes(hero, heroId); } } } /// @notice Check if the dungeon is completed, calculate index of the next stage. /// @dev Take {rewriteNextObject} from the results of the previous action and set next objects for the dungeon /// @param curStages Current value of dungStatus.stages /// @return isCompleted The dungeon is completed /// @return currentStage Next stage (0 if the dungeon is completed) /// @return currentObj Always 0. It means, that new current object should be opened. function _nextRoomOrComplete( ObjectActionInternalData memory context, ControllerContextLib.ControllerContext memory cc, IDungeonFactory.DungeonStatus storage dungStatus, uint curStages, EnumerableMap.AddressToUintMap storage treasuryTokens ) internal returns ( bool isCompleted, uint currentStage, uint32 currentObj ) { uint len = context.result.rewriteNextObject.length; if (context.currentStage + 1 >= curStages && len == 0) { // if we have reduced drop then do not mint token at all if (StatLib.mintDropChanceDelta(context.stats.experience, context.stats.level, context.biome) == 0) { _mintGameTokens( context.dungId, cc, StatLib.getVirtualLevel(context.stats.experience, context.stats.level, true), context.biome, treasuryTokens, context.heroToken, context.heroTokenId ); } isCompleted = true; } else { // need to extend stages for new rewrite objects size uint newStages = context.currentStage + 1 + len; if (curStages < newStages) { dungStatus.stages = uint8(newStages); // need to extend exist array dungStatus.uniqObjects = new uint32[](newStages); // no need to write again old uniq objects, they will be updated in case of hero death } for (uint i; i < len; ++i) { uint32 nextObjId = context.result.rewriteNextObject[i]; dungStatus.uniqObjects[context.currentStage + 1 + i] = nextObjId; } currentStage = context.currentStage + 1; } // currentObj is 0 by default return (isCompleted, currentStage, currentObj); } /// @notice Increase life, mana and lifeChances according to the action {result} function _increaseChangeableStats( IStatController statController, address heroToken, uint heroTokenId, IGOC.ActionResult memory result ) internal { if (result.heal != 0 || result.manaRegen != 0 || result.experience != 0 || result.lifeChancesRecovered != 0) { statController.changeCurrentStats( heroToken, heroTokenId, IStatController.ChangeableStats({ level: 0, experience: result.experience, life: uint32(result.heal.toUint()), mana: uint32(result.manaRegen.toUint()), lifeChances: uint32(result.lifeChancesRecovered.toUint()) }), true ); } } /// @notice Decrease life and mana according to the action {result} function _decreaseChangeableStats( IStatController statController, address heroToken, uint heroTokenId, IGOC.ActionResult memory result ) internal { // decrease changeable stats if (result.damage != 0 || result.manaConsumed != 0) { statController.changeCurrentStats( heroToken, heroTokenId, IStatController.ChangeableStats({ level: 0, experience: 0, life: uint32(result.damage.toUint()), mana: uint32(result.manaConsumed.toUint()), lifeChances: 0 }), false ); } } /// @notice Decrease lifeChances on 1, restore life and mana to full function _reduceLifeChances(IStatController statController, address hero, uint heroId, uint32 curLife, uint32 curMana) internal { uint32 lifeFull = uint32(CalcLib.toUint(statController.heroAttribute(hero, heroId, uint(IStatController.ATTRIBUTES.LIFE)))); uint32 manaFull = uint32(CalcLib.toUint(statController.heroAttribute(hero, heroId, uint(IStatController.ATTRIBUTES.MANA)))); // --------- reduce life chance statController.changeCurrentStats( hero, heroId, IStatController.ChangeableStats({level: 0, experience: 0, life: 0, mana: 0, lifeChances: 1}), false ); // --------- restore life and mana to full statController.changeCurrentStats( hero, heroId, IStatController.ChangeableStats({ level: 0, experience: 0, life: AppLib.sub0(lifeFull, curLife), mana: AppLib.sub0(manaFull, curMana), lifeChances: 0 }), true ); } /// @notice Mint mint-items from {result}, add them to {treasuryItems} function _mintItems( ObjectActionInternalData memory context, ControllerContextLib.ControllerContext memory cc, bytes32[] storage treasuryItems ) internal { uint64 dungId = context.dungId; IGOC.ActionResult memory result = context.result; IItemController ic = ControllerContextLib.itemController(cc); for (uint i; i < result.mintItems.length; i++) { if (result.mintItems[i] == address(0)) { continue; } uint itemId = ic.mint(result.mintItems[i], address(this)); treasuryItems.push(result.mintItems[i].packNftId(itemId)); emit IApplicationEvents.AddTreasuryItem(dungId, result.mintItems[i], itemId); } } /// @notice Register game-token in {treasuryTokens}, mint dungeon reward function _mintGameTokens( uint64 dungId, ControllerContextLib.ControllerContext memory cc, uint lvlForMint, uint biome, EnumerableMap.AddressToUintMap storage treasuryTokens, address hero, uint heroId ) private { IHeroController heroController = ControllerContextLib.heroController(cc); uint maxOpenedNgLevel = heroController.maxOpenedNgLevel(); uint heroNgLevel = heroController.getHeroInfo(hero, heroId).ngLevel; IGameToken gameToken = ControllerContextLib.gameToken(cc); uint amount = IMinter(gameToken.minter()).mintDungeonReward(dungId, biome, lvlForMint); // Total amount of rewards should be equal to: reward = normal_reward * (1 + NG_LVL) / ng_sum // We have minted {amount}, so we should burn off {amount - reward}. // {amount} is exactly equal to {reward} only if NG_LVL is 0 uint reward = amount * (1 + heroNgLevel) / RewardsPoolLib.getNgSum(maxOpenedNgLevel); if (amount > reward) { gameToken.burn(amount - reward); amount = reward; } _registerTreasuryToken(address(gameToken), treasuryTokens, amount); emit IApplicationEvents.AddTreasuryToken(dungId, address(gameToken), amount); } /// @notice Add {rewardToken} to {treasuryTokens} if it's not add there already function _registerTreasuryToken(address rewardToken, EnumerableMap.AddressToUintMap storage treasuryTokens, uint amount) internal { (bool exist, uint existAmount) = treasuryTokens.tryGet(rewardToken); if (!exist || existAmount + amount > 0) { uint balance = IERC20(rewardToken).balanceOf(address(this)); if (balance < existAmount + amount) { revert IAppErrors.NotEnoughTokens(balance, existAmount + amount); } treasuryTokens.set(rewardToken, existAmount + amount); } } //endregion ------------------------ Main logic - auxiliary functions //region ------------------------ ENTER/EXIT /// @notice Hero enters to the dungeon. Check requirements before entering, update status of the hero and the dungeon. function _enter( ControllerContextLib.ControllerContext memory cc, IDungeonFactory.DungeonStatus storage dungStatus, IDungeonFactory.DungeonAttributes storage dungAttrs, uint16 dungNum, uint64 dungId, address heroToken, uint heroTokenId ) internal { IDungeonFactory.MainState storage s = _S(); IStatController.ChangeableStats memory stats = ControllerContextLib.statController(cc).heroStats(heroToken, heroTokenId); uint8 dungBiome = dungAttrs.biome; if (ControllerContextLib.reinforcementController(cc).isStaked(heroToken, heroTokenId)) revert IAppErrors.Staked(heroToken, heroTokenId); { IPvpController pc = ControllerContextLib.pvpController(cc); if (address(pc) != address(0) && pc.isHeroStakedCurrently(heroToken, heroTokenId)) revert IAppErrors.PvpStaked(); } if (stats.lifeChances == 0) revert IAppErrors.ErrorHeroIsDead(heroToken, heroTokenId); if (s.heroCurrentDungeon[heroToken.packNftId(heroTokenId)] != 0) revert IAppErrors.ErrorAlreadyInDungeon(); // assume here that onlyEnteredHeroOwner is already checked by the caller if (ControllerContextLib.heroController(cc).heroBiome(heroToken, heroTokenId) != dungBiome) revert IAppErrors.ErrorNotBiome(); if (dungStatus.heroToken != address(0)) revert IAppErrors.ErrorDungeonBusy(); if (!isDungeonEligibleForHero(s, ControllerContextLib.statController(cc), dungNum, uint8(stats.level), heroToken, heroTokenId)) { revert IAppErrors.ErrorNotEligible(heroToken, dungNum); } // remove free dungeon if (s.freeDungeons[dungBiome].remove(uint(dungId))) { emit IApplicationEvents.FreeDungeonRemoved(dungBiome, dungId); } _changeCurrentDungeon(s, heroToken, heroTokenId, dungId); if (dungStatus.currentStage != 0) { dungStatus.currentStage = uint8(0); } dungStatus.heroToken = heroToken; dungStatus.heroTokenId = heroTokenId; emit IApplicationEvents.Entered(dungId, heroToken, heroTokenId); } /// @notice Check if dungeon is eligible for the hero /// @param dungNum Dungeon logic id function isDungeonEligibleForHero( IDungeonFactory.MainState storage s, IStatController statController, uint16 dungNum, uint8 heroLevel, address heroToken, uint heroTokenId ) internal view returns (bool) { IDungeonFactory.DungeonAttributes storage dungAttr = s.dungeonAttributes[dungNum]; // check if the hero level is in the range required by the dungeon { (uint minLevel, uint maxLevel,) = dungAttr.minMaxLevel.unpackUint8Array3(); if (heroLevel < minLevel || heroLevel > maxLevel) { return false; } } // check if hero/global custom values are in the ranges required by the dungeon bytes32[] memory requiredCustomDataIndex = dungAttr.requiredCustomDataIndex; bytes32[] memory requiredCustomDataValue = dungAttr.requiredCustomDataValue; uint len = requiredCustomDataIndex.length; for (uint i; i < len; ++i) { bytes32 index = requiredCustomDataIndex[i]; if (index == bytes32(0)) continue; (uint64 min, uint64 max, bool isHeroValue) = requiredCustomDataValue[i].unpackCustomDataRequirements(); uint value = isHeroValue ? statController.heroCustomData(heroToken, heroTokenId, index) : statController.globalCustomData(index); if (value < uint(min) || value > uint(max)) { return false; } } return true; } /// @notice Select logic for the new dungeon function getDungeonLogic( IDungeonFactory.MainState storage s_, ControllerContextLib.ControllerContext memory cc, uint8 heroLevel, address heroToken, uint heroTokenId, uint random ) internal view returns (uint16) { if (heroLevel == 0) revert IAppErrors.ErrorHeroLevelStartFrom1(); uint8 heroBiome; { IHeroController hc = ControllerContextLib.heroController(cc); heroBiome = hc.heroBiome(heroToken, heroTokenId); // try to get specific dungeon // specific dungeon for concrete level and class uint16 specificDungeon = s_.dungeonSpecific[_toUint8PackedArray(heroLevel / uint8(StatLib.BIOME_LEVEL_STEP) + 1, hc.heroClass(heroToken))]; // if no specific dungeon for concrete class try to find for all classes if (specificDungeon == 0) { specificDungeon = s_.dungeonSpecific[_toUint8PackedArray(heroLevel / uint8(StatLib.BIOME_LEVEL_STEP) + 1, 0)]; } // if no specific dungeon for concrete class and level try to find for all classes and all levels if (specificDungeon == 0) { // in this case we have 1 specific dungeon for all classes and levels, and only 1, suppose to be initial territory specificDungeon = s_.dungeonSpecific[_toUint8PackedArray(0, 0)]; } if (specificDungeon != 0) { if (!s_.specificDungeonCompleted[heroToken.packDungeonKey(uint64(heroTokenId), specificDungeon)] && s_.dungeonAttributes[specificDungeon].biome == heroBiome) { return specificDungeon; } } } EnumerableSet.UintSet storage dungs = s_.dungeonsLogicByBiome[heroBiome]; uint size = dungs.length(); if (size == 0) revert IAppErrors.ErrorNoDungeonsForBiome(heroBiome); IStatController statController = ControllerContextLib.statController(cc); uint16 dungeonLogic; uint dungeonIndex = random % size; for (uint i; i < size; ++i) { dungeonLogic = uint16(dungs.at(dungeonIndex)); if (isDungeonEligibleForHero(s_, statController, dungeonLogic, heroLevel, heroToken, heroTokenId)) { return dungeonLogic; } dungeonIndex++; if (dungeonIndex >= size) { dungeonIndex = 0; } } revert IAppErrors.ErrorNoEligibleDungeons(); } /// @notice Exit the dungeon /// @param claim Claim treasure items and tokens function exitDungeon(IController controller, uint64 dungId, bool claim, address msgSender) external { IDungeonFactory.DungeonStatus storage dungStatus = _S().dungeonStatuses[dungId]; ControllerContextLib.ControllerContext memory cc = ControllerContextLib.init(controller); address heroToken = dungStatus.heroToken; uint heroTokenId = dungStatus.heroTokenId; if (!dungStatus.isCompleted) revert IAppErrors.ErrorNotCompleted(); if (controller.onPause()) revert IAppErrors.ErrorPaused(); onlyOwner(heroToken, heroTokenId, msgSender); if (_S().heroCurrentDungeon[heroToken.packNftId(heroTokenId)] != dungId) revert IAppErrors.ErrorHeroNotInDungeon(); IHeroController heroController = ControllerContextLib.heroController(cc); (address payToken,) = heroController.payTokenInfo(heroToken); uint16 dungNum = dungStatus.dungNum; _setDungeonCompleted(_S(), dungNum, dungId, heroToken, heroTokenId); if (claim) { _claimAll(cc, msgSender, dungId, dungNum, dungStatus, heroToken, heroTokenId, payToken); } _heroExit(_S(), heroController, heroToken, heroTokenId); if (payToken == address(0)) { // F2P hero doesn't have pay token, he is destroyed after exit of the dungeon _killHero(heroController, dungId, heroToken, heroTokenId, dungStatus.treasuryItems); } // register daily activity address userController = controller.userController(); if (userController != address(0)) { if (heroController.sandboxMode(heroToken, heroTokenId) != uint8(IHeroController.SandboxMode.SANDBOX_MODE_1)) { IUserController(userController).registerPassedDungeon(msgSender); } } emit IApplicationEvents.Exit(dungId, claim); } /// @notice Emergency exit: the governance can drop the hero from dungeon in emergency function emergencyExit(IController controller, uint64 dungId) external { IDungeonFactory.MainState storage s = _S(); // assume that governance-restriction is checked on caller side IDungeonFactory.DungeonStatus storage dungStatus = s.dungeonStatuses[dungId]; _heroExit(s, IHeroController(controller.heroController()), dungStatus.heroToken, dungStatus.heroTokenId); dungStatus.isCompleted = true; dungStatus.heroToken = address(0); dungStatus.heroTokenId = 0; emit IApplicationEvents.Exit(dungId, false); } //endregion ------------------------ ENTER/EXIT //region ------------------------ ENTER/EXIT auxiliary functions /// @dev this function should emit event to indicate dungeon remove function _setDungeonCompleted(IDungeonFactory.MainState storage s, uint16 dungNum, uint64 dungeonId, address heroToken, uint heroTokenId) internal { if (s.allSpecificDungeons.contains(dungNum)) { s.specificDungeonCompleted[heroToken.packDungeonKey(uint64(heroTokenId), dungNum)] = true; } emit IApplicationEvents.DungeonCompleted(dungNum, dungeonId, heroToken, heroTokenId); } /// @notice Change current dungeon of the hero to 0 and release his reinforcement function _heroExit(IDungeonFactory.MainState storage s, IHeroController heroController, address heroToken, uint heroTokenId) internal { _changeCurrentDungeon(s, heroToken, heroTokenId, 0); heroController.releaseReinforcement(heroToken, heroTokenId); } /// @notice Change current dungeon of the hero to the {dungeonId} function _changeCurrentDungeon(IDungeonFactory.MainState storage s, address hero, uint heroId, uint64 dungeonId) internal { s.heroCurrentDungeon[hero.packNftId(heroId)] = dungeonId; emit IApplicationEvents.HeroCurrentDungeonChanged(hero, heroId, dungeonId); } /// @notice Enumerate busy slots of the hero, find all SKILL_XXX and return their addresses and ids /// @return skillSlotAdr Addresses of available skills. 0 - SKILL_1, 1 - SKILL_2, 2 - SKILL_3. /// Address is zero if the hero doesn't have the corresponded skill. /// @return skillSlotIds Ids of available skills. 0 - SKILL_1, 1 - SKILL_2, 2 - SKILL_3 /// ID is zero if the hero doesn't have the corresponded skill. function _getSkillSlotsForHero(IStatController statCtr, address heroToken, uint heroTokenId) internal view returns ( address[3] memory skillSlotAdr, uint[3] memory skillSlotIds ) { uint8[] memory busySlots = statCtr.heroItemSlots(heroToken, uint64(heroTokenId)); for (uint i; i < busySlots.length; ++i) { if (busySlots[i] == uint8(IStatController.ItemSlots.SKILL_1)) { (skillSlotAdr[0], skillSlotIds[0]) = statCtr.heroItemSlot(heroToken, uint64(heroTokenId), busySlots[i]).unpackNftId(); } if (busySlots[i] == uint8(IStatController.ItemSlots.SKILL_2)) { (skillSlotAdr[1], skillSlotIds[1]) = statCtr.heroItemSlot(heroToken, uint64(heroTokenId), busySlots[i]).unpackNftId(); } if (busySlots[i] == uint8(IStatController.ItemSlots.SKILL_3)) { (skillSlotAdr[2], skillSlotIds[2]) = statCtr.heroItemSlot(heroToken, uint64(heroTokenId), busySlots[i]).unpackNftId(); } } return (skillSlotAdr, skillSlotIds); } /// @notice Generate map[3] for SKILL_1, SKILL_2, SKILL_3 (0 - not marked, 1 - marked) /// and save the map to {s_}._skillSlotsForDurabilityReduction as packed uint8[] /// @dev mark skill slots for durability reduction /// SIP-001: take into account hero's skills only and ignore skills of the helper /// @param data abi.encoded IFightCalculator.AttackInfo function _markSkillSlotsForDurabilityReduction( IDungeonFactory.MainState storage s_, IStatController sc, IItemController itemController, bytes memory data, address heroToken, uint heroTokenId ) internal { uint8[] memory map = new uint8[](3); (IFightCalculator.AttackInfo memory attackInfo) = abi.decode(data, (IFightCalculator.AttackInfo)); uint length = attackInfo.skillTokens.length; if (length != 0 || attackInfo.attackToken != address(0)) { (address[3] memory skillSlotAdr, uint[3] memory skillSlotIds) = _getSkillSlotsForHero(sc, heroToken, heroTokenId); for (uint i; i < length; ++i) { address token = attackInfo.skillTokens[i]; uint tokenId = attackInfo.skillTokenIds[i]; // The hero is able to use own skills OR the skills of the helper. Take into account only own hero's skills here (address h,) = itemController.equippedOn(token, tokenId); if (h == heroToken) { if (token == skillSlotAdr[0] && tokenId == skillSlotIds[0]) { map[0] = 1; } else if (token == skillSlotAdr[1] && tokenId == skillSlotIds[1]) { map[1] = 1; } else if (token == skillSlotAdr[2] && tokenId == skillSlotIds[2]) { map[2] = 1; } } } if (attackInfo.attackToken == skillSlotAdr[0] && attackInfo.attackTokenId == skillSlotIds[0]) { map[0] = 1; } else if (attackInfo.attackToken == skillSlotAdr[1] && attackInfo.attackTokenId == skillSlotIds[1]) { map[1] = 1; } else if (attackInfo.attackToken == skillSlotAdr[2] && attackInfo.attackTokenId == skillSlotIds[2]) { map[2] = 1; } } // write even empty map for clear prev values s_.skillSlotsForDurabilityReduction[heroToken.packNftId(heroTokenId)] = map.packUint8Array(); } /// @dev clear all skill slots marks function _releaseSkillSlotsForDurabilityReduction(IDungeonFactory.MainState storage s_, address heroToken, uint heroTokenId) internal { delete s_.skillSlotsForDurabilityReduction[heroToken.packNftId(heroTokenId)]; } //endregion ------------------------ ENTER/EXIT auxiliary functions //region ------------------------ CLAIM /// @notice Calculate amount of biome owner tax /// @return taxPercent Percent of tax that is taken if favor of biome owner, decimals 3 /// @return guildBank Address of guild bank of the biome owner /// @return guildId The owner of the biome function _getBiomeTax( uint8 biome, ControllerContextLib.ControllerContext memory cc ) internal returns ( uint taxPercent, address guildBank, uint guildId ) { IPvpController pvpController = ControllerContextLib.pvpController(cc); if (address(pvpController) != address(0)) { (uint _guildId, uint _taxPercent) = pvpController.refreshBiomeTax(biome); if (_guildId != 0) { // assume that guildController cannot be 0 if pvp controller is set guildBank = ControllerContextLib.guildController(cc).getGuildBank(_guildId); if (guildBank != address(0)) { guildId = _guildId; taxPercent = _taxPercent; } } } return (taxPercent, guildBank, guildId); } /// @notice Claim all treasure tokens and items registered for the given hero. /// At first the tax is taken in favor of biome owner if any. /// Remain tokens are send to msgSender and/or helper, or they can be send to controller or burned. /// The items are transferred to msgSender or helper (random choice) or destroyed (F2P hero). /// @dev ClaimContext is used both for lazy initialization and to extend limits of allowed local vars. /// @param heroPayToken Hero pay token. It's zero for hero 5. function _claimAll( ControllerContextLib.ControllerContext memory cc, address msgSender, uint64 dungId, uint16 dungNum, IDungeonFactory.DungeonStatus storage dungStatus, address hero, uint heroId, address heroPayToken ) internal { ClaimContext memory context; context.msgSender = msgSender; context.dungId = dungId; (context.helpHeroToken, context.helpHeroId) = ControllerContextLib.heroController(cc).heroReinforcementHelp(hero, heroId); context.toHelperRatio = ControllerContextLib.reinforcementController(cc).toHelperRatio(context.helpHeroToken, context.helpHeroId); context.itemLength = dungStatus.treasuryItems.length; context.tokenLength = dungStatus.treasuryTokens.length(); context.tokens = new address[](context.tokenLength); context.amounts = new uint[](context.tokenLength); IDungeonFactory.DungeonAttributes storage dungAttrs = _S().dungeonAttributes[dungNum]; context.biome = dungAttrs.biome; (context.taxPercent, context.guildBank, context.guildId) = _getBiomeTax(context.biome, cc); context.heroPayToken = heroPayToken; context.sandboxMode = ControllerContextLib.heroController(cc).sandboxMode(hero, heroId); // need to write tokens separately coz we need to delete them from map for (uint i; i < context.tokenLength; i++) { (context.tokens[i], context.amounts[i]) = dungStatus.treasuryTokens.at(i); } for (uint i; i < context.tokenLength; i++) { _claimToken(dungStatus.treasuryTokens, context, cc, context.tokens[i], context.amounts[i]); } if (context.sandboxMode == uint8(IHeroController.SandboxMode.SANDBOX_MODE_1) && context.itemLength != 0) { context.items = new address[](context.itemLength); context.itemIds = new uint[](context.itemLength); context.countItems = 0; } for (uint i; i < context.itemLength; i++) { (address itemAdr, uint itemId) = dungStatus.treasuryItems[i].unpackNftId(); if (_claimItem(context, cc, itemAdr, itemId)) { // the item was already sent to itemBoxController, we need to call registerItems() for it below context.items[context.countItems] = itemAdr; context.itemIds[context.countItems] = itemId; context.countItems += 1; } } if (context.sandboxMode == uint8(IHeroController.SandboxMode.SANDBOX_MODE_1) && context.countItems != 0) { // Too much code is required to cut two arrays to required length here. // It's easier to ignore unnecessary items on ItemBox side. ControllerContextLib.itemBoxController(cc).registerItems(hero, heroId, context.items, context.itemIds, context.countItems); } delete dungStatus.treasuryItems; } /// @notice Remove {token} from treasuryTokens, transfer/burn token {amount} function _claimToken( EnumerableMap.AddressToUintMap storage treasuryTokens, ClaimContext memory context, ControllerContextLib.ControllerContext memory cc, address token, uint amount ) internal { treasuryTokens.remove(token); if (amount != 0) { if (context.heroPayToken == address(0)) { if (token == address(ControllerContextLib.gameToken(cc))) { IGameToken(token).burn(amount); } else { IERC20(token).transfer(address(cc.controller), amount); } } else { if (context.sandboxMode == uint8(IHeroController.SandboxMode.SANDBOX_MODE_1)) { // send treasury back to the Treasury in sandbox mode, assume that amount != 0 here IERC20(token).transfer(address(ControllerContextLib.treasury(cc)), amount); emit IApplicationEvents.SandboxReturnAmountToTreasury(context.dungId, token, amount); } else { // get tax in favor of biome owner if any uint amountMinusTax = amount; if (context.taxPercent != 0 && context.guildBank != address(0)) { uint taxAmount = amount * context.taxPercent / 100_000; IERC20(token).transfer(context.guildBank, taxAmount); // assume that taxAmount is not 0 here amountMinusTax -= taxAmount; emit IApplicationEvents.BiomeTaxPaid(context.msgSender, context.biome, context.guildId, amount, context.taxPercent, taxAmount, context.dungId); } uint toHelper = context.helpHeroToken == address(0) ? 0 : amountMinusTax * context.toHelperRatio / 100; uint toHeroOwner = amountMinusTax - toHelper; if (toHeroOwner != 0) { IERC20(token).transfer(context.msgSender, toHeroOwner); } if (toHelper != 0) { IReinforcementController reinforcementController = ControllerContextLib.reinforcementController(cc); IERC20(token).transfer(address(reinforcementController), toHelper); reinforcementController.registerTokenReward(context.helpHeroToken, context.helpHeroId, token, toHelper, context.dungId); } emit IApplicationEvents.ClaimToken(context.dungId, token, amount); } } } } /// @notice Destroy item (for F2P) or transfer the item to helper/sender (random choice) /// @return itemWasSentToItemBoxController True if ItemBoxController.registerItems() must be called after the call function _claimItem( ClaimContext memory context, ControllerContextLib.ControllerContext memory cc, address token, uint tokenId ) internal returns (bool itemWasSentToItemBoxController) { if (IERC721(token).ownerOf(tokenId) == address(this)) { if (context.heroPayToken == address(0)) { // if it is F2P hero destroy all drop (not applicable for sandbox) ControllerContextLib.itemController(cc).destroy(token, tokenId); } else { // get tax in favor of biome owner if any bool toBiomeOwner = false; if (context.taxPercent != 0 && context.guildBank != address(0)) { toBiomeOwner = ControllerContextLib.oracle(cc).getRandomNumber(100_000, 0) < context.taxPercent; } bool toHelper = false; if (!toBiomeOwner && context.helpHeroToken != address(0)) { toHelper = ControllerContextLib.oracle(cc).getRandomNumber(100, 0) < context.toHelperRatio; } if (toBiomeOwner) { // SCR-1253: Attention: GuildBank with version below 1.0.2 was not inherited from ERC721Holder (mistake). // As result, safeTransferFrom doesn't work with such banks, they must be updated. So, use transferFrom here. IERC721(token).transferFrom(address(this), context.guildBank, tokenId); emit IApplicationEvents.BiomeTaxPaidNft(context.msgSender, context.biome, context.guildId, token, tokenId, context.taxPercent, context.dungId); } else if (toHelper) { IReinforcementController reinforcementController = ControllerContextLib.reinforcementController(cc); IERC721(token).safeTransferFrom(address(this), address(reinforcementController), tokenId); reinforcementController.registerNftReward(context.helpHeroToken, context.helpHeroId, token, tokenId, context.dungId); } else { if (context.sandboxMode == uint8(IHeroController.SandboxMode.SANDBOX_MODE_1)) { IItemBoxController itemBoxController = ControllerContextLib.itemBoxController(cc); IERC721(token).safeTransferFrom(address(this), address(itemBoxController), tokenId); itemWasSentToItemBoxController = true; // notify caller that registerItems() should be called } else { IERC721(token).safeTransferFrom(address(this), context.msgSender, tokenId); } } emit IApplicationEvents.ClaimItem(context.dungId, token, tokenId); } } return itemWasSentToItemBoxController; } //endregion ------------------------ CLAIM //region ------------------------ Utils function _toUint8PackedArray(uint8 val0, uint8 val1) internal pure returns (bytes32 key) { return PackingLib.packUint8Array3(val0, val1, 0); } function _toUint8ArrayWithoutZeroes(bytes32 data) internal pure returns (uint8[] memory result) { uint8[] memory arr = data.unpackUint8Array(); uint newSize; for (uint i; i < arr.length; ++i) { if (arr[i] == 0) { break; } newSize++; } result = new uint8[](newSize); for (uint i; i < newSize; ++i) { result[i] = arr[i]; } } //endregion ------------------------ Utils }
// 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)); } function packItemBoxItemInfo(bool withdrawn, uint64 timestamp) internal pure returns (bytes32 data) { data = bytes32(uint(uint8(withdrawn ? 1 : 0))); data |= bytes32(uint(timestamp)) << 8; } function unpackItemBoxItemInfo(bytes32 data) internal pure returns (bool withdrawn, uint64 timestamp) { withdrawn = uint8(uint(data)) != 0; timestamp = uint64(uint(data) >> 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 //region ------------------------------------ Metadata of IPvpController.PvpAttackInfoDefaultStrategy function getPvpBehaviourStrategyKind(bytes memory encodedData) internal pure returns (uint) { bytes32 serialized; assembly { serialized := mload(add(encodedData, 64)) // first 32 bytes contain 0x20 and indicate array, we need to read second 32 bytes to get first uint in the struct } return uint(serialized); } //endregion ------------------------------------ Metadata of IPvpController.PvpAttackInfoDefaultStrategy }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../interfaces/IAppErrors.sol"; import "../interfaces/IApplicationEvents.sol"; import "../interfaces/IERC20.sol"; import "../interfaces/IGameToken.sol"; import "../interfaces/IRewardsPool.sol"; import "../openzeppelin/Math.sol"; import "../proxy/Controllable.sol"; library RewardsPoolLib { /// @dev keccak256(abi.encode(uint256(keccak256("rewards.pool.main")) - 1)) & ~bytes32(uint256(0xff)) bytes32 internal constant REWARDS_POOL_STORAGE_LOCATION = 0x6ad655e44097c54b487e7c9215cc0bbf37bbe7fc2f8034e2ddf6749036fda500; // rewards.pool.main //region ------------------------ Storage function _S() internal pure returns (IRewardsPool.MainState storage s) { assembly { s.slot := REWARDS_POOL_STORAGE_LOCATION } return s; } //endregion ------------------------ Storage //region ------------------------ Restrictions function onlyHeroController(IController controller) internal view { if (controller.heroController() != msg.sender) revert IAppErrors.ErrorNotHeroController(msg.sender); } function _onlyDeployer(IController controller) internal view { if (!controller.isDeployer(msg.sender)) revert IAppErrors.ErrorNotDeployer(msg.sender); } function _onlyGovernance(IController controller) internal view { if (controller.governance() != msg.sender) revert IAppErrors.NotGovernance(msg.sender); } //endregion ------------------------ Restrictions //region ------------------------ View function balanceOfToken(address token) internal view returns (uint) { return IERC20(token).balanceOf(address(this)); } function baseAmount(address token) internal view returns (uint) { return _S().baseAmounts[token]; } /// @param maxBiome Max available biome, see {IDungeonFactory.state.maxBiome} /// @param maxNgLevel Max opened NG_LEVEL, see {IHeroController.state.maxOpenedNgLevel} /// @param biome Current hero biome [0..19 /// @param heroNgLevel Current hero NG_LVL [0..99] /// @return Reward percent, decimals 18 function rewardPercent(uint maxBiome, uint maxNgLevel, uint biome, uint heroNgLevel) internal pure returns (uint) { // biome_sum = max biome*(max biome+1)/2 // biome_weight = biome / biome_sum // reward_percent = biome_weight * (1 + NG_LVL) / ng_sum return 1e18 * biome * (1 + heroNgLevel) / (maxBiome * (maxBiome + 1) / 2) // biome_sum / getNgSum(maxNgLevel); } /// @notice be definition ng_sum = (max_ng + 1) * (max_ng+2) / 2 function getNgSum(uint maxNgLevel) internal pure returns (uint) { return ((maxNgLevel + 1) * (maxNgLevel + 2) / 2); } function rewardAmount(address token, uint maxBiome, uint maxNgLevel, uint biome, uint heroNgLevel) internal view returns (uint) { return baseAmount(token) * rewardPercent(maxBiome, maxNgLevel, biome, heroNgLevel) / 1e18; } /// @notice Calculate lost profit amount in percents in the case when hero is created on {heroNgLevel} > 0 /// @param maxBiome Max available biome, see {IDungeonFactory.state.maxBiome} /// @param maxNgLevel Max opened NG_LEVEL, see {IHeroController.state.maxOpenedNgLevel} /// @param heroNgLevel NG_LVL [1..99] where the hero is created, assume heroNgLevel > 0 /// @return Lost reward percent, decimals 18 function lostProfitPercent(uint maxBiome, uint maxNgLevel, uint heroNgLevel) internal pure returns (uint) { uint percent; for (uint8 ngLevel = 0; ngLevel < heroNgLevel; ++ngLevel) { percent += totalProfitOnLevel(maxBiome, maxNgLevel, ngLevel); } return percent; } /// @notice SCR-1064: Calculate a percent to reduce drop chance of the monsters on various NG-levels. /// The percent is reverse to the percent of the rewards. /// @param maxBiome Max available biome, see {IDungeonFactory.state.maxBiome} /// @param maxNgLevel Max opened NG_LEVEL, see {IHeroController.state.maxOpenedNgLevel} /// @param heroNgLevel NG_LVL [1..99] where the hero is created, assume heroNgLevel > 0 /// @return Drop chance percent, decimals 18 function dropChancePercent(uint maxBiome, uint maxNgLevel, uint heroNgLevel) internal pure returns (uint) { if (heroNgLevel == 0) return 1e18; // NG0 is special case - drop is NOT reduced return heroNgLevel > maxNgLevel ? 0 : totalProfitOnLevel(maxBiome, maxNgLevel, maxNgLevel - heroNgLevel + 1); } /// @notice Calculate total percent of rewards in all biomes on the given {ngLevel} function totalProfitOnLevel(uint maxBiome, uint maxNgLevel, uint ngLevel) internal pure returns (uint percent) { for (uint8 biome = 1; biome <= maxBiome; ++biome) { percent += rewardPercent(maxBiome, maxNgLevel, biome, ngLevel); } return percent; } //endregion ------------------------ View //region ------------------------ Gov actions function setBaseAmount(IController controller, address token, uint baseAmount_) internal { _onlyDeployer(controller); emit IApplicationEvents.BaseAmountChanged(_S().baseAmounts[token], baseAmount_); _S().baseAmounts[token] = baseAmount_; } function withdraw(IController controller, address token, uint amount, address receiver) internal { _onlyGovernance(controller); IERC20(token).transfer(receiver, amount); } //endregion ------------------------ Gov actions //region ------------------------ Logic /// @notice Send {amount} of the {token} to the {dungeon} /// @dev Assume here that all calculations and checks are made on dungeonFactory-side function sendReward(IController controller, address token, uint rewardAmount_, address receiver) internal { onlyHeroController(controller); uint balance = IERC20(token).balanceOf(address(this)); if (balance >= rewardAmount_) { IERC20(token).transfer(receiver, rewardAmount_); emit IApplicationEvents.RewardSentToUser(receiver, token, rewardAmount_); } else { // there is not enough amount on reward pool balance // just register reward in events // assume that the reward should be paid to the receiver later manually emit IApplicationEvents.NotEnoughReward(receiver, token, rewardAmount_); } } //endregion ------------------------ Logic }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; /// @title Library for setting / getting slot variables (used in upgradable proxy contracts) /// @author bogdoslav library SlotsLib { /// @notice Version of the contract /// @dev Should be incremented when contract changed string public constant SLOT_LIB_VERSION = "1.0.0"; // ************* GETTERS ******************* /// @dev Gets a slot as bytes32 function getBytes32(bytes32 slot) internal view returns (bytes32 result) { assembly { result := sload(slot) } } /// @dev Gets a slot as an address function getAddress(bytes32 slot) internal view returns (address result) { assembly { result := sload(slot) } } /// @dev Gets a slot as uint256 function getUint(bytes32 slot) internal view returns (uint result) { assembly { result := sload(slot) } } // ************* ARRAY GETTERS ******************* /// @dev Gets an array length function arrayLength(bytes32 slot) internal view returns (uint result) { assembly { result := sload(slot) } } /// @dev Gets a slot array by index as address /// @notice First slot is array length, elements ordered backward in memory /// @notice This is unsafe, without checking array length. function addressAt(bytes32 slot, uint index) internal view returns (address result) { bytes32 pointer = bytes32(uint(slot) - 1 - index); assembly { result := sload(pointer) } } // ************* SETTERS ******************* /// @dev Sets a slot with bytes32 /// @notice Check address for 0 at the setter function set(bytes32 slot, bytes32 value) internal { assembly { sstore(slot, value) } } /// @dev Sets a slot with address /// @notice Check address for 0 at the setter function set(bytes32 slot, address value) internal { assembly { sstore(slot, value) } } /// @dev Sets a slot with uint function set(bytes32 slot, uint value) internal { assembly { sstore(slot, value) } } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../openzeppelin/Math.sol"; import "../openzeppelin/EnumerableSet.sol"; import "../interfaces/IItemController.sol"; import "../interfaces/IStatController.sol"; import "../interfaces/IAppErrors.sol"; import "../interfaces/IApplicationEvents.sol"; import "../lib/StatLib.sol"; /// @notice Implementation of StatController library StatControllerLib { using StatLib for uint; using StatLib for uint[]; using StatLib for uint32; using StatLib for int32; using StatLib for int32; using CalcLib for uint; using CalcLib for int; using CalcLib for int32; using EnumerableSet for EnumerableSet.AddressSet; using EnumerableMap for EnumerableMap.Bytes32ToUintMap; using PackingLib for bytes32[]; using PackingLib for bytes32; using PackingLib for int32; using PackingLib for uint32; //region ------------------------ Constants /// @dev keccak256(abi.encode(uint256(keccak256("stat.controller.main")) - 1)) & ~bytes32(uint256(0xff)) bytes32 internal constant MAIN_STORAGE_LOCATION = 0xca9e8235a410bd2656fc43f888ab589425034944963c2881072ee821e700e600; int32 public constant LEVEL_UP_SUM = 5; bytes32 public constant KARMA_HASH = bytes32("KARMA"); uint internal constant DEFAULT_KARMA_VALUE = 1000; /// @notice Virtual data, value is not stored to hero custom data, heroClass is taken from heroController by the index bytes32 public constant HERO_CLASS_HASH = bytes32("HERO_CLASS"); /// @notice Custom data of the hero. Value is incremented on every life-chance lost bytes32 public constant DEATH_COUNT_HASH = bytes32("DEATH_COUNT"); //endregion ------------------------ Constants //region ------------------------ RESTRICTIONS function onlyRegisteredContract(IController controller_) internal view returns (IHeroController) { // using of ControllerContextLib.ControllerContext increases size of the contract on 0.5 kb address sender = msg.sender; address heroController = controller_.heroController(); if ( heroController != sender && controller_.itemController() != sender && controller_.dungeonFactory() != sender && controller_.storyController() != sender && controller_.gameObjectController() != sender && controller_.pvpController() != sender ) revert IAppErrors.ErrorForbidden(sender); return IHeroController(heroController); } function onlyItemController(IController controller_) internal view { if (controller_.itemController() != msg.sender) revert IAppErrors.ErrorNotItemController(msg.sender); } function onlyHeroController(IController controller_) internal view returns (IHeroController) { address heroController = controller_.heroController(); if (heroController != msg.sender) revert IAppErrors.ErrorNotHeroController(msg.sender); return IHeroController(heroController); } function onlyDeployer(IController controller) internal view { if (!controller.isDeployer(msg.sender)) revert IAppErrors.ErrorNotDeployer(msg.sender); } //endregion ------------------------ RESTRICTIONS //region ------------------------ VIEWS function _S() internal pure returns (IStatController.MainState storage s) { assembly { s.slot := MAIN_STORAGE_LOCATION } return s; } function heroAttributes(IStatController.MainState storage s, address token, uint tokenId) internal view returns (int32[] memory) { return PackingLib.toInt32Array(s.heroTotalAttributes[PackingLib.packNftId(token, tokenId)], uint(IStatController.ATTRIBUTES.END_SLOT)); } function heroBonusAttributes(IStatController.MainState storage s, address token, uint tokenId) internal view returns (int32[] memory) { return PackingLib.toInt32Array(s.heroBonusAttributes[PackingLib.packNftId(token, tokenId)], uint(IStatController.ATTRIBUTES.END_SLOT)); } function heroTemporallyAttributes(IStatController.MainState storage s, address token, uint tokenId) internal view returns (int32[] memory) { return PackingLib.toInt32Array(s.heroTemporallyAttributes[PackingLib.packNftId(token, tokenId)], uint(IStatController.ATTRIBUTES.END_SLOT)); } function heroAttributesLength(address /*token*/, uint /*tokenId*/) internal pure returns (uint) { return uint(IStatController.ATTRIBUTES.END_SLOT); } function heroAttribute(IStatController.MainState storage s, address token, uint tokenId, uint index) internal view returns (int32) { return PackingLib.getInt32(s.heroTotalAttributes[PackingLib.packNftId(token, tokenId)], index); } function heroBaseAttributes(IStatController.MainState storage s, address token, uint tokenId) internal view returns ( IStatController.CoreAttributes memory core ) { int32[] memory data = PackingLib.unpackInt32Array(s._heroCore[PackingLib.packNftId(token, tokenId)]); core = IStatController.CoreAttributes({ strength: int32(data[0]), dexterity: int32(data[1]), vitality: int32(data[2]), energy: int32(data[3]) }); } function heroCustomData(IHeroController hc, address hero, uint heroId, bytes32 index) internal view returns (uint) { return heroCustomDataOnNgLevel(hc, hero, heroId, index, hc.getHeroInfo(hero, heroId).ngLevel); } function heroCustomDataOnNgLevel(IHeroController hc, address hero, uint heroId, bytes32 index, uint8 ngLevel) internal view returns (uint) { if (index == HERO_CLASS_HASH) { return hc.heroClass(hero); } else { (, uint value) = _S().heroCustomDataV2[PackingLib.packNftIdWithValue(hero, heroId, ngLevel)].tryGet(index); if (index == KARMA_HASH && value == 0) { return DEFAULT_KARMA_VALUE; } return value; } } function getAllHeroCustomData(IHeroController hc, address hero, uint heroId) internal view returns (bytes32[] memory keys, uint[] memory values) { // Result doesn't include HERO_CLASS_HASH EnumerableMap.Bytes32ToUintMap storage map = _S().heroCustomDataV2[PackingLib.packNftIdWithValue(hero, heroId, hc.getHeroInfo(hero, heroId).ngLevel)]; uint length = map.length(); keys = new bytes32[](length); values = new uint[](length); for (uint i; i < length; ++i) { (keys[i], values[i]) = map.at(i); } } function globalCustomData(IStatController.MainState storage s, bytes32 index) internal view returns (uint) { return s.globalCustomData[index]; } function heroStats(IStatController.MainState storage s, address token, uint tokenId) internal view returns ( IStatController.ChangeableStats memory result ) { uint32[] memory data = PackingLib.unpackUint32Array(s.heroStats[PackingLib.packNftId(token, tokenId)]); result = IStatController.ChangeableStats({ level: uint32(data[0]), experience: uint32(data[1]), life: uint32(data[2]), mana: uint32(data[3]), lifeChances: uint32(data[4]) }); } function heroItemSlot(IStatController.MainState storage s, address heroToken, uint64 heroTokenId, uint8 itemSlot) internal view returns ( bytes32 nftPacked ) { return s.heroSlots[PackingLib.packMapObject(heroToken, heroTokenId, itemSlot)]; } /// @return Return list of indices of the busy item slots for the given hero function heroItemSlots(IStatController.MainState storage s, address heroToken, uint heroTokenId) internal view returns ( uint8[] memory ) { uint8[] memory slots = PackingLib.unpackUint8Array(s.heroBusySlots[PackingLib.packNftId(heroToken, heroTokenId)]); uint8[] memory busySlotsNumbers = new uint8[](slots.length); uint counter; for (uint8 i; i < uint8(slots.length); ++i) { if (slots[i] != 0) { busySlotsNumbers[counter] = i; counter++; } } uint8[] memory result = new uint8[](counter); for (uint i; i < counter; ++i) { result[i] = busySlotsNumbers[i]; } return result; } function isHeroAlive(IStatController.MainState storage s, address heroToken, uint heroTokenId) internal view returns (bool) { return heroStats(s, heroToken, heroTokenId).lifeChances != 0; } function isConsumableUsed(IStatController.MainState storage s, address heroToken, uint heroTokenId, address item) internal view returns (bool) { return s.usedConsumables[PackingLib.packNftId(heroToken, heroTokenId)].contains(item); } /// @notice Calculate totalAttributes + all attributes of the items specified in {info} function buffHero( IStatController.MainState storage s, IController controller, IStatController.BuffInfo calldata info ) external view returns ( int32[] memory dest, int32 manaSum ) { uint length = info.buffTokens.length; if (length == 0) { return (heroAttributes(s, info.heroToken, info.heroTokenId), 0); } IItemController ic = IItemController(controller.itemController()); int32[] memory buffAttributes = new int32[](uint(IStatController.ATTRIBUTES.END_SLOT)); address[] memory usedTokens = new address[](length); for (uint i; i < length; ++i) { // we should ignore the same skills bool used; for (uint j; j < i; ++j) { if (usedTokens[j] == info.buffTokens[i]) { used = true; break; } } if (used) { continue; } manaSum += int32(ic.itemMeta(info.buffTokens[i]).manaCost); (int32[] memory values, uint8[] memory ids) = ic.casterAttributes(info.buffTokens[i], info.buffTokenIds[i]); StatLib.attributesAdd(buffAttributes, StatLib.valuesToFullAttributesArray(values, ids)); usedTokens[i] = info.buffTokens[i]; } int32[] memory totalAttributes = StatLib.attributesAdd(heroAttributes(s, info.heroToken, info.heroTokenId), buffAttributes); StatLib.attributesAdd(buffAttributes, heroBonusAttributes(s, info.heroToken, info.heroTokenId)); StatLib.attributesAdd(buffAttributes, heroTemporallyAttributes(s, info.heroToken, info.heroTokenId)); return ( StatLib.updateCoreDependAttributesInMemory( totalAttributes, buffAttributes, IHeroController(controller.heroController()).heroClass(info.heroToken), info.heroLevel ), manaSum ); } //endregion ------------------------ VIEWS //region ------------------------ PURE function isItemTypeEligibleToItemSlot(uint itemType, uint itemSlot) internal pure returns (bool) { // Consumable items not eligible if (itemType == 0 || itemSlot == 0) { return false; } // items with type before 5 mapped 1 to 1 if (itemType <= uint(IItemController.ItemType.AMULET)) { return itemSlot == itemType; } if (itemType == uint(IItemController.ItemType.RING)) { return itemSlot == uint(IStatController.ItemSlots.RIGHT_RING) || itemSlot == uint(IStatController.ItemSlots.LEFT_RING); } if (itemType == uint(IItemController.ItemType.BOOTS)) { return itemSlot == uint(IStatController.ItemSlots.BOOTS); } if (itemType == uint(IItemController.ItemType.ONE_HAND)) { return itemSlot == uint(IStatController.ItemSlots.RIGHT_HAND); } if (itemType == uint(IItemController.ItemType.OFF_HAND)) { return itemSlot == uint(IStatController.ItemSlots.LEFT_HAND); } if (itemType == uint(IItemController.ItemType.TWO_HAND)) { return itemSlot == uint(IStatController.ItemSlots.TWO_HAND); } if (itemType == uint(IItemController.ItemType.SKILL)) { return itemSlot == uint(IStatController.ItemSlots.SKILL_1) || itemSlot == uint(IStatController.ItemSlots.SKILL_2) || itemSlot == uint(IStatController.ItemSlots.SKILL_3); } // unknown types return false; } /// @notice How much experience is required to go from the {level} to the next level function levelUpExperienceRequired(uint32 level) internal pure returns (uint) { if (level == 0 || level >= StatLib.MAX_LEVEL) return 0; return level == uint32(1) ? StatLib.levelExperience(level) : StatLib.levelExperience(level) - StatLib.levelExperience(level - uint32(1)); } //endregion ------------------------ PURE //region ------------------------ ACTIONS /// @param heroClass Assume that heroController passes correct value of the heroClass for the given hero /// Also assume that the hero exists and alive function reborn(IController controller, address hero, uint heroId, uint heroClass) external { IStatController.MainState storage s = _S(); bytes32 heroPackedId = PackingLib.packNftId(hero, heroId); IHeroController heroController = onlyHeroController(controller); if (_S().heroBusySlots[heroPackedId] != 0) revert IAppErrors.EquippedItemsExist(); uint32 lifeChances = heroStats(s, hero, heroId).lifeChances; // -------------------------- clear delete s.heroTotalAttributes[heroPackedId]; delete s.heroTemporallyAttributes[heroPackedId]; delete s.heroBonusAttributes[heroPackedId]; // -------------------------- init from zero uint32[] memory baseStats = _initCoreAndAttributes(s, heroPackedId, heroClass); _changeChangeableStats( s, heroPackedId, 1, // level is set to 1 0, // experience is set to 0 baseStats[0], // life is restored baseStats[1], // mana is restored lifeChances// life chances are not changed ); // custom data is NOT cleared on reborn, new custom data map is used on each new NG_LVL _prepareHeroCustomDataForNextNgLevel(heroController, hero, heroId); } function _prepareHeroCustomDataForNextNgLevel(IHeroController heroController, address hero, uint heroId) internal { // assume here, that statController.reborn is called AFTER incrementing of NG_LVL, current NG_LVL has "new" value uint8 newNgLevel = heroController.getHeroInfo(hero, heroId).ngLevel; if (newNgLevel == 0) revert IAppErrors.ZeroValueNotAllowed(); // edge case uint8 prevNgLevel = newNgLevel - 1; // copy value of DEATH_COUNT from current ng-level to next ng-level (bool exist, uint value) = _S().heroCustomDataV2[PackingLib.packNftIdWithValue(hero, heroId, prevNgLevel)].tryGet(DEATH_COUNT_HASH); if (exist && value != 0) { _S().heroCustomDataV2[PackingLib.packNftIdWithValue(hero, heroId, newNgLevel)].set(DEATH_COUNT_HASH, value); emit IApplicationEvents.HeroCustomDataChangedNg(hero, heroId, DEATH_COUNT_HASH, value, newNgLevel); } // leave KARMA equal to 0 on next ng-level, getter returns default karma in this case emit IApplicationEvents.HeroCustomDataChangedNg(hero, heroId, KARMA_HASH, DEFAULT_KARMA_VALUE, newNgLevel); } /// @notice Keep stories, monsters, DEATH_COUNT_HASH and HERO_CLASS_HASH; remove all other custom data function _removeAllHeroCustomData(IHeroController hc, address hero, uint heroId) internal { EnumerableMap.Bytes32ToUintMap storage data = _S().heroCustomDataV2[PackingLib.packNftIdWithValue(hero, heroId, hc.getHeroInfo(hero, heroId).ngLevel)]; uint length = data.length(); bytes32[] memory keysToRemove = new bytes32[](length); bytes32 monsterPrefix = bytes32(abi.encodePacked("MONSTER_")); // 8 bytes bytes32 storyPrefix = bytes32(abi.encodePacked("STORY_")); // 6 bytes for (uint i; i < length; ++i) { (bytes32 key,) = data.at(i); if (key == DEATH_COUNT_HASH || key == HERO_CLASS_HASH) continue; bool isNotMonster; bool isNotStory; for (uint j; j < 8; j++) { if (!isNotMonster && key[j] != monsterPrefix[j]) { isNotMonster = true; } if (!isNotStory && j < 6 && key[j] != storyPrefix[j]) { isNotStory = true; } if (isNotMonster && isNotStory) break; } if (isNotMonster && isNotStory) { keysToRemove[i] = key; } } for (uint i; i < length; ++i) { if (keysToRemove[i] != bytes32(0)) { data.remove(keysToRemove[i]); } } emit IApplicationEvents.HeroCustomDataCleared(hero, heroId); } /// @notice Initialize new hero, set up custom data, core data, changeable stats by default value /// @param heroClass [1..6], see StatLib.initHeroXXX function initNewHero( IStatController.MainState storage s, IController c, address heroToken, uint heroTokenId, uint heroClass ) external { IHeroController heroController = onlyHeroController(c); bytes32 heroPackedId = PackingLib.packNftId(heroToken, heroTokenId); uint32[] memory baseStats = _initCoreAndAttributes(s, heroPackedId, heroClass); _changeChangeableStats(s, heroPackedId, 1, 0, baseStats[0], baseStats[1], baseStats[2]); emit IApplicationEvents.NewHeroInited(heroToken, heroTokenId, IStatController.ChangeableStats({ level: 1, experience: 0, life: baseStats[0], mana: baseStats[1], lifeChances: baseStats[2] })); // --- init predefined custom hero data _initNewHeroCustomData(s, heroController, heroToken, heroTokenId); } /// @dev Reset custom hero data if something went wrong function resetHeroCustomData( IStatController.MainState storage s, IController c, address heroToken, uint heroTokenId ) external { onlyDeployer(c); _removeAllHeroCustomData(IHeroController(c.heroController()), heroToken, heroTokenId); _initNewHeroCustomData(s, IHeroController(c.heroController()), heroToken, heroTokenId); } function _initNewHeroCustomData(IStatController.MainState storage s, IHeroController heroController, address hero, uint heroId) internal { uint8 ngLevel = heroController.getHeroInfo(hero, heroId).ngLevel; bytes32 heroPackedIdValue = PackingLib.packNftIdWithValue(hero, heroId, ngLevel); EnumerableMap.Bytes32ToUintMap storage customData = s.heroCustomDataV2[heroPackedIdValue]; // set initial karma customData.set(KARMA_HASH, DEFAULT_KARMA_VALUE); emit IApplicationEvents.HeroCustomDataChangedNg(hero, heroId, KARMA_HASH, DEFAULT_KARMA_VALUE, ngLevel); // HERO_CLASS_HASH is not used as custom data anymore, getter takes value directly from heroController // set death count value // customData[DEATH_COUNT_HASH] is initialized by 0 by default emit IApplicationEvents.HeroCustomDataChangedNg(hero, heroId, DEATH_COUNT_HASH, 0, ngLevel); } function _initCoreAndAttributes(IStatController.MainState storage s, bytes32 heroPackedId, uint heroClass) internal returns ( uint32[] memory baseStats ){ _initNewHeroCore(s, heroPackedId, heroClass); bytes32[] storage totalAttributes = s.heroTotalAttributes[heroPackedId]; return StatLib.initAttributes(totalAttributes, heroClass, 1, heroClass.initialHero().core); } function _initNewHeroCore(IStatController.MainState storage s, bytes32 heroPackedId, uint heroClass) internal { IStatController.CoreAttributes memory initialCore = heroClass.initialHero().core; int32[] memory arr = new int32[](4); arr[0] = int32(initialCore.strength); arr[1] = int32(initialCore.dexterity); arr[2] = int32(initialCore.vitality); arr[3] = int32(initialCore.energy); s._heroCore[heroPackedId] = PackingLib.packInt32Array(arr); } function _changeChangeableStats( IStatController.MainState storage s, bytes32 heroPackedId, uint32 level, uint32 experience, uint32 life, uint32 mana, uint32 lifeChances ) internal { if (lifeChances != 0 && life == 0) { life = 1; } uint32[] memory data = new uint32[](5); data[0] = level; data[1] = experience; data[2] = life; data[3] = mana; data[4] = lifeChances; s.heroStats[heroPackedId] = PackingLib.packUint32Array(data); } /// @notice Add/remove the item to/from the hero function changeHeroItemSlot( IStatController.MainState storage s, IController controller, address heroToken, uint64 heroTokenId, uint itemType, uint8 itemSlot, address itemToken, uint itemTokenId, bool equip ) internal { onlyItemController(controller); if (!isItemTypeEligibleToItemSlot(itemType, itemSlot)) revert IAppErrors.ErrorItemNotEligibleForTheSlot(itemType, itemSlot); // if we are going to take an item by two hands, we need both hands free. // if we are going to use only one hand, we shouldn't keep anything by two hands if (itemSlot == uint(IStatController.ItemSlots.TWO_HAND)) { if (heroItemSlot(s, heroToken, heroTokenId, uint8(IStatController.ItemSlots.RIGHT_HAND)) != bytes32(0) || heroItemSlot(s, heroToken, heroTokenId, uint8(IStatController.ItemSlots.LEFT_HAND)) != bytes32(0)) { revert IAppErrors.ErrorItemSlotBusyHand(itemSlot); } } if (itemSlot == uint(IStatController.ItemSlots.RIGHT_HAND) || itemSlot == uint(IStatController.ItemSlots.LEFT_HAND)) { if (heroItemSlot(s, heroToken, heroTokenId, uint8(IStatController.ItemSlots.TWO_HAND)) != bytes32(0)) { revert IAppErrors.ErrorItemSlotBusyHand(itemSlot); } } bytes32 heroPackedId = PackingLib.packNftId(heroToken, heroTokenId); (address equippedItem, uint equippedItemId) = PackingLib.unpackNftId(heroItemSlot(s, heroToken, heroTokenId, itemSlot)); if (equip) { if (equippedItem != address(0)) revert IAppErrors.ErrorItemSlotBusy(); s.heroSlots[PackingLib.packMapObject(heroToken, uint64(heroTokenId), itemSlot)] = PackingLib.packNftId(itemToken, itemTokenId); s.heroBusySlots[heroPackedId] = PackingLib.changeUnit8ArrayWithCheck(s.heroBusySlots[heroPackedId], itemSlot, 1, 0); } else { if (equippedItem != itemToken || equippedItemId != itemTokenId) revert IAppErrors.ErrorItemNotInSlot(); delete s.heroSlots[PackingLib.packMapObject(heroToken, uint64(heroTokenId), itemSlot)]; s.heroBusySlots[heroPackedId] = PackingLib.changeUnit8ArrayWithCheck(s.heroBusySlots[heroPackedId], itemSlot, 0, 1); } emit IApplicationEvents.HeroItemSlotChanged(heroToken, heroTokenId, itemType, itemSlot, itemToken, itemTokenId, equip, msg.sender); } /// @notice Increase or decrease stats (life, mana, lifeChances). Experience can be increased only. function changeCurrentStats( IStatController.MainState storage s, IController c, address heroToken, uint heroTokenId, IStatController.ChangeableStats memory change, bool increase ) internal { onlyRegisteredContract(c); bytes32 heroPackedId = PackingLib.packNftId(heroToken, heroTokenId); IStatController.ChangeableStats memory currentStats = heroStats(s, heroToken, heroTokenId); uint32 life = currentStats.life; uint32 mana = currentStats.mana; uint32 lifeChances = currentStats.lifeChances; if (increase) { bytes32[] storage attrs = s.heroTotalAttributes[heroPackedId]; int32 maxLife = attrs.getInt32(uint(IStatController.ATTRIBUTES.LIFE)); int32 maxMana = attrs.getInt32(uint(IStatController.ATTRIBUTES.MANA)); int32 maxLC = attrs.getInt32(uint(IStatController.ATTRIBUTES.LIFE_CHANCES)); currentStats.experience += change.experience; life = uint32(Math.min(maxLife.toUint(), uint(life + change.life))); mana = uint32(Math.min(maxMana.toUint(), uint(mana + change.mana))); // Assume that Life Chances can be increased only by 1 per use. // Some stories and events can allow users to increase life chance above max... // Such attempts should be forbidden on UI side, we just silently ignore them here, no revert lifeChances = uint32(Math.min(maxLC.toUint(), uint(lifeChances + change.lifeChances))); } else { if (change.experience != 0) revert IAppErrors.ErrorExperienceMustNotDecrease(); life = life > change.life ? life - change.life : 0; lifeChances = lifeChances > change.lifeChances ? lifeChances - change.lifeChances : 0; mana = mana > change.mana ? mana - change.mana : 0; } _changeChangeableStats(s, heroPackedId, currentStats.level, currentStats.experience, life, mana, lifeChances); emit IApplicationEvents.CurrentStatsChanged(heroToken, heroTokenId, change, increase, msg.sender); } /// @notice Mark consumable {item} as used function registerConsumableUsage( IStatController.MainState storage s, IController c, address heroToken, uint heroTokenId, address item ) internal { onlyRegisteredContract(c); if (!s.usedConsumables[PackingLib.packNftId(heroToken, heroTokenId)].add(item)) revert IAppErrors.ErrorConsumableItemIsUsed(item); emit IApplicationEvents.ConsumableUsed(heroToken, heroTokenId, item); } /// @notice Clear all consumable items of the given hero function clearUsedConsumables( IStatController.MainState storage s, IController c, address heroToken, uint heroTokenId ) internal { onlyRegisteredContract(c); EnumerableSet.AddressSet storage items = s.usedConsumables[PackingLib.packNftId(heroToken, heroTokenId)]; uint length = items.length(); for (uint i; i < length; ++i) { // we are removing the first element, so it's safe to use in cycle address item = items.at(0); if (!items.remove(item)) revert IAppErrors.ErrorCannotRemoveItemFromMap(); emit IApplicationEvents.RemoveConsumableUsage(heroToken, heroTokenId, item); } } /// @notice Increase or decrease values of the given attributes, any attributes are allowed. /// @dev If a core attribute is changed than depended attributes are recalculated function changeBonusAttributes( IStatController.MainState storage s, IController c, IStatController.ChangeAttributesInfo memory info ) internal { IHeroController heroController = onlyRegisteredContract(c); bytes32 heroPackedId = PackingLib.packNftId(info.heroToken, info.heroTokenId); IStatController.ChangeableStats memory stats = heroStats(s, info.heroToken, info.heroTokenId); bytes32[] storage totalAttributes = s.heroTotalAttributes[heroPackedId]; (bytes32[] storage bonusMain, bytes32[] storage bonusExtra) = info.temporally ? (s.heroTemporallyAttributes[heroPackedId], s.heroBonusAttributes[heroPackedId]) : (s.heroBonusAttributes[heroPackedId], s.heroTemporallyAttributes[heroPackedId]); int32[] memory cachedTotalAttrChanged = new int32[](info.changeAttributes.length); for (uint i; i < info.changeAttributes.length; ++i) { int32 change = info.changeAttributes[i]; if (change != 0) { int32 newTotalValue; if (info.add) { bonusMain.changeInt32(i, change); newTotalValue = totalAttributes.getInt32(i) + change; } else { bonusMain.changeInt32(i, - change); newTotalValue = totalAttributes.getInt32(i) - change; } // todo in some cases value stored here to totalAttributes will be overwritten below by updateCoreDependAttributes // it happens if core attribute is changed AND it's depend attribute is change too // values of the depend attribute will be overwritten by updateCoreDependAttributes // fix it together with PACKED WRITING totalAttributes.setInt32(i, newTotalValue); cachedTotalAttrChanged[i] = newTotalValue; } } _updateCoreDependAttributes(heroController.heroClass(info.heroToken), totalAttributes, bonusMain, bonusExtra, stats, cachedTotalAttrChanged, info.changeAttributes); _compareStatsWithAttributes(s, heroPackedId, totalAttributes, stats); emit IApplicationEvents.BonusAttributesChanged(info.heroToken, info.heroTokenId, info.add, info.temporally, msg.sender); } /// @dev Make sure we don't have life/mana more than total attributes after decreasing function _compareStatsWithAttributes( IStatController.MainState storage s, bytes32 heroPackedId, bytes32[] storage totalAttributes, IStatController.ChangeableStats memory curStats ) internal { uint life = totalAttributes.getInt32(uint(IStatController.ATTRIBUTES.LIFE)).toUint(); uint mana = totalAttributes.getInt32(uint(IStatController.ATTRIBUTES.MANA)).toUint(); bool changed; if (life < curStats.life) { curStats.life = uint32(Math.min(life, curStats.life)); changed = true; } if (mana < curStats.mana) { curStats.mana = uint32(Math.min(mana, curStats.mana)); changed = true; } if (changed) { _changeChangeableStats(s, heroPackedId, curStats.level, curStats.experience, curStats.life, curStats.mana, curStats.lifeChances ); } } function clearTemporallyAttributes( IStatController.MainState storage s, IController c, address heroToken, uint heroTokenId ) internal { IHeroController heroController = onlyRegisteredContract(c); bytes32 heroPackedId = PackingLib.packNftId(heroToken, heroTokenId); bytes32[] memory tmpBonuses = s.heroTemporallyAttributes[heroPackedId]; IStatController.ChangeableStats memory stats = heroStats(s, heroToken, heroTokenId); bytes32[] storage bonus = s.heroBonusAttributes[heroPackedId]; bytes32[] storage totalAttributes = s.heroTotalAttributes[heroPackedId]; int32[] memory baseValues = new int32[](uint(IStatController.ATTRIBUTES.END_SLOT)); int32[] memory tmpBonusesUnpacked = new int32[](uint(IStatController.ATTRIBUTES.END_SLOT)); for (uint i; i < uint(IStatController.ATTRIBUTES.END_SLOT); ++i) { int32 value = tmpBonuses.getInt32Memory(i); if (value != int32(0)) { (baseValues[i],) = totalAttributes.changeInt32(i, - int32(uint32(value))); tmpBonusesUnpacked[i] = value; } } delete s.heroTemporallyAttributes[heroPackedId]; bytes32[] storage tmpBonusesStorage = s.heroTemporallyAttributes[heroPackedId]; _updateCoreDependAttributes(heroController.heroClass(heroToken), totalAttributes, bonus, tmpBonusesStorage, stats, baseValues, tmpBonusesUnpacked); _compareStatsWithAttributes(s, heroPackedId, totalAttributes, stats); emit IApplicationEvents.TemporallyAttributesCleared(heroToken, heroTokenId, msg.sender); } /// @dev Update depend-values for all changed attributes function _updateCoreDependAttributes( uint heroClass, bytes32[] storage totalAttributes, bytes32[] storage bonusMain, bytes32[] storage bonusExtra, IStatController.ChangeableStats memory stats, int32[] memory baseValues, int32[] memory changed ) internal { // handle core depend attributes in the second loop, totalAttributes should be updated together uint len = changed.length; for (uint i; i < len; ++i) { // depend-values should be recalculated if corresponded core value is changed (even if it's equal to 0 now) if (changed[i] != 0) { StatLib.updateCoreDependAttributes(totalAttributes, bonusMain, bonusExtra, stats, i, heroClass, baseValues[i]); } } } function levelUp( IStatController.MainState storage s, IController c, address heroToken, uint heroTokenId, uint heroClass, IStatController.CoreAttributes memory change ) internal returns (uint newLvl) { onlyHeroController(c); bytes32 heroPackedId = PackingLib.packNftId(heroToken, heroTokenId); if (change.strength + change.dexterity + change.vitality + change.energy != LEVEL_UP_SUM) revert IAppErrors.ErrorWrongLevelUpSum(); IStatController.ChangeableStats memory currentStats = heroStats(s, heroToken, heroTokenId); if (currentStats.level >= StatLib.MAX_LEVEL) revert IAppErrors.ErrorMaxLevel(); if (currentStats.level.levelExperience() > currentStats.experience) revert IAppErrors.ErrorNotEnoughExperience(); currentStats.level++; { int32[] memory data = PackingLib.unpackInt32Array(s._heroCore[heroPackedId]); data[0] += change.strength; data[1] += change.dexterity; data[2] += change.vitality; data[3] += change.energy; s._heroCore[heroPackedId] = PackingLib.packInt32Array(data); } bytes32[] storage totalAttributes = s.heroTotalAttributes[heroPackedId]; { bytes32[] storage bonus = s.heroBonusAttributes[heroPackedId]; bytes32[] storage bonusTmp = s.heroTemporallyAttributes[heroPackedId]; // update _addCoreToTotal( heroClass, totalAttributes, bonus, bonusTmp, currentStats, change.strength, uint(IStatController.ATTRIBUTES.STRENGTH) ); _addCoreToTotal( heroClass, totalAttributes, bonus, bonusTmp, currentStats, change.dexterity, uint(IStatController.ATTRIBUTES.DEXTERITY) ); _addCoreToTotal( heroClass, totalAttributes, bonus, bonusTmp, currentStats, change.vitality, uint(IStatController.ATTRIBUTES.VITALITY) ); _addCoreToTotal( heroClass, totalAttributes, bonus, bonusTmp, currentStats, change.energy, uint(IStatController.ATTRIBUTES.ENERGY) ); } // setup new level and restore life/mana currentStats.life = uint32(totalAttributes.getInt32(uint(IStatController.ATTRIBUTES.LIFE)).toUint()); currentStats.mana = uint32(totalAttributes.getInt32(uint(IStatController.ATTRIBUTES.MANA)).toUint()); _changeChangeableStats( s, heroPackedId, currentStats.level, currentStats.experience, currentStats.life, currentStats.mana, currentStats.lifeChances ); emit IApplicationEvents.LevelUp(heroToken, heroTokenId, heroClass, change); return currentStats.level; } /// @notice scb-1009: Update current values of Life and mana during reinforcement as following: /// Reinforcement increases max value of life/mana on DELTA, current value of life/mana is increased on DELTA too /// @param prevAttributes Hero attributes before reinforcement function restoreLifeAndMana( IStatController.MainState storage s, IController c, address heroToken, uint heroTokenId, int32[] memory prevAttributes ) external { onlyRegisteredContract(c); IStatController.ChangeableStats memory currentStats = heroStats(s, heroToken, heroTokenId); bytes32 heroPackedId = PackingLib.packNftId(heroToken, heroTokenId); // assume here that totalAttributes were already updated during reinforcement // and so max values of life and mana were increased on delta1 and delta2 bytes32[] storage totalAttributes = s.heroTotalAttributes[heroPackedId]; // now increase current values of life and mana on delta1 and delta2 too currentStats.life += _getPositiveDelta(totalAttributes.getInt32(uint(IStatController.ATTRIBUTES.LIFE)), prevAttributes[uint(IStatController.ATTRIBUTES.LIFE)]); currentStats.mana += _getPositiveDelta(totalAttributes.getInt32(uint(IStatController.ATTRIBUTES.MANA)), prevAttributes[uint(IStatController.ATTRIBUTES.MANA)]); _changeChangeableStats( s, heroPackedId, currentStats.level, currentStats.experience, currentStats.life, currentStats.mana, currentStats.lifeChances ); } function _getPositiveDelta(int32 a, int32 b) internal pure returns (uint32) { return a < b ? 0 : uint32(uint(int(a - b))); } function _addCoreToTotal( uint heroClass, bytes32[] storage totalAttributes, bytes32[] storage bonus, bytes32[] storage bonusTmp, IStatController.ChangeableStats memory stats, int32 changeValue, uint attrIndex ) internal { if (changeValue != 0) { (int32 newValue,) = totalAttributes.changeInt32(attrIndex, int32(uint32(changeValue))); StatLib.updateCoreDependAttributes(totalAttributes, bonus, bonusTmp, stats, attrIndex, heroClass, newValue); } } function setHeroCustomData( IStatController.MainState storage s, IController c, address token, uint tokenId, bytes32 index, uint value ) internal { IHeroController heroController = onlyRegisteredContract(c); uint8 ngLevel = heroController.getHeroInfo(token, tokenId).ngLevel; if (index == KARMA_HASH && value == 0) { revert IAppErrors.ErrorZeroKarmaNotAllowed(); } s.heroCustomDataV2[PackingLib.packNftIdWithValue(token, tokenId, ngLevel)].set(index, value); emit IApplicationEvents.HeroCustomDataChangedNg(token, tokenId, index, value, ngLevel); } function setGlobalCustomData( IStatController.MainState storage s, IController c, bytes32 index, uint value ) internal { onlyRegisteredContract(c); s.globalCustomData[index] = value; emit IApplicationEvents.GlobalCustomDataChanged(index, value); } //endregion ------------------------ ACTIONS }
// 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( bytes32[] storage attributes, bytes32[] storage bonusMain, bytes32[] storage bonusExtra, IStatController.ChangeableStats memory _heroStats, uint index, uint heroClass, int32 base ) internal { 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 }
// 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; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableMap.js. pragma solidity ^0.8.20; import {EnumerableSet} from "./EnumerableSet.sol"; /** * @dev Library for managing an enumerable variant of Solidity's * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`] * type. * * Maps have the following properties: * * - Entries are added, removed, and checked for existence in constant time * (O(1)). * - Entries are enumerated in O(n). No guarantees are made on the ordering. * * ```solidity * contract Example { * // Add the library methods * using EnumerableMap for EnumerableMap.UintToAddressMap; * * // Declare a set state variable * EnumerableMap.UintToAddressMap private myMap; * } * ``` * * The following map types are supported: * * - `uint256 -> address` (`UintToAddressMap`) since v3.0.0 * - `address -> uint256` (`AddressToUintMap`) since v4.6.0 * - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0 * - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0 * - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0 * * [WARNING] * ==== * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure * unusable. * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. * * In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an * array of EnumerableMap. * ==== */ library EnumerableMap { using EnumerableSet for EnumerableSet.Bytes32Set; // To implement this library for multiple types with as little code repetition as possible, we write it in // terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions, // and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map. // This means that we can only create new EnumerableMaps for types that fit in bytes32. /** * @dev Query for a nonexistent map key. */ error EnumerableMapNonexistentKey(bytes32 key); struct Bytes32ToBytes32Map { // Storage of keys EnumerableSet.Bytes32Set _keys; mapping(bytes32 key => bytes32) _values; } /** * @dev Adds a key-value pair to a map, or updates the value for an existing * key. O(1). * * Returns true if the key was added to the map, that is if it was not * already present. */ function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) { map._values[key] = value; return map._keys.add(key); } /** * @dev Removes a key-value pair from a map. O(1). * * Returns true if the key was removed from the map, that is if it was present. */ function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) { delete map._values[key]; return map._keys.remove(key); } /** * @dev Returns true if the key is in the map. O(1). */ function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) { return map._keys.contains(key); } /** * @dev Returns the number of key-value pairs in the map. O(1). */ function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) { return map._keys.length(); } /** * @dev Returns the key-value pair stored at position `index` in the map. O(1). * * Note that there are no guarantees on the ordering of entries inside the * array, and it may change when more entries are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) { bytes32 key = map._keys.at(index); return (key, map._values[key]); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) { bytes32 value = map._values[key]; if (value == bytes32(0)) { return (contains(map, key), bytes32(0)); } else { return (true, value); } } /** * @dev Returns the value associated with `key`. O(1). * * Requirements: * * - `key` must be in the map. */ function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) { bytes32 value = map._values[key]; if (value == 0 && !contains(map, key)) { revert EnumerableMapNonexistentKey(key); } return value; } /** * @dev Return the an array containing all the keys * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. */ function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) { return map._keys.values(); } // UintToUintMap struct UintToUintMap { Bytes32ToBytes32Map _inner; } /** * @dev Adds a key-value pair to a map, or updates the value for an existing * key. O(1). * * Returns true if the key was added to the map, that is if it was not * already present. */ function set(UintToUintMap storage map, uint256 key, uint256 value) internal returns (bool) { return set(map._inner, bytes32(key), bytes32(value)); } /** * @dev Removes a value from a map. O(1). * * Returns true if the key was removed from the map, that is if it was present. */ function remove(UintToUintMap storage map, uint256 key) internal returns (bool) { return remove(map._inner, bytes32(key)); } /** * @dev Returns true if the key is in the map. O(1). */ function contains(UintToUintMap storage map, uint256 key) internal view returns (bool) { return contains(map._inner, bytes32(key)); } /** * @dev Returns the number of elements in the map. O(1). */ function length(UintToUintMap storage map) internal view returns (uint256) { return length(map._inner); } /** * @dev Returns the element stored at position `index` in the map. O(1). * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) { (bytes32 key, bytes32 value) = at(map._inner, index); return (uint256(key), uint256(value)); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) { (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); return (success, uint256(value)); } /** * @dev Returns the value associated with `key`. O(1). * * Requirements: * * - `key` must be in the map. */ function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) { return uint256(get(map._inner, bytes32(key))); } /** * @dev Return the an array containing all the keys * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. */ function keys(UintToUintMap storage map) internal view returns (uint256[] memory) { bytes32[] memory store = keys(map._inner); uint256[] memory result; /// @solidity memory-safe-assembly assembly { result := store } return result; } // UintToAddressMap struct UintToAddressMap { Bytes32ToBytes32Map _inner; } /** * @dev Adds a key-value pair to a map, or updates the value for an existing * key. O(1). * * Returns true if the key was added to the map, that is if it was not * already present. */ function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) { return set(map._inner, bytes32(key), bytes32(uint256(uint160(value)))); } /** * @dev Removes a value from a map. O(1). * * Returns true if the key was removed from the map, that is if it was present. */ function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) { return remove(map._inner, bytes32(key)); } /** * @dev Returns true if the key is in the map. O(1). */ function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) { return contains(map._inner, bytes32(key)); } /** * @dev Returns the number of elements in the map. O(1). */ function length(UintToAddressMap storage map) internal view returns (uint256) { return length(map._inner); } /** * @dev Returns the element stored at position `index` in the map. O(1). * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) { (bytes32 key, bytes32 value) = at(map._inner, index); return (uint256(key), address(uint160(uint256(value)))); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) { (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); return (success, address(uint160(uint256(value)))); } /** * @dev Returns the value associated with `key`. O(1). * * Requirements: * * - `key` must be in the map. */ function get(UintToAddressMap storage map, uint256 key) internal view returns (address) { return address(uint160(uint256(get(map._inner, bytes32(key))))); } /** * @dev Return the an array containing all the keys * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. */ function keys(UintToAddressMap storage map) internal view returns (uint256[] memory) { bytes32[] memory store = keys(map._inner); uint256[] memory result; /// @solidity memory-safe-assembly assembly { result := store } return result; } // AddressToUintMap struct AddressToUintMap { Bytes32ToBytes32Map _inner; } /** * @dev Adds a key-value pair to a map, or updates the value for an existing * key. O(1). * * Returns true if the key was added to the map, that is if it was not * already present. */ function set(AddressToUintMap storage map, address key, uint256 value) internal returns (bool) { return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value)); } /** * @dev Removes a value from a map. O(1). * * Returns true if the key was removed from the map, that is if it was present. */ function remove(AddressToUintMap storage map, address key) internal returns (bool) { return remove(map._inner, bytes32(uint256(uint160(key)))); } /** * @dev Returns true if the key is in the map. O(1). */ function contains(AddressToUintMap storage map, address key) internal view returns (bool) { return contains(map._inner, bytes32(uint256(uint160(key)))); } /** * @dev Returns the number of elements in the map. O(1). */ function length(AddressToUintMap storage map) internal view returns (uint256) { return length(map._inner); } /** * @dev Returns the element stored at position `index` in the map. O(1). * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) { (bytes32 key, bytes32 value) = at(map._inner, index); return (address(uint160(uint256(key))), uint256(value)); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) { (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key)))); return (success, uint256(value)); } /** * @dev Returns the value associated with `key`. O(1). * * Requirements: * * - `key` must be in the map. */ function get(AddressToUintMap storage map, address key) internal view returns (uint256) { return uint256(get(map._inner, bytes32(uint256(uint160(key))))); } /** * @dev Return the an array containing all the keys * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. */ function keys(AddressToUintMap storage map) internal view returns (address[] memory) { bytes32[] memory store = keys(map._inner); address[] memory result; /// @solidity memory-safe-assembly assembly { result := store } return result; } // Bytes32ToUintMap struct Bytes32ToUintMap { Bytes32ToBytes32Map _inner; } /** * @dev Adds a key-value pair to a map, or updates the value for an existing * key. O(1). * * Returns true if the key was added to the map, that is if it was not * already present. */ function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) { return set(map._inner, key, bytes32(value)); } /** * @dev Removes a value from a map. O(1). * * Returns true if the key was removed from the map, that is if it was present. */ function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) { return remove(map._inner, key); } /** * @dev Returns true if the key is in the map. O(1). */ function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) { return contains(map._inner, key); } /** * @dev Returns the number of elements in the map. O(1). */ function length(Bytes32ToUintMap storage map) internal view returns (uint256) { return length(map._inner); } /** * @dev Returns the element stored at position `index` in the map. O(1). * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) { (bytes32 key, bytes32 value) = at(map._inner, index); return (key, uint256(value)); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) { (bool success, bytes32 value) = tryGet(map._inner, key); return (success, uint256(value)); } /** * @dev Returns the value associated with `key`. O(1). * * Requirements: * * - `key` must be in the map. */ function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) { return uint256(get(map._inner, key)); } /** * @dev Return the an array containing all the keys * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. */ function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) { bytes32[] memory store = keys(map._inner); bytes32[] memory result; /// @solidity memory-safe-assembly assembly { result := store } return result; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. pragma solidity ^0.8.20; /** * @dev Library for managing * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive * types. * * Sets have the following properties: * * - Elements are added, removed, and checked for existence in constant time * (O(1)). * - Elements are enumerated in O(n). No guarantees are made on the ordering. * * ```solidity * contract Example { * // Add the library methods * using EnumerableSet for EnumerableSet.AddressSet; * * // Declare a set state variable * EnumerableSet.AddressSet private mySet; * } * ``` * * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) * and `uint256` (`UintSet`) are supported. * * [WARNING] * ==== * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure * unusable. * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. * * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an * array of EnumerableSet. * ==== */ library EnumerableSet { // To implement this library for multiple types with as little code // repetition as possible, we write it in terms of a generic Set type with // bytes32 values. // The Set implementation uses private functions, and user-facing // implementations (such as AddressSet) are just wrappers around the // underlying Set. // This means that we can only create new EnumerableSets for types that fit // in bytes32. struct Set { // Storage of set values bytes32[] _values; // Position is the index of the value in the `values` array plus 1. // Position 0 is used to mean a value is not in the set. mapping(bytes32 value => uint256) _positions; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function _add(Set storage set, bytes32 value) private returns (bool) { if (!_contains(set, value)) { set._values.push(value); // The value is stored at length-1, but we add 1 to all indexes // and use 0 as a sentinel value set._positions[value] = set._values.length; return true; } else { return false; } } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function _remove(Set storage set, bytes32 value) private returns (bool) { // We cache the value's position to prevent multiple reads from the same storage slot uint256 position = set._positions[value]; if (position != 0) { // Equivalent to contains(set, value) // To delete an element from the _values array in O(1), we swap the element to delete with the last one in // the array, and then remove the last element (sometimes called as 'swap and pop'). // This modifies the order of the array, as noted in {at}. uint256 valueIndex = position - 1; uint256 lastIndex = set._values.length - 1; if (valueIndex != lastIndex) { bytes32 lastValue = set._values[lastIndex]; // Move the lastValue to the index where the value to delete is set._values[valueIndex] = lastValue; // Update the tracked position of the lastValue (that was just moved) set._positions[lastValue] = position; } // Delete the slot where the moved value was stored set._values.pop(); // Delete the tracked position for the deleted slot delete set._positions[value]; return true; } else { return false; } } /** * @dev Returns true if the value is in the set. O(1). */ function _contains(Set storage set, bytes32 value) private view returns (bool) { return set._positions[value] != 0; } /** * @dev Returns the number of values on the set. O(1). */ function _length(Set storage set) private view returns (uint256) { return set._values.length; } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function _at(Set storage set, uint256 index) private view returns (bytes32) { return set._values[index]; } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function _values(Set storage set) private view returns (bytes32[] memory) { return set._values; } // Bytes32Set struct Bytes32Set { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _add(set._inner, value); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _remove(set._inner, value); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { return _contains(set._inner, value); } /** * @dev Returns the number of values in the set. O(1). */ function length(Bytes32Set storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { return _at(set._inner, index); } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { bytes32[] memory store = _values(set._inner); bytes32[] memory result; /// @solidity memory-safe-assembly assembly { result := store } return result; } // AddressSet struct AddressSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(AddressSet storage set, address value) internal returns (bool) { return _add(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(AddressSet storage set, address value) internal returns (bool) { return _remove(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(AddressSet storage set, address value) internal view returns (bool) { return _contains(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Returns the number of values in the set. O(1). */ function length(AddressSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(AddressSet storage set, uint256 index) internal view returns (address) { return address(uint160(uint256(_at(set._inner, index)))); } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function values(AddressSet storage set) internal view returns (address[] memory) { bytes32[] memory store = _values(set._inner); address[] memory result; /// @solidity memory-safe-assembly assembly { result := store } return result; } // UintSet struct UintSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(UintSet storage set, uint256 value) internal returns (bool) { return _add(set._inner, bytes32(value)); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(UintSet storage set, uint256 value) internal returns (bool) { return _remove(set._inner, bytes32(value)); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(UintSet storage set, uint256 value) internal view returns (bool) { return _contains(set._inner, bytes32(value)); } /** * @dev Returns the number of values in the set. O(1). */ function length(UintSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(UintSet storage set, uint256 index) internal view returns (uint256) { return uint256(_at(set._inner, index)); } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function values(UintSet storage set) internal view returns (uint256[] memory) { bytes32[] memory store = _values(set._inner); uint256[] memory result; /// @solidity memory-safe-assembly assembly { result := store } return result; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../interfaces/IERC721Receiver.sol"; /** * @dev Implementation of the {IERC721Receiver} interface. * * Accepts all token transfers. * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}. */ contract ERC721Holder is IERC721Receiver { /** * @dev See {IERC721Receiver-onERC721Received}. * * Always returns `IERC721Receiver.onERC721Received.selector`. */ function onERC721Received( address, address, uint256, bytes memory ) public virtual override returns (bytes4) { return this.onERC721Received.selector; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol) pragma solidity ^0.8.20; /** * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. * * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in * case an upgrade adds a module that needs to be initialized. * * For example: * * [.hljs-theme-light.nopadding] * ```solidity * contract MyToken is ERC20Upgradeable { * function initialize() initializer public { * __ERC20_init("MyToken", "MTK"); * } * } * * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { * function initializeV2() reinitializer(2) public { * __ERC20Permit_init("MyToken"); * } * } * ``` * * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. * * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. * * [CAUTION] * ==== * Avoid leaving a contract uninitialized. * * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: * * [.hljs-theme-light.nopadding] * ``` * /// @custom:oz-upgrades-unsafe-allow constructor * constructor() { * _disableInitializers(); * } * ``` * ==== */ abstract contract Initializable { /** * @dev Storage of the initializable contract. * * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions * when using with upgradeable contracts. * * @custom:storage-location erc7201:openzeppelin.storage.Initializable */ struct InitializableStorage { /** * @dev Indicates that the contract has been initialized. */ uint64 _initialized; /** * @dev Indicates that the contract is in the process of being initialized. */ bool _initializing; } // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00; /** * @dev The contract is already initialized. */ error InvalidInitialization(); /** * @dev The contract is not initializing. */ error NotInitializing(); /** * @dev Triggered when the contract has been initialized or reinitialized. */ event Initialized(uint64 version); /** * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, * `onlyInitializing` functions can be used to initialize parent contracts. * * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in * production. * * Emits an {Initialized} event. */ modifier initializer() { // solhint-disable-next-line var-name-mixedcase InitializableStorage storage $ = _getInitializableStorage(); // Cache values to avoid duplicated sloads bool isTopLevelCall = !$._initializing; uint64 initialized = $._initialized; // Allowed calls: // - initialSetup: the contract is not in the initializing state and no previous version was // initialized // - construction: the contract is initialized at version 1 (no reininitialization) and the // current contract is just being deployed bool initialSetup = initialized == 0 && isTopLevelCall; bool construction = initialized == 1 && address(this).code.length == 0; if (!initialSetup && !construction) { revert InvalidInitialization(); } $._initialized = 1; if (isTopLevelCall) { $._initializing = true; } _; if (isTopLevelCall) { $._initializing = false; emit Initialized(1); } } /** * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be * used to initialize parent contracts. * * A reinitializer may be used after the original initialization step. This is essential to configure modules that * are added through upgrades and that require initialization. * * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` * cannot be nested. If one is invoked in the context of another, execution will revert. * * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in * a contract, executing them in the right order is up to the developer or operator. * * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization. * * Emits an {Initialized} event. */ modifier reinitializer(uint64 version) { // solhint-disable-next-line var-name-mixedcase InitializableStorage storage $ = _getInitializableStorage(); if ($._initializing || $._initialized >= version) { revert InvalidInitialization(); } $._initialized = version; $._initializing = true; _; $._initializing = false; emit Initialized(version); } /** * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the * {initializer} and {reinitializer} modifiers, directly or indirectly. */ modifier onlyInitializing() { _checkInitializing(); _; } /** * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}. */ function _checkInitializing() internal view virtual { if (!_isInitializing()) { revert NotInitializing(); } } /** * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized * to any version. It is recommended to use this to lock implementation contracts that are designed to be called * through proxies. * * Emits an {Initialized} event the first time it is successfully executed. */ function _disableInitializers() internal virtual { // solhint-disable-next-line var-name-mixedcase InitializableStorage storage $ = _getInitializableStorage(); if ($._initializing) { revert InvalidInitialization(); } if ($._initialized != type(uint64).max) { $._initialized = type(uint64).max; emit Initialized(type(uint64).max); } } /** * @dev Returns the highest version that has been initialized. See {reinitializer}. */ function _getInitializedVersion() internal view returns (uint64) { return _getInitializableStorage()._initialized; } /** * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}. */ function _isInitializing() internal view returns (bool) { return _getInitializableStorage()._initializing; } /** * @dev Returns a pointer to the storage namespace. */ // solhint-disable-next-line var-name-mixedcase function _getInitializableStorage() private pure returns (InitializableStorage storage $) { assembly { $.slot := INITIALIZABLE_STORAGE } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol) pragma solidity ^0.8.20; /** * @dev Standard math utilities missing in the Solidity language. */ library Math { /** * @dev Muldiv operation overflow. */ error MathOverflowedMulDiv(); enum Rounding { Floor, // Toward negative infinity Ceil, // Toward positive infinity Trunc, // Toward zero Expand // Away from zero } /** * @dev Returns the addition of two unsigned integers, with an success flag (no overflow). */ function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { uint256 c = a + b; if (c < a) return (false, 0); return (true, c); } } /** * @dev Returns the subtraction of two unsigned integers, with an success flag (no overflow). */ function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { if (b > a) return (false, 0); return (true, a - b); } } /** * @dev Returns the multiplication of two unsigned integers, with an success flag (no overflow). */ function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) return (true, 0); uint256 c = a * b; if (c / a != b) return (false, 0); return (true, c); } } /** * @dev Returns the division of two unsigned integers, with a success flag (no division by zero). */ function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { if (b == 0) return (false, 0); return (true, a / b); } } /** * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero). */ function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { if (b == 0) return (false, 0); return (true, a % b); } } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } /** * @dev Returns the average of two numbers. The result is rounded towards * zero. */ function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow. return (a & b) + (a ^ b) / 2; } /** * @dev Returns the ceiling of the division of two numbers. * * This differs from standard division with `/` in that it rounds towards infinity instead * of rounding towards zero. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { if (b == 0) { // Guarantee the same behavior as in a regular Solidity division. return a / b; } // The following calculation ensures accurate ceiling division without overflow. // Since a is non-zero, (a - 1) / b will not overflow. // The largest possible result occurs when (a - 1) / b is type(uint256).max, // but the largest value we can obtain is type(uint256).max - 1, which happens // when a = type(uint256).max and b = 1. unchecked { return a == 0 ? 0 : (a - 1) / b + 1; } } /** * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or * denominator == 0. * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by * Uniswap Labs also under MIT license. */ function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. uint256 prod0 = x * y; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { // Solidity will revert if denominator == 0, unlike the div opcode on its own. // The surrounding unchecked block does not change this fact. // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. return prod0 / denominator; } // Make sure the result is less than 2^256. Also prevents denominator == 0. if (denominator <= prod1) { revert MathOverflowedMulDiv(); } /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. // Always >= 1. See https://cs.stackexchange.com/q/138556/92363. uint256 twos = denominator & (0 - denominator); assembly { // Divide denominator by twos. denominator := div(denominator, twos) // Divide [prod1 prod0] by twos. prod0 := div(prod0, twos) // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. twos := add(div(sub(0, twos), twos), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * twos; // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also // works in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2^8 inverse *= 2 - denominator * inverse; // inverse mod 2^16 inverse *= 2 - denominator * inverse; // inverse mod 2^32 inverse *= 2 - denominator * inverse; // inverse mod 2^64 inverse *= 2 - denominator * inverse; // inverse mod 2^128 inverse *= 2 - denominator * inverse; // inverse mod 2^256 // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /** * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { result += 1; } return result; } /** * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded * towards zero. * * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ function sqrt(uint256 a) internal pure returns (uint256) { if (a == 0) { return 0; } // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. // // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. // // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` // // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1 << (log2(a) >> 1); // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision // into the expected uint128 result. unchecked { result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; return min(result, a / result); } } /** * @notice Calculates sqrt(a), following the selected rounding direction. */ function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); } } /** * @dev Return the log in base 2 of a positive value rounded towards zero. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 128; } if (value >> 64 > 0) { value >>= 64; result += 64; } if (value >> 32 > 0) { value >>= 32; result += 32; } if (value >> 16 > 0) { value >>= 16; result += 16; } if (value >> 8 > 0) { value >>= 8; result += 8; } if (value >> 4 > 0) { value >>= 4; result += 4; } if (value >> 2 > 0) { value >>= 2; result += 2; } if (value >> 1 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 2, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); } } /** * @dev Return the log in base 10 of a positive value rounded towards zero. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >= 10 ** 64) { value /= 10 ** 64; result += 64; } if (value >= 10 ** 32) { value /= 10 ** 32; result += 32; } if (value >= 10 ** 16) { value /= 10 ** 16; result += 16; } if (value >= 10 ** 8) { value /= 10 ** 8; result += 8; } if (value >= 10 ** 4) { value /= 10 ** 4; result += 4; } if (value >= 10 ** 2) { value /= 10 ** 2; result += 2; } if (value >= 10 ** 1) { result += 1; } } return result; } /** * @dev Return the log in base 10, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); } } /** * @dev Return the log in base 256 of a positive value rounded towards zero. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. */ function log256(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 16; } if (value >> 64 > 0) { value >>= 64; result += 8; } if (value >> 32 > 0) { value >>= 32; result += 4; } if (value >> 16 > 0) { value >>= 16; result += 2; } if (value >> 8 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 256, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); } } /** * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. */ function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { return uint8(rounding) % 2 == 1; } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.23; import "../openzeppelin/Initializable.sol"; import "../interfaces/IControllable.sol"; import "../interfaces/IController.sol"; import "../lib/SlotsLib.sol"; /// @title Implement basic functionality for any contract that require strict control /// @dev Can be used with upgradeable pattern. /// Require call __Controllable_init() in any case. /// @author belbix abstract contract Controllable is Initializable, IControllable { using SlotsLib for bytes32; /// @notice Version of the contract /// @dev Should be incremented when contract changed string public constant CONTROLLABLE_VERSION = "1.0.0"; bytes32 internal constant _CONTROLLER_SLOT = bytes32(uint256(keccak256("eip1967.controllable.controller")) - 1); bytes32 internal constant _CREATED_SLOT = bytes32(uint256(keccak256("eip1967.controllable.created")) - 1); bytes32 internal constant _CREATED_BLOCK_SLOT = bytes32(uint256(keccak256("eip1967.controllable.created_block")) - 1); bytes32 internal constant _REVISION_SLOT = bytes32(uint256(keccak256("eip1967.controllable.revision")) - 1); bytes32 internal constant _PREVIOUS_LOGIC_SLOT = bytes32(uint256(keccak256("eip1967.controllable.prev_logic")) - 1); event ContractInitialized(address controller, uint ts, uint block); event RevisionIncreased(uint value, address oldLogic); // init implementation contract constructor() initializer {} /// @notice Initialize contract after setup it as proxy implementation /// Save block.timestamp in the "created" variable /// @dev Use it only once after first logic setup /// @param controller_ Controller address function __Controllable_init(address controller_) internal onlyInitializing { _init(controller_); } function _init(address controller_) private { require(controller_ != address(0), "Zero controller"); _CONTROLLER_SLOT.set(controller_); _CREATED_SLOT.set(block.timestamp); _CREATED_BLOCK_SLOT.set(block.number); emit ContractInitialized(controller_, block.timestamp, block.number); } /// @dev Return true if given address is controller function isController(address value_) public override view returns (bool) { return value_ == controller(); } /// @notice Return true if given address is setup as governance in Controller function isGovernance(address value_) public override view returns (bool) { return IController(controller()).governance() == value_; } /// @dev Contract upgrade counter function revision() external view override returns (uint) { return _REVISION_SLOT.getUint(); } /// @dev Previous logic implementation function previousImplementation() external view override returns (address) { return _PREVIOUS_LOGIC_SLOT.getAddress(); } // ************* SETTERS/GETTERS ******************* /// @notice Return controller address saved in the contract slot function controller() public view override returns (address) { return _CONTROLLER_SLOT.getAddress(); } /// @notice Return creation timestamp /// @return Creation timestamp function created() external view override returns (uint256) { return _CREATED_SLOT.getUint(); } /// @notice Return creation block number /// @return Creation block number function createdBlock() external override view returns (uint256) { return _CREATED_BLOCK_SLOT.getUint(); } /// @dev Revision should be increased on each contract upgrade function increaseRevision(address oldLogic) external override { require(msg.sender == address(this), "Increase revision forbidden"); uint r = _REVISION_SLOT.getUint() + 1; _REVISION_SLOT.set(r); _PREVIOUS_LOGIC_SLOT.set(oldLogic); emit RevisionIncreased(r, oldLogic); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (metatx/ERC2771Context.sol) pragma solidity ^0.8.1; import "../interfaces/IAppErrors.sol"; /** * @dev Context variant with ERC2771 support. */ // based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/metatx/ERC2771Context.sol abstract contract ERC2771Context { // for whitelist new relayers need to add new constants and update proxies address private constant GELATO_RELAY_1_BALANCE_ERC_2771 = 0xd8253782c45a12053594b9deB72d8e8aB2Fca54c; address private constant SACRA_RELAY = 0x52CEba41Da235Af367bFC0b0cCd3314cb901bB5F; address private constant SACRA_RELAY_2 = 0x102f1f556cD9C3D5f820E6920A8931657c5Da21B; function isTrustedForwarder(address forwarder) public view virtual returns (bool){ return forwarder == GELATO_RELAY_1_BALANCE_ERC_2771 || forwarder == SACRA_RELAY || forwarder == SACRA_RELAY_2; } function _msgSender() internal view virtual returns (address sender) { if (isTrustedForwarder(msg.sender)) { // The assembly code is more direct than the Solidity version using `abi.decode`. /// @solidity memory-safe-assembly assembly { sender := shr(96, calldataload(sub(calldatasize(), 20))) } return sender; } else { return msg.sender; } } function _msgData() internal view virtual returns (bytes calldata) { if (isTrustedForwarder(msg.sender)) { return msg.data[: msg.data.length - 20]; } else { return msg.data; } } /// @notice Return true if given address is not a smart contract but a wallet address. /// @dev It is not 100% guarantee after EIP-3074 implementation, use it as an additional check. /// @return true if the address is a wallet. function _isNotSmartContract() internal view returns (bool) { return isTrustedForwarder(msg.sender) || msg.sender == tx.origin; } function onlyEOA() internal view { if (!_isNotSmartContract()) { revert IAppErrors.NotEOA(msg.sender); } } }
// 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. } } }
{ "evmVersion": "istanbul", "optimizer": { "enabled": true, "runs": 50 }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "metadata": { "useLiteralContent": true }, "libraries": { "contracts/lib/DungeonFactoryLib.sol": { "DungeonFactoryLib": "0x2c550024df438312efd1804231d924ca59b60ca6" }, "contracts/lib/DungeonLib.sol": { "DungeonLib": "0x643d50456d451fae5ec11a9fe506c101adece96f" } } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"uint16","name":"dungNum","type":"uint16"}],"name":"DungeonAlreadySpecific","type":"error"},{"inputs":[{"internalType":"uint16","name":"dungNum","type":"uint16"}],"name":"DungeonAlreadySpecific2","type":"error"},{"inputs":[],"name":"ErrorDungeonCompleted","type":"error"},{"inputs":[],"name":"ErrorDungeonIsFreeAlready","type":"error"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"ErrorHeroIsDead","type":"error"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"}],"name":"ErrorHeroIsNotRegistered","type":"error"},{"inputs":[],"name":"ErrorHeroLevelStartFrom1","type":"error"},{"inputs":[],"name":"ErrorHeroNotInDungeon","type":"error"},{"inputs":[{"internalType":"uint256","name":"biome","type":"uint256"}],"name":"ErrorIncorrectBiome","type":"error"},{"inputs":[{"internalType":"uint256","name":"heroLevel","type":"uint256"}],"name":"ErrorLevelTooLow","type":"error"},{"inputs":[{"internalType":"uint8","name":"heroBiome","type":"uint8"}],"name":"ErrorNoDungeonsForBiome","type":"error"},{"inputs":[],"name":"ErrorNoEligibleDungeons","type":"error"},{"inputs":[],"name":"ErrorNotChances","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ErrorNotDeployer","type":"error"},{"inputs":[],"name":"ErrorNotObject1","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ErrorNotOwner","type":"error"},{"inputs":[],"name":"ErrorNotReady","type":"error"},{"inputs":[],"name":"ErrorNotStages","type":"error"},{"inputs":[],"name":"ErrorOnlyEoa","type":"error"},{"inputs":[],"name":"ErrorPaused","type":"error"},{"inputs":[{"internalType":"uint256","name":"heroLevel","type":"uint256"}],"name":"ErrorWrongLevel","type":"error"},{"inputs":[{"internalType":"uint256","name":"multiplier","type":"uint256"}],"name":"ErrorWrongMultiplier","type":"error"},{"inputs":[{"internalType":"uint256","name":"stage","type":"uint256"}],"name":"ErrorWrongStage","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint256","name":"length","type":"uint256"}],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"TooHighValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"TooLowX","type":"error"},{"inputs":[],"name":"WrongSpecificDungeon","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"dungId","type":"uint64"}],"name":"Clear","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":"uint16","name":"dungLogicId","type":"uint16"},{"components":[{"internalType":"uint8[][]","name":"objTypesByStages","type":"uint8[][]"},{"internalType":"uint32[][]","name":"objChancesByStages","type":"uint32[][]"},{"internalType":"uint32[]","name":"uniqObjects","type":"uint32[]"},{"internalType":"uint8","name":"minLevel","type":"uint8"},{"internalType":"uint8","name":"maxLevel","type":"uint8"},{"internalType":"bytes32[]","name":"requiredCustomDataIndex","type":"bytes32[]"},{"internalType":"uint64[]","name":"requiredCustomDataMinValue","type":"uint64[]"},{"internalType":"uint64[]","name":"requiredCustomDataMaxValue","type":"uint64[]"},{"internalType":"bool[]","name":"requiredCustomDataIsHero","type":"bool[]"}],"indexed":false,"internalType":"struct IDungeonFactory.DungeonGenerateInfo","name":"info","type":"tuple"}],"name":"DungeonLogicRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"dungLogicId","type":"uint16"}],"name":"DungeonLogicRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"dungLogicId","type":"uint16"},{"indexed":false,"internalType":"uint256","name":"biome","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"heroCls","type":"uint256"}],"name":"DungeonSpecificLogicRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"dungLogicId","type":"uint16"},{"indexed":false,"internalType":"uint256","name":"heroLvl","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"heroCls","type":"uint256"}],"name":"DungeonSpecificLogicRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"biome","type":"uint8"},{"indexed":false,"internalType":"uint64","name":"dungeonId","type":"uint64"}],"name":"FreeDungeonAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"level","type":"uint256"}],"name":"MinLevelForTreasuryChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"dungId","type":"uint64"},{"indexed":false,"internalType":"address","name":"hero","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint32","name":"objId","type":"uint32"},{"indexed":false,"internalType":"uint256","name":"iteration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"currentStage","type":"uint256"}],"name":"ObjectOpened","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"},{"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":[],"name":"controller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"created","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"createdBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"currentDungeon","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"dungLogicNum","type":"uint16"}],"name":"dungeonAttributes","outputs":[{"components":[{"internalType":"uint8","name":"stages","type":"uint8"},{"internalType":"uint8","name":"biome","type":"uint8"},{"internalType":"uint32[]","name":"uniqObjects","type":"uint32[]"},{"internalType":"bytes32","name":"minMaxLevel","type":"bytes32"},{"internalType":"bytes32[]","name":"requiredCustomDataIndex","type":"bytes32[]"},{"internalType":"bytes32[]","name":"requiredCustomDataValue","type":"bytes32[]"},{"components":[{"internalType":"bytes32[]","name":"objTypesByStages","type":"bytes32[]"},{"internalType":"uint32[][]","name":"objChancesByStages","type":"uint32[][]"}],"internalType":"struct IDungeonFactory.ObjectGenerateInfo","name":"info","type":"tuple"}],"internalType":"struct IDungeonFactory.DungeonAttributes","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dungeonCounter","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"dungeonId","type":"uint64"}],"name":"dungeonNgLevel","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"dungeonId","type":"uint64"}],"name":"dungeonStatus","outputs":[{"internalType":"uint16","name":"dungNum","type":"uint16"},{"internalType":"bool","name":"isCompleted","type":"bool"},{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"uint32","name":"currentObject","type":"uint32"},{"internalType":"uint8","name":"currentObjIndex","type":"uint8"},{"internalType":"address[]","name":"treasuryTokens","type":"address[]"},{"internalType":"uint256[]","name":"treasuryTokensAmounts","type":"uint256[]"},{"internalType":"bytes32[]","name":"treasuryItems","type":"bytes32[]"},{"internalType":"uint8","name":"stages","type":"uint8"},{"internalType":"uint32[]","name":"uniqObjects","type":"uint32[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"maxAvailableBiome_","type":"uint256"},{"internalType":"uint256","name":"treasuryBalance","type":"uint256"},{"internalType":"uint8","name":"heroLevel","type":"uint8"},{"internalType":"uint8","name":"dungeonBiome","type":"uint8"},{"internalType":"uint8","name":"maxOpenedNgLevel","type":"uint8"},{"internalType":"uint8","name":"heroNgLevel","type":"uint8"}],"name":"dungeonTreasuryReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"dungId","type":"uint64"}],"name":"emergencyExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"dungId","type":"uint64"},{"internalType":"address","name":"heroToken_","type":"address"},{"internalType":"uint256","name":"heroTokenId_","type":"uint256"}],"name":"enter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"dungId","type":"uint64"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"exit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"hero","type":"address"},{"internalType":"uint256","name":"heroId","type":"uint256"},{"internalType":"address","name":"msgSender","type":"address"}],"name":"exitForcibly","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"hero","type":"address"},{"internalType":"uint256","name":"heroId","type":"uint256"}],"name":"exitSuicide","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"biome","type":"uint256"}],"name":"freeDungeonsByLevel","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"biome","type":"uint256"}],"name":"freeDungeonsByLevelLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IController","name":"controller_","type":"address"},{"internalType":"uint8","name":"heroLevel","type":"uint8"},{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"uint256","name":"random","type":"uint256"}],"name":"getDungeonLogic","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"heroLevel","type":"uint256"},{"internalType":"uint256","name":"biome","type":"uint256"},{"internalType":"uint256","name":"heroNgLevel","type":"uint256"}],"name":"getDungeonTreasuryAmount","outputs":[{"internalType":"uint256","name":"totalAmount","type":"uint256"},{"internalType":"uint256","name":"amountForDungeon","type":"uint256"},{"internalType":"uint256","name":"mintAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"oldLogic","type":"address"}],"name":"increaseRevision","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"controller_","type":"address"}],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"isBiomeBoss","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"value_","type":"address"}],"name":"isController","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"dungeonLogic","type":"uint16"},{"internalType":"uint8","name":"heroLevel","type":"uint8"},{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"isDungeonEligibleForHero","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"value_","type":"address"}],"name":"isGovernance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"forwarder","type":"address"}],"name":"isTrustedForwarder","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"address","name":"treasuryToken","type":"address"}],"name":"launch","outputs":[{"internalType":"uint64","name":"dungeonId","type":"uint64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"address","name":"owner","type":"address"}],"name":"launchForNewHero","outputs":[{"internalType":"uint64","name":"dungeonId","type":"uint64"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maxAvailableBiome","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"maxBiomeCompleted","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"minLevelForTreasury","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"dungId","type":"uint64"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"objectAction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"dungId","type":"uint64"}],"name":"openObject","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"previousImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"reborn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"dungLogicId","type":"uint16"},{"internalType":"uint8","name":"biome","type":"uint8"},{"components":[{"internalType":"uint8[][]","name":"objTypesByStages","type":"uint8[][]"},{"internalType":"uint32[][]","name":"objChancesByStages","type":"uint32[][]"},{"internalType":"uint32[]","name":"uniqObjects","type":"uint32[]"},{"internalType":"uint8","name":"minLevel","type":"uint8"},{"internalType":"uint8","name":"maxLevel","type":"uint8"},{"internalType":"bytes32[]","name":"requiredCustomDataIndex","type":"bytes32[]"},{"internalType":"uint64[]","name":"requiredCustomDataMinValue","type":"uint64[]"},{"internalType":"uint64[]","name":"requiredCustomDataMaxValue","type":"uint64[]"},{"internalType":"bool[]","name":"requiredCustomDataIsHero","type":"bool[]"}],"internalType":"struct IDungeonFactory.DungeonGenerateInfo","name":"genInfo","type":"tuple"},{"internalType":"uint8","name":"specReqBiome","type":"uint8"},{"internalType":"uint8","name":"specReqHeroClass","type":"uint8"},{"internalType":"bool","name":"isSpecific","type":"bool"}],"name":"registerDungeonLogic","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"dungLogicId","type":"uint16"},{"internalType":"uint8","name":"specReqBiome","type":"uint8"},{"internalType":"uint8","name":"specReqHeroClass","type":"uint8"}],"name":"removeDungeonLogic","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revision","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"objectId","type":"uint32"},{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"uint8","name":"heroBiome","type":"uint8"}],"name":"setBossCompleted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"heroLevel","type":"uint256"}],"name":"setMinLevelForTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"skillSlotsForDurabilityReduction","outputs":[{"internalType":"uint8[]","name":"result","type":"uint8[]"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
60806040523480156200001157600080fd5b507ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff1615906001600160401b03166000811580156200005d5750825b90506000826001600160401b031660011480156200007a5750303b155b90508115801562000089575080155b15620000a85760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b03191660011785558315620000d757845460ff60401b1916680100000000000000001785555b83156200011e57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505061580180620001336000396000f3fe608060405234801561001057600080fd5b50600436106102295760003560e01c80639a641e0211610131578063b5a44d04116100b3578063b5a44d0414610509578063b6d2fe6c1461051c578063b701c1aa1461054a578063bd410d641461056a578063be2e0ad81461057d578063d06969c71461059d578063dee1f0e4146105b0578063e76bd825146105c3578063f77c4791146105d6578063fee27cbe146105de578063ffa1ad74146105f157600080fd5b80639a641e02146104355780639d0bcca0146104485780639f3cfa7a1461045d578063a433af6f14610465578063a46374b314610478578063a4efd309146104a2578063a7540ee5146104b5578063ac257b3a146104c8578063ad8c5755146104db578063b2192fdd146104ee578063b429afeb146104f657600080fd5b80634fac6ccd116101ba5780634fac6ccd14610326578063572b6c051461033957806357bd05b01461034c5780635a67eb481461035f5780636a8a5e8c146103725780636bb4b459146103855780637442adbc146103b05780637be89369146103c35780637c00116c146103e95780637cc96380146103fc578063936725ec1461040457600080fd5b80630e30c13e1461022e578063150b7a021461025857806319ab453c1461028457806327dd892f146102995780632fcd5deb146102ac578063325a19f1146102cf57806338430cce146102e55780634593144c146102f857806348b371bf146103005780634d52532c14610313575b600080fd5b61024161023c3660046142f7565b610615565b60405160ff90911681526020015b60405180910390f35b61026b610266366004614401565b61062a565b6040516001600160e01b0319909116815260200161024f565b61029761029236600461446c565b61063b565b005b6102976102a73660046144be565b610735565b6102bf6102ba366004614523565b61075b565b604051901515815260200161024f565b6102d76107e2565b60405190815260200161024f565b6102976102f3366004614572565b61081b565b6102d761083d565b61029761030e3660046145c1565b61086d565b6102d76103213660046145c1565b610881565b61029761033436600461446c565b61088c565b6102bf61034736600461446c565b61099e565b61029761035a3660046145f0565b610a13565b61029761036d3660046142f7565b610ab6565b610297610380366004614643565b610ac8565b610398610393366004614643565b610b44565b6040516001600160401b03909116815260200161024f565b6102d76103be366004614685565b610c06565b6103d66103d136600461469e565b610c11565b60405161ffff909116815260200161024f565b6103986103f73660046146f9565b610c3f565b6102d7610c4b565b610428604051806040016040528060058152602001640312e302e360dc1b81525081565b60405161024f9190614761565b6103986104433660046142f7565b610c69565b610450610c75565b60405161024f9190614774565b610398610ca5565b6102976104733660046142f7565b610caf565b61048b6104863660046145c1565b610d34565b60405161024f9b9a99989796959493929190614835565b6102976104b03660046148e0565b610d74565b6102d76104c336600461446c565b610d8c565b6102d76104d636600461491e565b610d97565b6102bf6104e93660046142f7565b610dc0565b610241610dd4565b6102bf61050436600461446c565b610dde565b6102976105173660046149a2565b610e03565b61052f61052a3660046149e3565b610e8d565b6040805193845260208401929092529082015260600161024f565b61055d610558366004614a1e565b610f49565b60405161024f9190614ac1565b6102976105783660046142f7565b610f5a565b61059061058b3660046142f7565b610fa8565b60405161024f9190614b97565b6103986105ab366004614643565b610fb4565b6102bf6105be36600461446c565b61101b565b6102976105d1366004614ed2565b6110a0565b6104506110b6565b6102976105ec3660046145c1565b6110e6565b61042860405180604001604052806005815260200164322e312e3160d81b81525081565b60006106218383611107565b90505b92915050565b630a85bd0160e11b5b949350505050565b6000610645611144565b805490915060ff600160401b82041615906001600160401b031660008115801561066c5750825b90506000826001600160401b031660011480156106885750303b155b905081158015610696575080155b156106b45760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156106de57845460ff60401b1916600160401b1785555b6106e786611168565b831561072d57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b610757610740611179565b6107486110b6565b610750611191565b85856111b3565b5050565b60006107d961076861124d565b6107706110b6565b6001600160a01b0316628e96916040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107d09190615091565b87878787611271565b95945050505050565b600061081661081260017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b6150c4565b5490565b905090565b610757610826611179565b61082e6110b6565b610836611191565b858561152a565b600061081661081260017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f16150c4565b61087e6108786110b6565b82611768565b50565b6000610624826117c0565b3330146108de5760405162461bcd60e51b815260206004820152601b60248201527a24b731b932b0b9b2903932bb34b9b4b7b7103337b93134b23232b760291b60448201526064015b60405180910390fd5b60006108fc61081260016000805160206157ac8339815191526150c4565b6109079060016150d7565b90506109298161092660016000805160206157ac8339815191526150c4565b55565b6109588261092660017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e46150c4565b604080518281526001600160a01b03841660208201527ff27e2ef832a4eb8ed8ec553b875eecd44764cda95b1c24170e281539e0a869c891015b60405180910390a15050565b60006001600160a01b03821673d8253782c45a12053594b9deb72d8e8ab2fca54c14806109e757506001600160a01b0382167352ceba41da235af367bfc0b0ccd3314cb901bb5f145b8061062457506001600160a01b03821673102f1f556cd9c3d5f820e6920a8931657c5da21b1492915050565b732c550024df438312efd1804231d924ca59b60ca6637c1ecb8e610a356110b6565b6040516001600160e01b031960e084901b1681526001600160a01b03918216600482015263ffffffff8816602482015290861660448201526064810185905260ff8416608482015260a40160006040518083038186803b158015610a9857600080fd5b505af4158015610aac573d6000803e3d6000fd5b5050505050505050565b610757610ac16110b6565b83836117ec565b732c550024df438312efd1804231d924ca59b60ca6632ca22aeb610aea6110b6565b85858560016040518663ffffffff1660e01b8152600401610b0f959493929190615100565b60006040518083038186803b158015610b2757600080fd5b505af4158015610b3b573d6000803e3d6000fd5b50505050505050565b6000732c550024df438312efd1804231d924ca59b60ca663987707f9610b68611179565b610b706110b6565b610b78611191565b6040516001600160e01b031960e086901b16815292151560048401526001600160a01b03918216602484015281166044830152808816606483015260848201879052851660a482015260c4015b602060405180830381865af4158015610be2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106339190615155565b60006106248261188b565b600080610c1d876118ac565b9050610c34610c2a61124d565b82888888886118c4565b979650505050505050565b60006106218383611bf1565b600061081661081260016000805160206157ac8339815191526150c4565b60006106218383611c14565b600061081661081260017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e46150c4565b6000610816611c57565b732c550024df438312efd1804231d924ca59b60ca663dc01b901610cd16110b6565b6040516001600160e01b031960e084901b1681526001600160a01b0391821660048201529085166024820152604481018490526064015b60006040518083038186803b158015610d2057600080fd5b505af415801561072d573d6000803e3d6000fd5b600080600080600080606080606060006060610d4f8c611c73565b9a509a509a509a509a509a509a509a509a509a509a5091939597999b90929496989a50565b610d87610d7f6110b6565b848484611f1c565b505050565b60006106248261210a565b6000610db48888888860ff168860ff168860ff168860ff16612136565b98975050505050505050565b6000610621610dcd6110b6565b84846122d5565b60006108166123e8565b6000610de86110b6565b6001600160a01b0316826001600160a01b0316149050919050565b732c550024df438312efd1804231d924ca59b60ca663efa20d9b610e25611179565b610e2d6110b6565b610e35611191565b6040516001600160e01b031960e086901b16815292151560048401526001600160a01b039182166024840152811660448301526001600160401b03871660648301528516608482015260a4810184905260c401610b0f565b6000806000732c550024df438312efd1804231d924ca59b60ca6633e457e4f610eb46110b6565b6040516001600160e01b031960e084901b1681526001600160a01b039182166004820152908a16602482015260448101899052606481018890526084810187905260a401606060405180830381865af4158015610f15573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f399190615172565b9250925092509450945094915050565b610f516140c3565b610624826123fe565b732c550024df438312efd1804231d924ca59b60ca6632ca22aeb610f7c6110b6565b8484610f86611191565b60026040518663ffffffff1660e01b8152600401610d08959493929190615100565b606061062183836126b4565b6000732c550024df438312efd1804231d924ca59b60ca663c77d56a2610fd86110b6565b6040516001600160e01b031960e084901b1681526001600160a01b0391821660048201528186166024820152908716604482015260648101869052608401610bc5565b6000816001600160a01b031661102f6110b6565b6001600160a01b0316635aa6e6756040518163ffffffff1660e01b8152600401602060405180830381865afa15801561106c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110909190615091565b6001600160a01b03161492915050565b61072d6110ab6110b6565b8787878787876126ed565b600061081661081260017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c36186150c4565b61087e6110f1611179565b6110f96110b6565b611101611191565b84612af4565b6000611111612b1c565b60030160006111296001600160a01b03861685612b26565b815260208101919091526040016000205460ff169392505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b611170612b76565b61087e81612b9d565b60006111843361099e565b8061081657505032331490565b600061119c3361099e565b156111ae575060131936013560601c90565b503390565b6111bc85612cb0565b60405163139ed9b560e21b81526001600160a01b0380861660048301526001600160401b038416602483015282151560448301528416606482015273643d50456d451fae5ec11a9fe506c101adece96f90634e7b66d49060840160006040518083038186803b15801561122e57600080fd5b505af4158015611242573d6000803e3d6000fd5b505050505050505050565b7fae5971282b317bbed599861775fe0712755bb3b2f655bfe8fb14280d8429f60090565b61ffff84166000908152600d870160205260408120600281015460ff8082169160081c81169087168211806112a85750808760ff16115b156112b95760009350505050611520565b505060008160030180548060200260200160405190810160405280929190818152602001828054801561130b57602002820191906000526020600020905b8154815260200190600101908083116112f7575b5050505050905060008260040180548060200260200160405190810160405280929190818152602001828054801561136257602002820191906000526020600020905b81548152602001906001019080831161134e575b505085519394506000925050505b8181101561151657600084828151811061138c5761138c6151a0565b602002602001015190506000801b81036113a6575061150e565b60008060006113dd8786815181106113c0576113c06151a0565b602002602001015190604082901c90608083901c60ff1660011490565b92509250925060008161145e578f6001600160a01b0316630a82c861866040518263ffffffff1660e01b815260040161141891815260200190565b602060405180830381865afa158015611435573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061145991906151b6565b6114cf565b8f6001600160a01b0316631c2aafe78e8e886040518463ffffffff1660e01b815260040161148e939291906151cf565b602060405180830381865afa1580156114ab573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114cf91906151b6565b9050836001600160401b03168110806114f05750826001600160401b031681115b156115085760009a5050505050505050505050611520565b50505050505b600101611370565b5060019450505050505b9695505050505050565b6000611534612b1c565b90506000611541866118ac565b6040805160a0810182526000808252602080830182905282840182905260608301829052608083018290526001600160401b0389168252600e870181528382208054600160401b900461ffff168352600d8801909152929020929350916115a78a612cb0565b6115b2828986612cce565b50508160020160009054906101000a900463ffffffff16836040019063ffffffff16908163ffffffff168152505073643d50456d451fae5ec11a9fe506c101adece96f6311942d1783838a8c8b8f8a604001516040518863ffffffff1660e01b815260040161162797969594939291906151f0565b608060405180830381865af4158015611644573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611668919061524d565b1515602087015263ffffffff1660608601526080850152158015845261169a57815460ff60501b1916600160501b1782555b6080830151156116cb57608083015160028301805460ff909216600160201b0264ff00000000199092169190911790555b826040015163ffffffff16836060015163ffffffff161461170757606083015160028301805463ffffffff191663ffffffff9092169190911790555b826020015115611727578054611727908390610100900460ff1689612df6565b825115801561173e5750600282015463ffffffff16155b801561174c57508260200151155b1561175c5761175c848989612e61565b50505050505050505050565b611771826130d0565b604051632586866b60e11b81526001600160a01b03831660048201526001600160401b038216602482015273643d50456d451fae5ec11a9fe506c101adece96f90634b0d0cd690604401610d08565b60006117ca612b1c565b6001600160401b039092166000908152600f9290920160205250604090205490565b6117f5836130d0565b600581101561181a57604051634bebee7f60e01b8152600481018290526024016108d5565b80611823612b1c565b600a016000846001600160a01b03166001600160a01b03168152602001908152602001600020819055507f996f9d801b1a05cd3c7b555e5b6f61db994ea8ef57125b5b5d3b07b9dad4e2cf828260405161187e929190615297565b60405180910390a1505050565b6000610624611898612b1c565b60008481526020919091526040902061315c565b6118b4614123565b6001600160a01b03909116815290565b60008460ff166000036118ea5760405163a8473eab60e01b815260040160405180910390fd5b6000806118f688613166565b604051631ebd249360e01b81529091506001600160a01b03821690631ebd2493906119279089908990600401615297565b602060405180830381865afa158015611944573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061196891906152b0565b915060008960050160006119ff60058b61198291906152e3565b61198d906001615305565b604051631789b7b160e01b81526001600160a01b03871690631789b7b1906119b9908e90600401614774565b602060405180830381865afa1580156119d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119fa91906152b0565b613186565b8152602081019190915260400160009081205461ffff169150819003611a5f57896005016000611a4760058b611a3591906152e3565b611a40906001615305565b6000613186565b815260208101919091526040016000205461ffff1690505b8061ffff16600003611a9457896005016000611a7c600080613186565b815260208101919091526040016000205461ffff1690505b61ffff811615611b1a576001600160a01b03871660a087901b67ffffffffffffffff60a01b161760f082901b6001600160f01b03191617600090815260028b01602052604090205460ff16158015611b0b575061ffff81166000908152600d8b01602052604090205460ff84811661010090920416145b15611b1a579250611520915050565b505060ff81166000908152600889016020526040812090611b3a8261315c565b905080600003611b61576040516283533360e01b815260ff841660048201526024016108d5565b6000611b6c8a61319b565b9050600080611b7b848961531e565b905060005b84811015611bd757611b9286836131ba565b9250611ba28e85858f8f8f611271565b15611bb65782975050505050505050611520565b81611bc081615332565b925050848210611bcf57600091505b600101611b80565b5060405163b940612960e01b815260040160405180910390fd5b600061062183611bff612b1c565b600085815260209190915260409020906131ba565b6000611c1e612b1c565b6004016000611c366001600160a01b03861685612b26565b81526020810191909152604001600020546001600160401b03169392505050565b6000611c61612b1c565b600c01546001600160401b0316919050565b6000806000806000806060806060600060606000611c8f612b1c565b600e0160008e6001600160401b03166001600160401b0316815260200190815260200160002090508060000160089054906101000a900461ffff169b5080600001600a9054906101000a900460ff169a5080600001600b9054906101000a90046001600160a01b03169950806001015498508060020160009054906101000a900463ffffffff1697508060020160049054906101000a900460ff16965080600601805480602002602001604051908101604052809291908181526020018280548015611d7a57602002820191906000526020600020905b815481526020019060010190808311611d66575b505050600784015460088501805460408051602080840282018101909252828152969a5060ff90931698509093509150830182828015611e0557602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff1681526020019060040190602082600301049283019260010382029150808411611dc85790505b505050505091506000611e1a826003016131c6565b9050806001600160401b03811115611e3457611e34614323565b604051908082528060200260200182016040528015611e5d578160200160208202803683370190505b509650806001600160401b03811115611e7857611e78614323565b604051908082528060200260200182016040528015611ea1578160200160208202803683370190505b50955060005b81811015611f0a57611ebc60038401826131d1565b898381518110611ece57611ece6151a0565b60200260200101898481518110611ee757611ee76151a0565b60209081029190910101919091526001600160a01b039091169052600101611ea7565b50505091939597999b90929496989a50565b611f25846130d0565b6000611f2f612b1c565b61ffff85166000908152600d820160205260408120805461ffff198116825592935061010090920460ff169190611f696001830182614146565b6002820160009055600382016000611f81919061416b565b611f8f60048301600061416b565b600582016000611f9f828261416b565b611fad600183016000614189565b50505060ff821660009081526008840160205260409020611fd3915061ffff87166131ed565b156120335760ff811660009081526008830160205260409020611ffa9061ffff8716613205565b5060405161ffff861681527f6ee9dd6501e6ec9a0a3e60e773ac5fd9d48414213bc6a16588c88bda44f492899060200160405180910390a15b6120446006830161ffff87166131ed565b1561072d5760006120558585613186565b600081815260058501602052604090205490915061ffff87811691161461208f5760405163066a129160e31b815260040160405180910390fd5b60008181526005840160205260409020805461ffff191690556120b96006840161ffff8816613205565b506040805161ffff8816815260ff878116602083015286168183015290517f9c5c01904f703f1392e848da84f3f506975241d8d8d0f67550729c5f5080c94e9181900360600190a150505050505050565b6000612114612b1c565b6001600160a01b039092166000908152600a9290920160205250604090205490565b60008684108061214557508282105b1561215257506000610c34565b600061215c61124d565b6001600160a01b038a166000908152600a9190910160205260409020549050801580159061218957508086105b15612198576000915050610c34565b60638611156121bd57604051633505ce1b60e11b8152600481018790526024016108d5565b60138511156121e257604051637d34ca9360e01b8152600481018690526024016108d5565b60006121ef60058761534b565b9050675c33b801024d7e4a6000606461222e61220c8560636150c4565b6122179060016150d7565b61222990670de0b6b3a764000061534b565b613211565b61223890846150c4565b6122429190615362565b9050670de0b6b3a7640000811061226f57604051630dd7f01960e01b8152600481018290526024016108d5565b6000670de0b6b3a7640000612284838d61534b565b61228e9190615362565b9050898410156122c5576122a2848b6150c4565b6122ad90600a6150d7565b6122b890600261545a565b6122c29082615362565b90505b9c9b505050505050505050505050565b600080846001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612316573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061233a9190615091565b6001600160a01b0316631ebd249385856040518363ffffffff1660e01b8152600401612367929190615297565b602060405180830381865afa158015612384573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123a891906152b0565b90506123b2612b1c565b60010160006123cb6001600160a01b03871686856132f2565b815260208101919091526040016000205460ff1695945050505050565b60006123f2612b1c565b6009015460ff16919050565b6124066140c3565b61240e612b1c565b61ffff83166000908152600d919091016020908152604091829020825160e081018452815460ff80821683526101009091041681840152600182018054855181860281018601875281815292959394938601938301828280156124bc57602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff168152602001906004019060208260030104928301926001038202915080841161247f5790505b50505050508152602001600282015481526020016003820180548060200260200160405190810160405280929190818152602001828054801561251e57602002820191906000526020600020905b81548152602001906001019080831161250a575b505050505081526020016004820180548060200260200160405190810160405280929190818152602001828054801561257657602002820191906000526020600020905b815481526020019060010190808311612562575b5050505050815260200160058201604051806040016040529081600082018054806020026020016040519081016040528092919081815260200182805480156125de57602002820191906000526020600020905b8154815260200190600101908083116125ca575b5050505050815260200160018201805480602002602001604051908101604052809291908181526020016000905b828210156126a45760008481526020908190208301805460408051828502810185019091528181529283018282801561269057602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff16815260200190600401906020826003010492830192600103820291508084116126535790505b50505050508152602001906001019061260c565b5050509152505090525092915050565b60606106216126c1612b1c565b600b0160006126d96001600160a01b03871686612b26565b815260200190815260200160002054613327565b6126f6876130d0565b6000612700612b1c565b602086015151865151919250908114158061272057508560400151518114155b1561273e5760405163c81acf3360e01b815260040160405180910390fd5b60005b818110156127ab57865180518290811061275d5761275d6151a0565b6020026020010151518760200151828151811061277c5761277c6151a0565b602002602001015151146127a35760405163f001029b60e01b815260040160405180910390fd5b600101612741565b5061ffff88166000908152600d830160205260409020600983015460ff90811690891611156127e65760098301805460ff191660ff8a161790555b805460ff8981166101000261ffff19909216908416171781556040870151805161281a9160018401916020909101906141a7565b5061282d87606001518860800151613186565b600282015560a0870151805161284d916003840191602090910190614256565b506004810160005b8860c00151518110156128e157816128c68a60c00151838151811061287c5761287c6151a0565b60200260200101518b60e00151848151811061289a5761289a6151a0565b60200260200101518c610100015185815181106128b9576128b96151a0565b602002602001015161339f565b81546001818101845560009384526020909320015501612855565b5060005b8381101561298257885180516005850191612917918490811061290a5761290a6151a0565b60200260200101516133da565b815460018101835560009283526020928390200155890151805160068501919083908110612947576129476151a0565b6020908102919091018101518254600181018455600093845292829020815161297994919091019291909101906141a7565b506001016128e5565b508415612a845760006129958888613186565b600081815260058701602052604090205490915061ffff16156129d1576040516366e6550f60e01b815261ffff8c1660048201526024016108d5565b60008181526005860160205260409020805461ffff191661ffff8d16908117909155612a019060068701906131ed565b15612a255760405163ce9c78df60e01b815261ffff8c1660048201526024016108d5565b612a366006860161ffff8d16613459565b506040805161ffff8d16815260ff8a8116602083015289168183015290517fc27376e37bec2cd9acd6957a46ca5cba20a88a21fd0eff1b02cf8536462304769181900360600190a150612aae565b8154610100900460ff1660009081526008850160205260409020612aac9061ffff8c16613459565b505b7fa8932b9bd78638467b3e5c0f0ffa9c72761db9f7f605b8df2f7a899770d064ae8a89604051612adf929190615520565b60405180910390a15050505050505050505050565b612afd84612cb0565b6000612b08846118ac565b9050612b15818484612e61565b5050505050565b600061081661124d565b60006001600160401b03821115612b5357604051633995b34160e01b8152600481018390526024016108d5565b5067ffffffffffffffff60a01b60a09190911b166001600160a01b039091161790565b612b7e613465565b612b9b57604051631afcd79f60e31b815260040160405180910390fd5b565b6001600160a01b038116612be55760405162461bcd60e51b815260206004820152600f60248201526e2d32b9379031b7b73a3937b63632b960891b60448201526064016108d5565b612c148161092660017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c36186150c4565b612c434261092660017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b6150c4565b612c724361092660017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f16150c4565b7f1a2dd071001ebf6e03174e3df5b305795a4ad5d41d8fdb9ba41dbbe236713426814243604051612ca5939291906151cf565b60405180910390a150565b8061087e57604051636221cab960e01b815260040160405180910390fd5b82546001840154600160581b82046001600160a01b031691600160501b900460ff1615612d0e57604051636927c40b60e11b815260040160405180910390fd5b612d1a8282868661347f565b612d238361319b565b6001600160a01b031663f16a306683836040518363ffffffff1660e01b8152600401612d50929190615297565b602060405180830381865afa158015612d6d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d91919061561f565b612db257818160405163033aace360e41b81526004016108d5929190615297565b84546001600160401b0316612dc78383611c14565b6001600160401b031614612dee576040516337bcc65160e01b815260040160405180910390fd5b935093915050565b8254600160581b600160f81b03191683556000600184015560028301805464ffffffffff19169055612e28828261363c565b6040516001600160401b03821681527f883aa0807cbcd64d238a9795b0433f37884de37e569aff04d512cb1df62c6e019060200161187e565b6000612e6b612b1c565b6001600160401b0383166000908152600e8201602090815260408083208054600160401b900461ffff168452600d85018352818420825160a081018452858152938401859052918301849052606083018490526080830193909352929350909190612ed5876136c8565b6001600160a01b03166040820152600283015463ffffffff1615612f0c5760405163282404cd60e01b815260040160405180910390fd5b612f17838789612cce565b602083018190526001600160a01b03909116808352600285015460ff600160201b90910416606084018190526040840151612f6093869388939291612f5b8e61319b565b6136e8565b63ffffffff1660808201819052600003612f8d57604051638a874ec160e01b815260040160405180910390fd5b608081015160028401805463ffffffff191663ffffffff909216918217905560408083015183516020850151925163f2c6c6fb60e01b81526001600160a01b03918216600482015260248101939093526044830193909352600092169063f2c6c6fb906064016020604051808303816000875af1158015613012573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061303691906151b6565b90507f7a40f4421a71ae931ce048b2171197c34a78510813f1d3f4202935dd9f105b1a868360000151846020015185608001518587606001516040516130be969594939291906001600160401b039690961686526001600160a01b03949094166020860152604085019290925263ffffffff166060840152608083015260a082015260c00190565b60405180910390a15050505050505050565b604051631430d62960e21b81526001600160a01b038216906350c358a4906130fc903390600401614774565b602060405180830381865afa158015613119573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061313d919061561f565b61087e573360405163451cea1760e11b81526004016108d59190614774565b6000610624825490565b600061062482600884600001516001600160a01b031663016dff5d613ae3565b600060ff8316600883901b61ff001617610621565b600061062482600084600001516001600160a01b0316628e9691613ae3565b60006106218383613bef565b600061062482613c19565b60008080806131e08686613c24565b9097909650945050505050565b60008181526001830160205260408120541515610621565b60006106218383613c4f565b6000670de0b6b3a764000082101561323f57604051637046c4a960e01b8152600481018390526024016108d5565b600061325b613256670de0b6b3a764000085615362565b613d42565b9050600061327182670de0b6b3a764000061534b565b905083821c670de0b6b3a764000081900361328e57509392505050565b6706f05b59d3b200005b80156132e857670de0b6b3a76400006132b1838061534b565b6132bb9190615362565b9150671bc16d674ec8000082106132e0576132d681846150d7565b9250600182901c91505b60011c613298565b5090949350505050565b60609290921b6001600160601b03191660209190911b6bffffffffffffffff00000000161760189190911b63ff000000161790565b6040805160208082526104208201909252606091600091908082016104008036833701905050905060005b60208110156133985761336681600861534b565b8460001c901c82828151811061337e5761337e6151a0565b60ff90921660209283029190910190910152600101613352565b5092915050565b67ffffffffffffffff60401b604083901b166001600160401b038416176080826133ca5760006133cd565b60015b60ff16901b179392505050565b8051600090602081111561340b57604051633d71388b60e21b815260048101829052602060248201526044016108d5565b6000805b828110156134515761342281600861534b565b858281518110613434576134346151a0565b602090810291909101015160ff16901b919091179060010161340f565b509392505050565b60006106218383613e25565b600061346f611144565b54600160401b900460ff16919050565b6040516331a9108f60e11b8152600481018490526001600160a01b038084169190861690636352211e90602401602060405180830381865afa1580156134c9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134ed9190615091565b6001600160a01b03161461351857838360405163547208b960e11b81526004016108d5929190615297565b61352181613166565b6001600160a01b0316631789b7b1856040518263ffffffff1660e01b815260040161354c9190614774565b602060405180830381865afa158015613569573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061358d91906152b0565b60ff166000036135b2578360405163adc7fced60e01b81526004016108d59190614774565b80600001516001600160a01b03166318d928316040518163ffffffff1660e01b8152600401602060405180830381865afa1580156135f4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613618919061561f565b1561363657604051635e1633d360e11b815260040160405180910390fd5b50505050565b61366a816001600160401b0316613651612b1c565b60ff851660009081526020919091526040902090613459565b613687576040516313e5d5f160e11b815260040160405180910390fd5b6040805160ff841681526001600160401b03831660208201527f3e9a7af2aeebce9d24ef707bdb61adf2708293e374a19f8e9c31f905c68b43c19101610992565b600061062482600584600001516001600160a01b03166389dd9f13613ae3565b600786015460009060ff168610613715576040516315f7ec4560e21b8152600481018790526024016108d5565b86600801868154811061372a5761372a6151a0565b60009182526020822060088204015460079091166004026101000a900463ffffffff169150819003610c3457604051631a95890960e31b81526000906001600160a01b0384169063d4ac4848906137879088908890600401615297565b60a060405180830381865afa1580156137a4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137c8919061563c565b602081015181518b549293506137f29263ffffffff9283169290911690610100900460ff16613e74565b158015906138435750613803612b1c565b89546001919091019060009061382d906001600160a01b038916908890610100900460ff166132f2565b815260208101919091526040016000205460ff16155b80156138705750885461385f90610100900460ff1660056156d3565b60ff16816000015163ffffffff1610155b156138e9578854610100900460ff166001036138925762138884915050610c34565b8854610100900460ff166002036138af576222f238915050610c34565b8854610100900460ff166003036138cc57623234dc915050610c34565b8854610100900460ff166004036138e95762417780915050610c34565b6040805160058b0180546060602082028401810185529383018181526000948492849184018282801561393b57602002820191906000526020600020905b815481526020019060010190808311613927575b5050505050815260200160018201805480602002602001604051908101604052809291908181526020016000905b82821015613a01576000848152602090819020830180546040805182850281018501909152818152928301828280156139ed57602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff16815260200190600401906020826003010492830192600103820291508084116139b05790505b505050505081526020019060010190613969565b50505050815250509050866001600160a01b0316636f383c0f613a4083600001518b81518110613a3357613a336151a0565b6020026020010151613eca565b83602001518b81518110613a5657613a566151a0565b60200260200101518d60000160019054906101000a900460ff168a8a6040518663ffffffff1660e01b8152600401613a929594939291906156ef565b6020604051808303816000875af1158015613ab1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613ad5919061573e565b9a9950505050505050505050565b600080856020015185600e811115613afd57613afd6150ea565b600f8110613b0d57613b0d6151a0565b602002015190506001600160a01b03811615613b2a579050610633565b83836040518163ffffffff1660e01b8152600401602060405180830381865afa158015613b5b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613b7f9190615091565b866020015186600e811115613b9657613b966150ea565b600f8110613ba657613ba66151a0565b6001600160a01b039092166020928302919091015286015185600e811115613bd057613bd06150ea565b600f8110613be057613be06151a0565b60200201519695505050505050565b6000826000018281548110613c0657613c066151a0565b9060005260206000200154905092915050565b60006106248261315c565b60008080613c3285856131ba565b600081815260029690960160205260409095205494959350505050565b60008181526001830160205260408120548015613d38576000613c736001836150c4565b8554909150600090613c87906001906150c4565b9050808214613cec576000866000018281548110613ca757613ca76151a0565b9060005260206000200154905080876000018481548110613cca57613cca6151a0565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080613cfd57613cfd61575b565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610624565b6000915050610624565b6000600160801b8210613d6257608091821c91613d5f90826150d7565b90505b600160401b8210613d8057604091821c91613d7d90826150d7565b90505b600160201b8210613d9e57602091821c91613d9b90826150d7565b90505b620100008210613dbb57601091821c91613db890826150d7565b90505b6101008210613dd757600891821c91613dd490826150d7565b90505b60108210613df257600491821c91613def90826150d7565b90505b60048210613e0d57600291821c91613e0a90826150d7565b90505b60028210613e20576106246001826150d7565b919050565b6000818152600183016020526040812054613e6c57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610624565b506000610624565b6000806005613e8586866001613fbe565b613e8f9190615362565b613e9a9060016150d7565b9050828111613eaa5760006107d9565b613eb483826150c4565b613ebf90600a6150d7565b6107d990600261545a565b60606000613ed783613327565b90506000805b8251811015613f2057828181518110613ef857613ef86151a0565b602002602001015160ff1660000315613f205781613f1581615332565b925050600101613edd565b50806001600160401b03811115613f3957613f39614323565b604051908082528060200260200182016040528015613f62578160200160208202803683370190505b50925060005b81811015613fb657828181518110613f8257613f826151a0565b6020026020010151848281518110613f9c57613f9c6151a0565b60ff90921660209283029190910190910152600101613f68565b505050919050565b600080613fcb8585613ff4565b9050828015613fe457506002613fe28260016150d7565b115b15610633576107d96002826150c4565b805b60638110156106245761400a8360016150d7565b61401382614027565b63ffffffff16101561062457600101613ff6565b600063ffffffff821615806140435750606363ffffffff831610155b1561405057506000919050565b670de0b6b3a764000061408a614067846063615771565b61407290600261578e565b6122299063ffffffff16670de0b6b3a764000061534b565b61409c90675cfb2e807b1e00006150c4565b6140af620186a063ffffffff861661534b565b6140b9919061534b565b6106249190615362565b6040518060e00160405280600060ff168152602001600060ff1681526020016060815260200160008019168152602001606081526020016060815260200161411e604051806040016040528060608152602001606081525090565b905290565b604051806040016040528060006001600160a01b0316815260200161411e614291565b50805460008255600701600890049060005260206000209081019061087e91906142b0565b508054600082559060005260206000209081019061087e91906142b0565b508054600082559060005260206000209081019061087e91906142c5565b828054828255906000526020600020906007016008900481019282156142465791602002820160005b8382111561421457835183826101000a81548163ffffffff021916908363ffffffff16021790555092602001926004016020816003010492830192600103026141d0565b80156142445782816101000a81549063ffffffff0219169055600401602081600301049283019260010302614214565b505b506142529291506142b0565b5090565b828054828255906000526020600020908101928215614246579160200282015b82811115614246578251825591602001919060010190614276565b604051806101e00160405280600f906020820280368337509192915050565b5b8082111561425257600081556001016142b1565b808211156142525760006142d98282614146565b506001016142c5565b6001600160a01b038116811461087e57600080fd5b6000806040838503121561430a57600080fd5b8235614315816142e2565b946020939093013593505050565b634e487b7160e01b600052604160045260246000fd5b60405161012081016001600160401b038111828210171561435c5761435c614323565b60405290565b604051601f8201601f191681016001600160401b038111828210171561438a5761438a614323565b604052919050565b600082601f8301126143a357600080fd5b81356001600160401b038111156143bc576143bc614323565b6143cf601f8201601f1916602001614362565b8181528460208386010111156143e457600080fd5b816020850160208301376000918101602001919091529392505050565b6000806000806080858703121561441757600080fd5b8435614422816142e2565b93506020850135614432816142e2565b92506040850135915060608501356001600160401b0381111561445457600080fd5b61446087828801614392565b91505092959194509250565b60006020828403121561447e57600080fd5b8135614489816142e2565b9392505050565b6001600160401b038116811461087e57600080fd5b801515811461087e57600080fd5b8035613e20816144a5565b600080604083850312156144d157600080fd5b82356144dc81614490565b915060208301356144ec816144a5565b809150509250929050565b803561ffff81168114613e2057600080fd5b60ff8116811461087e57600080fd5b8035613e2081614509565b6000806000806080858703121561453957600080fd5b614542856144f7565b9350602085013561455281614509565b92506040850135614562816142e2565b9396929550929360600135925050565b6000806040838503121561458557600080fd5b823561459081614490565b915060208301356001600160401b038111156145ab57600080fd5b6145b785828601614392565b9150509250929050565b6000602082840312156145d357600080fd5b813561448981614490565b63ffffffff8116811461087e57600080fd5b6000806000806080858703121561460657600080fd5b8435614611816145de565b93506020850135614621816142e2565b925060408501359150606085013561463881614509565b939692955090935050565b60008060006060848603121561465857600080fd5b8335614663816142e2565b925060208401359150604084013561467a816142e2565b809150509250925092565b60006020828403121561469757600080fd5b5035919050565b600080600080600060a086880312156146b657600080fd5b85356146c1816142e2565b945060208601356146d181614509565b935060408601356146e1816142e2565b94979396509394606081013594506080013592915050565b6000806040838503121561470c57600080fd5b50508035926020909101359150565b6000815180845260005b8181101561474157602081850181015186830182015201614725565b506000602082860101526020601f19601f83011685010191505092915050565b602081526000610621602083018461471b565b6001600160a01b0391909116815260200190565b60008151808452602080850194506020840160005b838110156147c25781516001600160a01b03168752958201959082019060010161479d565b509495945050505050565b60008151808452602080850194506020840160005b838110156147c2578151875295820195908201906001016147e2565b60008151808452602080850194506020840160005b838110156147c257815163ffffffff1687529582019590820190600101614813565b61ffff8c1681528a151560208201526001600160a01b038a1660408201526060810189905263ffffffff88166080820152600061016060ff891660a08401528060c084015261488681840189614788565b905082810360e084015261489a81886147cd565b90508281036101008401526148af81876147cd565b60ff861661012085015290508281036101408401526148ce81856147fe565b9e9d5050505050505050505050505050565b6000806000606084860312156148f557600080fd5b6148fe846144f7565b9250602084013561490e81614509565b9150604084013561467a81614509565b600080600080600080600060e0888a03121561493957600080fd5b8735614944816142e2565b96506020880135955060408801359450606088013561496281614509565b9350608088013561497281614509565b925060a088013561498281614509565b915060c088013561499281614509565b8091505092959891949750929550565b6000806000606084860312156149b757600080fd5b83356149c281614490565b925060208401356149d2816142e2565b929592945050506040919091013590565b600080600080608085870312156149f957600080fd5b8435614a04816142e2565b966020860135965060408601359560600135945092505050565b600060208284031215614a3057600080fd5b610621826144f7565b60008282518085526020808601955060208260051b8401016020860160005b84811015614a8657601f19868403018952614a748383516147fe565b98840198925090830190600101614a58565b5090979650505050505050565b6000815160408452614aa860408501826147cd565b9050602083015184820360208601526107d98282614a39565b6020815260ff825116602082015260006020830151614ae5604084018260ff169052565b50604083015160e06060840152614b006101008401826147fe565b9050606084015160808401526080840151601f19808584030160a0860152614b2883836147cd565b925060a08601519150808584030160c0860152614b4583836147cd565b925060c08601519150808584030160e0860152506107d98282614a93565b60008151808452602080850194506020840160005b838110156147c257815160ff1687529582019590820190600101614b78565b6020815260006106216020830184614b63565b60006001600160401b03821115614bc357614bc3614323565b5060051b60200190565b600082601f830112614bde57600080fd5b81356020614bf3614bee83614baa565b614362565b828152600592831b8501820192828201919087851115614c1257600080fd5b8387015b85811015614a865780356001600160401b03811115614c355760008081fd5b8801603f81018a13614c475760008081fd5b858101356040614c59614bee83614baa565b82815291851b8301810191888101908d841115614c765760008081fd5b938201935b83851015614ca05784359250614c9083614509565b8282529389019390890190614c7b565b885250505093850193508401614c16565b600082601f830112614cc257600080fd5b81356020614cd2614bee83614baa565b8083825260208201915060208460051b870101935086841115614cf457600080fd5b602086015b84811015614d19578035614d0c816145de565b8352918301918301614cf9565b509695505050505050565b600082601f830112614d3557600080fd5b81356020614d45614bee83614baa565b82815260059290921b84018101918181019086841115614d6457600080fd5b8286015b84811015614d195780356001600160401b03811115614d875760008081fd5b614d958986838b0101614cb1565b845250918301918301614d68565b600082601f830112614db457600080fd5b81356020614dc4614bee83614baa565b8083825260208201915060208460051b870101935086841115614de657600080fd5b602086015b84811015614d195780358352918301918301614deb565b600082601f830112614e1357600080fd5b81356020614e23614bee83614baa565b8083825260208201915060208460051b870101935086841115614e4557600080fd5b602086015b84811015614d19578035614e5d81614490565b8352918301918301614e4a565b600082601f830112614e7b57600080fd5b81356020614e8b614bee83614baa565b8083825260208201915060208460051b870101935086841115614ead57600080fd5b602086015b84811015614d19578035614ec5816144a5565b8352918301918301614eb2565b60008060008060008060c08789031215614eeb57600080fd5b614ef4876144f7565b95506020870135614f0481614509565b945060408701356001600160401b0380821115614f2057600080fd5b90880190610120828b031215614f3557600080fd5b614f3d614339565b823582811115614f4c57600080fd5b614f588c828601614bcd565b825250602083013582811115614f6d57600080fd5b614f798c828601614d24565b602083015250604083013582811115614f9157600080fd5b614f9d8c828601614cb1565b604083015250614faf60608401614518565b6060820152614fc060808401614518565b608082015260a083013582811115614fd757600080fd5b614fe38c828601614da3565b60a08301525060c083013582811115614ffb57600080fd5b6150078c828601614e02565b60c08301525060e08301358281111561501f57600080fd5b61502b8c828601614e02565b60e083015250610100808401358381111561504557600080fd5b6150518d828701614e6a565b82840152505080965050505061506960608801614518565b925061507760808801614518565b915061508560a088016144b3565b90509295509295509295565b6000602082840312156150a357600080fd5b8151614489816142e2565b634e487b7160e01b600052601160045260246000fd5b81810381811115610624576106246150ae565b80820180821115610624576106246150ae565b634e487b7160e01b600052602160045260246000fd5b6001600160a01b0386811682528581166020830152604082018590528316606082015260a081016003831061514557634e487b7160e01b600052602160045260246000fd5b8260808301529695505050505050565b60006020828403121561516757600080fd5b815161448981614490565b60008060006060848603121561518757600080fd5b8351925060208401519150604084015190509250925092565b634e487b7160e01b600052603260045260246000fd5b6000602082840312156151c857600080fd5b5051919050565b6001600160a01b039390931683526020830191909152604082015260600190565b8781528660208201526001600160401b0386166040820152600060018060a01b03808716606084015260e0608084015261522d60e084018761471b565b941660a08301525063ffffffff9190911660c09091015295945050505050565b6000806000806080858703121561526357600080fd5b845161526e816144a5565b602086015160408701519195509350615286816145de565b6060860151909250614638816144a5565b6001600160a01b03929092168252602082015260400190565b6000602082840312156152c257600080fd5b815161448981614509565b634e487b7160e01b600052601260045260246000fd5b600060ff8316806152f6576152f66152cd565b8060ff84160491505092915050565b60ff8181168382160190811115610624576106246150ae565b60008261532d5761532d6152cd565b500690565b600060018201615344576153446150ae565b5060010190565b8082028115828204841417610624576106246150ae565b600082615371576153716152cd565b500490565b600181815b808511156153b1578160001904821115615397576153976150ae565b808516156153a457918102915b93841c939080029061537b565b509250929050565b6000826153c857506001610624565b816153d557506000610624565b81600181146153eb57600281146153f557615411565b6001915050610624565b60ff841115615406576154066150ae565b50506001821b610624565b5060208310610133831016604e8410600b8410161715615434575081810a610624565b61543e8383615376565b8060001904821115615452576154526150ae565b029392505050565b600061062183836153b9565b60008282518085526020808601955060208260051b8401016020860160005b84811015614a8657601f198684030189526154a1838351614b63565b98840198925090830190600101615485565b60008151808452602080850194506020840160005b838110156147c25781516001600160401b0316875295820195908201906001016154c8565b60008151808452602080850194506020840160005b838110156147c2578151151587529582019590820190600101615502565b61ffff831681526040602082015260008251610120806040850152615549610160850183615466565b91506020850151603f19808685030160608701526155678483614a39565b9350604087015191508086850301608087015261558484836147fe565b93506060870151915061559c60a087018360ff169052565b608087015160ff1660c087015260a0870151868503820160e088015291506155c484836147cd565b935060c087015191506101008187860301818801526155e385846154b3565b945060e088015192508187860301848801526155ff85846154b3565b9450808801519350508086850301610140870152505061152082826154ed565b60006020828403121561563157600080fd5b8151614489816144a5565b600060a0828403121561564e57600080fd5b60405160a081018181106001600160401b038211171561567057615670614323565b604052825161567e816145de565b8152602083015161568e816145de565b602082015260408301516156a1816145de565b604082015260608301516156b4816145de565b606082015260808301516156c7816145de565b60808201529392505050565b60ff8181168382160290811690818114613398576133986150ae565b60a08152600061570260a0830188614b63565b828103602084015261571481886147fe565b60ff96909616604084015250506001600160a01b0392909216606083015260809091015292915050565b60006020828403121561575057600080fd5b8151614489816145de565b634e487b7160e01b600052603160045260246000fd5b63ffffffff828116828216039080821115613398576133986150ae565b63ffffffff818116838216019080821115613398576133986150ae56fe22573091f17911fb166032a3d9e0554aa73d31b7b7ddea4a4dd2995650af84bda264697066735822122051b07f07a87b4e03d3d5e165d80ccd1a459b4a640c665371780d53ddaa9075e764736f6c63430008170033
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106102295760003560e01c80639a641e0211610131578063b5a44d04116100b3578063b5a44d0414610509578063b6d2fe6c1461051c578063b701c1aa1461054a578063bd410d641461056a578063be2e0ad81461057d578063d06969c71461059d578063dee1f0e4146105b0578063e76bd825146105c3578063f77c4791146105d6578063fee27cbe146105de578063ffa1ad74146105f157600080fd5b80639a641e02146104355780639d0bcca0146104485780639f3cfa7a1461045d578063a433af6f14610465578063a46374b314610478578063a4efd309146104a2578063a7540ee5146104b5578063ac257b3a146104c8578063ad8c5755146104db578063b2192fdd146104ee578063b429afeb146104f657600080fd5b80634fac6ccd116101ba5780634fac6ccd14610326578063572b6c051461033957806357bd05b01461034c5780635a67eb481461035f5780636a8a5e8c146103725780636bb4b459146103855780637442adbc146103b05780637be89369146103c35780637c00116c146103e95780637cc96380146103fc578063936725ec1461040457600080fd5b80630e30c13e1461022e578063150b7a021461025857806319ab453c1461028457806327dd892f146102995780632fcd5deb146102ac578063325a19f1146102cf57806338430cce146102e55780634593144c146102f857806348b371bf146103005780634d52532c14610313575b600080fd5b61024161023c3660046142f7565b610615565b60405160ff90911681526020015b60405180910390f35b61026b610266366004614401565b61062a565b6040516001600160e01b0319909116815260200161024f565b61029761029236600461446c565b61063b565b005b6102976102a73660046144be565b610735565b6102bf6102ba366004614523565b61075b565b604051901515815260200161024f565b6102d76107e2565b60405190815260200161024f565b6102976102f3366004614572565b61081b565b6102d761083d565b61029761030e3660046145c1565b61086d565b6102d76103213660046145c1565b610881565b61029761033436600461446c565b61088c565b6102bf61034736600461446c565b61099e565b61029761035a3660046145f0565b610a13565b61029761036d3660046142f7565b610ab6565b610297610380366004614643565b610ac8565b610398610393366004614643565b610b44565b6040516001600160401b03909116815260200161024f565b6102d76103be366004614685565b610c06565b6103d66103d136600461469e565b610c11565b60405161ffff909116815260200161024f565b6103986103f73660046146f9565b610c3f565b6102d7610c4b565b610428604051806040016040528060058152602001640312e302e360dc1b81525081565b60405161024f9190614761565b6103986104433660046142f7565b610c69565b610450610c75565b60405161024f9190614774565b610398610ca5565b6102976104733660046142f7565b610caf565b61048b6104863660046145c1565b610d34565b60405161024f9b9a99989796959493929190614835565b6102976104b03660046148e0565b610d74565b6102d76104c336600461446c565b610d8c565b6102d76104d636600461491e565b610d97565b6102bf6104e93660046142f7565b610dc0565b610241610dd4565b6102bf61050436600461446c565b610dde565b6102976105173660046149a2565b610e03565b61052f61052a3660046149e3565b610e8d565b6040805193845260208401929092529082015260600161024f565b61055d610558366004614a1e565b610f49565b60405161024f9190614ac1565b6102976105783660046142f7565b610f5a565b61059061058b3660046142f7565b610fa8565b60405161024f9190614b97565b6103986105ab366004614643565b610fb4565b6102bf6105be36600461446c565b61101b565b6102976105d1366004614ed2565b6110a0565b6104506110b6565b6102976105ec3660046145c1565b6110e6565b61042860405180604001604052806005815260200164322e312e3160d81b81525081565b60006106218383611107565b90505b92915050565b630a85bd0160e11b5b949350505050565b6000610645611144565b805490915060ff600160401b82041615906001600160401b031660008115801561066c5750825b90506000826001600160401b031660011480156106885750303b155b905081158015610696575080155b156106b45760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156106de57845460ff60401b1916600160401b1785555b6106e786611168565b831561072d57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b610757610740611179565b6107486110b6565b610750611191565b85856111b3565b5050565b60006107d961076861124d565b6107706110b6565b6001600160a01b0316628e96916040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107d09190615091565b87878787611271565b95945050505050565b600061081661081260017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b6150c4565b5490565b905090565b610757610826611179565b61082e6110b6565b610836611191565b858561152a565b600061081661081260017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f16150c4565b61087e6108786110b6565b82611768565b50565b6000610624826117c0565b3330146108de5760405162461bcd60e51b815260206004820152601b60248201527a24b731b932b0b9b2903932bb34b9b4b7b7103337b93134b23232b760291b60448201526064015b60405180910390fd5b60006108fc61081260016000805160206157ac8339815191526150c4565b6109079060016150d7565b90506109298161092660016000805160206157ac8339815191526150c4565b55565b6109588261092660017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e46150c4565b604080518281526001600160a01b03841660208201527ff27e2ef832a4eb8ed8ec553b875eecd44764cda95b1c24170e281539e0a869c891015b60405180910390a15050565b60006001600160a01b03821673d8253782c45a12053594b9deb72d8e8ab2fca54c14806109e757506001600160a01b0382167352ceba41da235af367bfc0b0ccd3314cb901bb5f145b8061062457506001600160a01b03821673102f1f556cd9c3d5f820e6920a8931657c5da21b1492915050565b732c550024df438312efd1804231d924ca59b60ca6637c1ecb8e610a356110b6565b6040516001600160e01b031960e084901b1681526001600160a01b03918216600482015263ffffffff8816602482015290861660448201526064810185905260ff8416608482015260a40160006040518083038186803b158015610a9857600080fd5b505af4158015610aac573d6000803e3d6000fd5b5050505050505050565b610757610ac16110b6565b83836117ec565b732c550024df438312efd1804231d924ca59b60ca6632ca22aeb610aea6110b6565b85858560016040518663ffffffff1660e01b8152600401610b0f959493929190615100565b60006040518083038186803b158015610b2757600080fd5b505af4158015610b3b573d6000803e3d6000fd5b50505050505050565b6000732c550024df438312efd1804231d924ca59b60ca663987707f9610b68611179565b610b706110b6565b610b78611191565b6040516001600160e01b031960e086901b16815292151560048401526001600160a01b03918216602484015281166044830152808816606483015260848201879052851660a482015260c4015b602060405180830381865af4158015610be2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106339190615155565b60006106248261188b565b600080610c1d876118ac565b9050610c34610c2a61124d565b82888888886118c4565b979650505050505050565b60006106218383611bf1565b600061081661081260016000805160206157ac8339815191526150c4565b60006106218383611c14565b600061081661081260017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e46150c4565b6000610816611c57565b732c550024df438312efd1804231d924ca59b60ca663dc01b901610cd16110b6565b6040516001600160e01b031960e084901b1681526001600160a01b0391821660048201529085166024820152604481018490526064015b60006040518083038186803b158015610d2057600080fd5b505af415801561072d573d6000803e3d6000fd5b600080600080600080606080606060006060610d4f8c611c73565b9a509a509a509a509a509a509a509a509a509a509a5091939597999b90929496989a50565b610d87610d7f6110b6565b848484611f1c565b505050565b60006106248261210a565b6000610db48888888860ff168860ff168860ff168860ff16612136565b98975050505050505050565b6000610621610dcd6110b6565b84846122d5565b60006108166123e8565b6000610de86110b6565b6001600160a01b0316826001600160a01b0316149050919050565b732c550024df438312efd1804231d924ca59b60ca663efa20d9b610e25611179565b610e2d6110b6565b610e35611191565b6040516001600160e01b031960e086901b16815292151560048401526001600160a01b039182166024840152811660448301526001600160401b03871660648301528516608482015260a4810184905260c401610b0f565b6000806000732c550024df438312efd1804231d924ca59b60ca6633e457e4f610eb46110b6565b6040516001600160e01b031960e084901b1681526001600160a01b039182166004820152908a16602482015260448101899052606481018890526084810187905260a401606060405180830381865af4158015610f15573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f399190615172565b9250925092509450945094915050565b610f516140c3565b610624826123fe565b732c550024df438312efd1804231d924ca59b60ca6632ca22aeb610f7c6110b6565b8484610f86611191565b60026040518663ffffffff1660e01b8152600401610d08959493929190615100565b606061062183836126b4565b6000732c550024df438312efd1804231d924ca59b60ca663c77d56a2610fd86110b6565b6040516001600160e01b031960e084901b1681526001600160a01b0391821660048201528186166024820152908716604482015260648101869052608401610bc5565b6000816001600160a01b031661102f6110b6565b6001600160a01b0316635aa6e6756040518163ffffffff1660e01b8152600401602060405180830381865afa15801561106c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110909190615091565b6001600160a01b03161492915050565b61072d6110ab6110b6565b8787878787876126ed565b600061081661081260017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c36186150c4565b61087e6110f1611179565b6110f96110b6565b611101611191565b84612af4565b6000611111612b1c565b60030160006111296001600160a01b03861685612b26565b815260208101919091526040016000205460ff169392505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b611170612b76565b61087e81612b9d565b60006111843361099e565b8061081657505032331490565b600061119c3361099e565b156111ae575060131936013560601c90565b503390565b6111bc85612cb0565b60405163139ed9b560e21b81526001600160a01b0380861660048301526001600160401b038416602483015282151560448301528416606482015273643d50456d451fae5ec11a9fe506c101adece96f90634e7b66d49060840160006040518083038186803b15801561122e57600080fd5b505af4158015611242573d6000803e3d6000fd5b505050505050505050565b7fae5971282b317bbed599861775fe0712755bb3b2f655bfe8fb14280d8429f60090565b61ffff84166000908152600d870160205260408120600281015460ff8082169160081c81169087168211806112a85750808760ff16115b156112b95760009350505050611520565b505060008160030180548060200260200160405190810160405280929190818152602001828054801561130b57602002820191906000526020600020905b8154815260200190600101908083116112f7575b5050505050905060008260040180548060200260200160405190810160405280929190818152602001828054801561136257602002820191906000526020600020905b81548152602001906001019080831161134e575b505085519394506000925050505b8181101561151657600084828151811061138c5761138c6151a0565b602002602001015190506000801b81036113a6575061150e565b60008060006113dd8786815181106113c0576113c06151a0565b602002602001015190604082901c90608083901c60ff1660011490565b92509250925060008161145e578f6001600160a01b0316630a82c861866040518263ffffffff1660e01b815260040161141891815260200190565b602060405180830381865afa158015611435573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061145991906151b6565b6114cf565b8f6001600160a01b0316631c2aafe78e8e886040518463ffffffff1660e01b815260040161148e939291906151cf565b602060405180830381865afa1580156114ab573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114cf91906151b6565b9050836001600160401b03168110806114f05750826001600160401b031681115b156115085760009a5050505050505050505050611520565b50505050505b600101611370565b5060019450505050505b9695505050505050565b6000611534612b1c565b90506000611541866118ac565b6040805160a0810182526000808252602080830182905282840182905260608301829052608083018290526001600160401b0389168252600e870181528382208054600160401b900461ffff168352600d8801909152929020929350916115a78a612cb0565b6115b2828986612cce565b50508160020160009054906101000a900463ffffffff16836040019063ffffffff16908163ffffffff168152505073643d50456d451fae5ec11a9fe506c101adece96f6311942d1783838a8c8b8f8a604001516040518863ffffffff1660e01b815260040161162797969594939291906151f0565b608060405180830381865af4158015611644573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611668919061524d565b1515602087015263ffffffff1660608601526080850152158015845261169a57815460ff60501b1916600160501b1782555b6080830151156116cb57608083015160028301805460ff909216600160201b0264ff00000000199092169190911790555b826040015163ffffffff16836060015163ffffffff161461170757606083015160028301805463ffffffff191663ffffffff9092169190911790555b826020015115611727578054611727908390610100900460ff1689612df6565b825115801561173e5750600282015463ffffffff16155b801561174c57508260200151155b1561175c5761175c848989612e61565b50505050505050505050565b611771826130d0565b604051632586866b60e11b81526001600160a01b03831660048201526001600160401b038216602482015273643d50456d451fae5ec11a9fe506c101adece96f90634b0d0cd690604401610d08565b60006117ca612b1c565b6001600160401b039092166000908152600f9290920160205250604090205490565b6117f5836130d0565b600581101561181a57604051634bebee7f60e01b8152600481018290526024016108d5565b80611823612b1c565b600a016000846001600160a01b03166001600160a01b03168152602001908152602001600020819055507f996f9d801b1a05cd3c7b555e5b6f61db994ea8ef57125b5b5d3b07b9dad4e2cf828260405161187e929190615297565b60405180910390a1505050565b6000610624611898612b1c565b60008481526020919091526040902061315c565b6118b4614123565b6001600160a01b03909116815290565b60008460ff166000036118ea5760405163a8473eab60e01b815260040160405180910390fd5b6000806118f688613166565b604051631ebd249360e01b81529091506001600160a01b03821690631ebd2493906119279089908990600401615297565b602060405180830381865afa158015611944573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061196891906152b0565b915060008960050160006119ff60058b61198291906152e3565b61198d906001615305565b604051631789b7b160e01b81526001600160a01b03871690631789b7b1906119b9908e90600401614774565b602060405180830381865afa1580156119d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119fa91906152b0565b613186565b8152602081019190915260400160009081205461ffff169150819003611a5f57896005016000611a4760058b611a3591906152e3565b611a40906001615305565b6000613186565b815260208101919091526040016000205461ffff1690505b8061ffff16600003611a9457896005016000611a7c600080613186565b815260208101919091526040016000205461ffff1690505b61ffff811615611b1a576001600160a01b03871660a087901b67ffffffffffffffff60a01b161760f082901b6001600160f01b03191617600090815260028b01602052604090205460ff16158015611b0b575061ffff81166000908152600d8b01602052604090205460ff84811661010090920416145b15611b1a579250611520915050565b505060ff81166000908152600889016020526040812090611b3a8261315c565b905080600003611b61576040516283533360e01b815260ff841660048201526024016108d5565b6000611b6c8a61319b565b9050600080611b7b848961531e565b905060005b84811015611bd757611b9286836131ba565b9250611ba28e85858f8f8f611271565b15611bb65782975050505050505050611520565b81611bc081615332565b925050848210611bcf57600091505b600101611b80565b5060405163b940612960e01b815260040160405180910390fd5b600061062183611bff612b1c565b600085815260209190915260409020906131ba565b6000611c1e612b1c565b6004016000611c366001600160a01b03861685612b26565b81526020810191909152604001600020546001600160401b03169392505050565b6000611c61612b1c565b600c01546001600160401b0316919050565b6000806000806000806060806060600060606000611c8f612b1c565b600e0160008e6001600160401b03166001600160401b0316815260200190815260200160002090508060000160089054906101000a900461ffff169b5080600001600a9054906101000a900460ff169a5080600001600b9054906101000a90046001600160a01b03169950806001015498508060020160009054906101000a900463ffffffff1697508060020160049054906101000a900460ff16965080600601805480602002602001604051908101604052809291908181526020018280548015611d7a57602002820191906000526020600020905b815481526020019060010190808311611d66575b505050600784015460088501805460408051602080840282018101909252828152969a5060ff90931698509093509150830182828015611e0557602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff1681526020019060040190602082600301049283019260010382029150808411611dc85790505b505050505091506000611e1a826003016131c6565b9050806001600160401b03811115611e3457611e34614323565b604051908082528060200260200182016040528015611e5d578160200160208202803683370190505b509650806001600160401b03811115611e7857611e78614323565b604051908082528060200260200182016040528015611ea1578160200160208202803683370190505b50955060005b81811015611f0a57611ebc60038401826131d1565b898381518110611ece57611ece6151a0565b60200260200101898481518110611ee757611ee76151a0565b60209081029190910101919091526001600160a01b039091169052600101611ea7565b50505091939597999b90929496989a50565b611f25846130d0565b6000611f2f612b1c565b61ffff85166000908152600d820160205260408120805461ffff198116825592935061010090920460ff169190611f696001830182614146565b6002820160009055600382016000611f81919061416b565b611f8f60048301600061416b565b600582016000611f9f828261416b565b611fad600183016000614189565b50505060ff821660009081526008840160205260409020611fd3915061ffff87166131ed565b156120335760ff811660009081526008830160205260409020611ffa9061ffff8716613205565b5060405161ffff861681527f6ee9dd6501e6ec9a0a3e60e773ac5fd9d48414213bc6a16588c88bda44f492899060200160405180910390a15b6120446006830161ffff87166131ed565b1561072d5760006120558585613186565b600081815260058501602052604090205490915061ffff87811691161461208f5760405163066a129160e31b815260040160405180910390fd5b60008181526005840160205260409020805461ffff191690556120b96006840161ffff8816613205565b506040805161ffff8816815260ff878116602083015286168183015290517f9c5c01904f703f1392e848da84f3f506975241d8d8d0f67550729c5f5080c94e9181900360600190a150505050505050565b6000612114612b1c565b6001600160a01b039092166000908152600a9290920160205250604090205490565b60008684108061214557508282105b1561215257506000610c34565b600061215c61124d565b6001600160a01b038a166000908152600a9190910160205260409020549050801580159061218957508086105b15612198576000915050610c34565b60638611156121bd57604051633505ce1b60e11b8152600481018790526024016108d5565b60138511156121e257604051637d34ca9360e01b8152600481018690526024016108d5565b60006121ef60058761534b565b9050675c33b801024d7e4a6000606461222e61220c8560636150c4565b6122179060016150d7565b61222990670de0b6b3a764000061534b565b613211565b61223890846150c4565b6122429190615362565b9050670de0b6b3a7640000811061226f57604051630dd7f01960e01b8152600481018290526024016108d5565b6000670de0b6b3a7640000612284838d61534b565b61228e9190615362565b9050898410156122c5576122a2848b6150c4565b6122ad90600a6150d7565b6122b890600261545a565b6122c29082615362565b90505b9c9b505050505050505050505050565b600080846001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612316573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061233a9190615091565b6001600160a01b0316631ebd249385856040518363ffffffff1660e01b8152600401612367929190615297565b602060405180830381865afa158015612384573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123a891906152b0565b90506123b2612b1c565b60010160006123cb6001600160a01b03871686856132f2565b815260208101919091526040016000205460ff1695945050505050565b60006123f2612b1c565b6009015460ff16919050565b6124066140c3565b61240e612b1c565b61ffff83166000908152600d919091016020908152604091829020825160e081018452815460ff80821683526101009091041681840152600182018054855181860281018601875281815292959394938601938301828280156124bc57602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff168152602001906004019060208260030104928301926001038202915080841161247f5790505b50505050508152602001600282015481526020016003820180548060200260200160405190810160405280929190818152602001828054801561251e57602002820191906000526020600020905b81548152602001906001019080831161250a575b505050505081526020016004820180548060200260200160405190810160405280929190818152602001828054801561257657602002820191906000526020600020905b815481526020019060010190808311612562575b5050505050815260200160058201604051806040016040529081600082018054806020026020016040519081016040528092919081815260200182805480156125de57602002820191906000526020600020905b8154815260200190600101908083116125ca575b5050505050815260200160018201805480602002602001604051908101604052809291908181526020016000905b828210156126a45760008481526020908190208301805460408051828502810185019091528181529283018282801561269057602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff16815260200190600401906020826003010492830192600103820291508084116126535790505b50505050508152602001906001019061260c565b5050509152505090525092915050565b60606106216126c1612b1c565b600b0160006126d96001600160a01b03871686612b26565b815260200190815260200160002054613327565b6126f6876130d0565b6000612700612b1c565b602086015151865151919250908114158061272057508560400151518114155b1561273e5760405163c81acf3360e01b815260040160405180910390fd5b60005b818110156127ab57865180518290811061275d5761275d6151a0565b6020026020010151518760200151828151811061277c5761277c6151a0565b602002602001015151146127a35760405163f001029b60e01b815260040160405180910390fd5b600101612741565b5061ffff88166000908152600d830160205260409020600983015460ff90811690891611156127e65760098301805460ff191660ff8a161790555b805460ff8981166101000261ffff19909216908416171781556040870151805161281a9160018401916020909101906141a7565b5061282d87606001518860800151613186565b600282015560a0870151805161284d916003840191602090910190614256565b506004810160005b8860c00151518110156128e157816128c68a60c00151838151811061287c5761287c6151a0565b60200260200101518b60e00151848151811061289a5761289a6151a0565b60200260200101518c610100015185815181106128b9576128b96151a0565b602002602001015161339f565b81546001818101845560009384526020909320015501612855565b5060005b8381101561298257885180516005850191612917918490811061290a5761290a6151a0565b60200260200101516133da565b815460018101835560009283526020928390200155890151805160068501919083908110612947576129476151a0565b6020908102919091018101518254600181018455600093845292829020815161297994919091019291909101906141a7565b506001016128e5565b508415612a845760006129958888613186565b600081815260058701602052604090205490915061ffff16156129d1576040516366e6550f60e01b815261ffff8c1660048201526024016108d5565b60008181526005860160205260409020805461ffff191661ffff8d16908117909155612a019060068701906131ed565b15612a255760405163ce9c78df60e01b815261ffff8c1660048201526024016108d5565b612a366006860161ffff8d16613459565b506040805161ffff8d16815260ff8a8116602083015289168183015290517fc27376e37bec2cd9acd6957a46ca5cba20a88a21fd0eff1b02cf8536462304769181900360600190a150612aae565b8154610100900460ff1660009081526008850160205260409020612aac9061ffff8c16613459565b505b7fa8932b9bd78638467b3e5c0f0ffa9c72761db9f7f605b8df2f7a899770d064ae8a89604051612adf929190615520565b60405180910390a15050505050505050505050565b612afd84612cb0565b6000612b08846118ac565b9050612b15818484612e61565b5050505050565b600061081661124d565b60006001600160401b03821115612b5357604051633995b34160e01b8152600481018390526024016108d5565b5067ffffffffffffffff60a01b60a09190911b166001600160a01b039091161790565b612b7e613465565b612b9b57604051631afcd79f60e31b815260040160405180910390fd5b565b6001600160a01b038116612be55760405162461bcd60e51b815260206004820152600f60248201526e2d32b9379031b7b73a3937b63632b960891b60448201526064016108d5565b612c148161092660017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c36186150c4565b612c434261092660017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b6150c4565b612c724361092660017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f16150c4565b7f1a2dd071001ebf6e03174e3df5b305795a4ad5d41d8fdb9ba41dbbe236713426814243604051612ca5939291906151cf565b60405180910390a150565b8061087e57604051636221cab960e01b815260040160405180910390fd5b82546001840154600160581b82046001600160a01b031691600160501b900460ff1615612d0e57604051636927c40b60e11b815260040160405180910390fd5b612d1a8282868661347f565b612d238361319b565b6001600160a01b031663f16a306683836040518363ffffffff1660e01b8152600401612d50929190615297565b602060405180830381865afa158015612d6d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d91919061561f565b612db257818160405163033aace360e41b81526004016108d5929190615297565b84546001600160401b0316612dc78383611c14565b6001600160401b031614612dee576040516337bcc65160e01b815260040160405180910390fd5b935093915050565b8254600160581b600160f81b03191683556000600184015560028301805464ffffffffff19169055612e28828261363c565b6040516001600160401b03821681527f883aa0807cbcd64d238a9795b0433f37884de37e569aff04d512cb1df62c6e019060200161187e565b6000612e6b612b1c565b6001600160401b0383166000908152600e8201602090815260408083208054600160401b900461ffff168452600d85018352818420825160a081018452858152938401859052918301849052606083018490526080830193909352929350909190612ed5876136c8565b6001600160a01b03166040820152600283015463ffffffff1615612f0c5760405163282404cd60e01b815260040160405180910390fd5b612f17838789612cce565b602083018190526001600160a01b03909116808352600285015460ff600160201b90910416606084018190526040840151612f6093869388939291612f5b8e61319b565b6136e8565b63ffffffff1660808201819052600003612f8d57604051638a874ec160e01b815260040160405180910390fd5b608081015160028401805463ffffffff191663ffffffff909216918217905560408083015183516020850151925163f2c6c6fb60e01b81526001600160a01b03918216600482015260248101939093526044830193909352600092169063f2c6c6fb906064016020604051808303816000875af1158015613012573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061303691906151b6565b90507f7a40f4421a71ae931ce048b2171197c34a78510813f1d3f4202935dd9f105b1a868360000151846020015185608001518587606001516040516130be969594939291906001600160401b039690961686526001600160a01b03949094166020860152604085019290925263ffffffff166060840152608083015260a082015260c00190565b60405180910390a15050505050505050565b604051631430d62960e21b81526001600160a01b038216906350c358a4906130fc903390600401614774565b602060405180830381865afa158015613119573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061313d919061561f565b61087e573360405163451cea1760e11b81526004016108d59190614774565b6000610624825490565b600061062482600884600001516001600160a01b031663016dff5d613ae3565b600060ff8316600883901b61ff001617610621565b600061062482600084600001516001600160a01b0316628e9691613ae3565b60006106218383613bef565b600061062482613c19565b60008080806131e08686613c24565b9097909650945050505050565b60008181526001830160205260408120541515610621565b60006106218383613c4f565b6000670de0b6b3a764000082101561323f57604051637046c4a960e01b8152600481018390526024016108d5565b600061325b613256670de0b6b3a764000085615362565b613d42565b9050600061327182670de0b6b3a764000061534b565b905083821c670de0b6b3a764000081900361328e57509392505050565b6706f05b59d3b200005b80156132e857670de0b6b3a76400006132b1838061534b565b6132bb9190615362565b9150671bc16d674ec8000082106132e0576132d681846150d7565b9250600182901c91505b60011c613298565b5090949350505050565b60609290921b6001600160601b03191660209190911b6bffffffffffffffff00000000161760189190911b63ff000000161790565b6040805160208082526104208201909252606091600091908082016104008036833701905050905060005b60208110156133985761336681600861534b565b8460001c901c82828151811061337e5761337e6151a0565b60ff90921660209283029190910190910152600101613352565b5092915050565b67ffffffffffffffff60401b604083901b166001600160401b038416176080826133ca5760006133cd565b60015b60ff16901b179392505050565b8051600090602081111561340b57604051633d71388b60e21b815260048101829052602060248201526044016108d5565b6000805b828110156134515761342281600861534b565b858281518110613434576134346151a0565b602090810291909101015160ff16901b919091179060010161340f565b509392505050565b60006106218383613e25565b600061346f611144565b54600160401b900460ff16919050565b6040516331a9108f60e11b8152600481018490526001600160a01b038084169190861690636352211e90602401602060405180830381865afa1580156134c9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134ed9190615091565b6001600160a01b03161461351857838360405163547208b960e11b81526004016108d5929190615297565b61352181613166565b6001600160a01b0316631789b7b1856040518263ffffffff1660e01b815260040161354c9190614774565b602060405180830381865afa158015613569573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061358d91906152b0565b60ff166000036135b2578360405163adc7fced60e01b81526004016108d59190614774565b80600001516001600160a01b03166318d928316040518163ffffffff1660e01b8152600401602060405180830381865afa1580156135f4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613618919061561f565b1561363657604051635e1633d360e11b815260040160405180910390fd5b50505050565b61366a816001600160401b0316613651612b1c565b60ff851660009081526020919091526040902090613459565b613687576040516313e5d5f160e11b815260040160405180910390fd5b6040805160ff841681526001600160401b03831660208201527f3e9a7af2aeebce9d24ef707bdb61adf2708293e374a19f8e9c31f905c68b43c19101610992565b600061062482600584600001516001600160a01b03166389dd9f13613ae3565b600786015460009060ff168610613715576040516315f7ec4560e21b8152600481018790526024016108d5565b86600801868154811061372a5761372a6151a0565b60009182526020822060088204015460079091166004026101000a900463ffffffff169150819003610c3457604051631a95890960e31b81526000906001600160a01b0384169063d4ac4848906137879088908890600401615297565b60a060405180830381865afa1580156137a4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137c8919061563c565b602081015181518b549293506137f29263ffffffff9283169290911690610100900460ff16613e74565b158015906138435750613803612b1c565b89546001919091019060009061382d906001600160a01b038916908890610100900460ff166132f2565b815260208101919091526040016000205460ff16155b80156138705750885461385f90610100900460ff1660056156d3565b60ff16816000015163ffffffff1610155b156138e9578854610100900460ff166001036138925762138884915050610c34565b8854610100900460ff166002036138af576222f238915050610c34565b8854610100900460ff166003036138cc57623234dc915050610c34565b8854610100900460ff166004036138e95762417780915050610c34565b6040805160058b0180546060602082028401810185529383018181526000948492849184018282801561393b57602002820191906000526020600020905b815481526020019060010190808311613927575b5050505050815260200160018201805480602002602001604051908101604052809291908181526020016000905b82821015613a01576000848152602090819020830180546040805182850281018501909152818152928301828280156139ed57602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff16815260200190600401906020826003010492830192600103820291508084116139b05790505b505050505081526020019060010190613969565b50505050815250509050866001600160a01b0316636f383c0f613a4083600001518b81518110613a3357613a336151a0565b6020026020010151613eca565b83602001518b81518110613a5657613a566151a0565b60200260200101518d60000160019054906101000a900460ff168a8a6040518663ffffffff1660e01b8152600401613a929594939291906156ef565b6020604051808303816000875af1158015613ab1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613ad5919061573e565b9a9950505050505050505050565b600080856020015185600e811115613afd57613afd6150ea565b600f8110613b0d57613b0d6151a0565b602002015190506001600160a01b03811615613b2a579050610633565b83836040518163ffffffff1660e01b8152600401602060405180830381865afa158015613b5b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613b7f9190615091565b866020015186600e811115613b9657613b966150ea565b600f8110613ba657613ba66151a0565b6001600160a01b039092166020928302919091015286015185600e811115613bd057613bd06150ea565b600f8110613be057613be06151a0565b60200201519695505050505050565b6000826000018281548110613c0657613c066151a0565b9060005260206000200154905092915050565b60006106248261315c565b60008080613c3285856131ba565b600081815260029690960160205260409095205494959350505050565b60008181526001830160205260408120548015613d38576000613c736001836150c4565b8554909150600090613c87906001906150c4565b9050808214613cec576000866000018281548110613ca757613ca76151a0565b9060005260206000200154905080876000018481548110613cca57613cca6151a0565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080613cfd57613cfd61575b565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610624565b6000915050610624565b6000600160801b8210613d6257608091821c91613d5f90826150d7565b90505b600160401b8210613d8057604091821c91613d7d90826150d7565b90505b600160201b8210613d9e57602091821c91613d9b90826150d7565b90505b620100008210613dbb57601091821c91613db890826150d7565b90505b6101008210613dd757600891821c91613dd490826150d7565b90505b60108210613df257600491821c91613def90826150d7565b90505b60048210613e0d57600291821c91613e0a90826150d7565b90505b60028210613e20576106246001826150d7565b919050565b6000818152600183016020526040812054613e6c57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610624565b506000610624565b6000806005613e8586866001613fbe565b613e8f9190615362565b613e9a9060016150d7565b9050828111613eaa5760006107d9565b613eb483826150c4565b613ebf90600a6150d7565b6107d990600261545a565b60606000613ed783613327565b90506000805b8251811015613f2057828181518110613ef857613ef86151a0565b602002602001015160ff1660000315613f205781613f1581615332565b925050600101613edd565b50806001600160401b03811115613f3957613f39614323565b604051908082528060200260200182016040528015613f62578160200160208202803683370190505b50925060005b81811015613fb657828181518110613f8257613f826151a0565b6020026020010151848281518110613f9c57613f9c6151a0565b60ff90921660209283029190910190910152600101613f68565b505050919050565b600080613fcb8585613ff4565b9050828015613fe457506002613fe28260016150d7565b115b15610633576107d96002826150c4565b805b60638110156106245761400a8360016150d7565b61401382614027565b63ffffffff16101561062457600101613ff6565b600063ffffffff821615806140435750606363ffffffff831610155b1561405057506000919050565b670de0b6b3a764000061408a614067846063615771565b61407290600261578e565b6122299063ffffffff16670de0b6b3a764000061534b565b61409c90675cfb2e807b1e00006150c4565b6140af620186a063ffffffff861661534b565b6140b9919061534b565b6106249190615362565b6040518060e00160405280600060ff168152602001600060ff1681526020016060815260200160008019168152602001606081526020016060815260200161411e604051806040016040528060608152602001606081525090565b905290565b604051806040016040528060006001600160a01b0316815260200161411e614291565b50805460008255600701600890049060005260206000209081019061087e91906142b0565b508054600082559060005260206000209081019061087e91906142b0565b508054600082559060005260206000209081019061087e91906142c5565b828054828255906000526020600020906007016008900481019282156142465791602002820160005b8382111561421457835183826101000a81548163ffffffff021916908363ffffffff16021790555092602001926004016020816003010492830192600103026141d0565b80156142445782816101000a81549063ffffffff0219169055600401602081600301049283019260010302614214565b505b506142529291506142b0565b5090565b828054828255906000526020600020908101928215614246579160200282015b82811115614246578251825591602001919060010190614276565b604051806101e00160405280600f906020820280368337509192915050565b5b8082111561425257600081556001016142b1565b808211156142525760006142d98282614146565b506001016142c5565b6001600160a01b038116811461087e57600080fd5b6000806040838503121561430a57600080fd5b8235614315816142e2565b946020939093013593505050565b634e487b7160e01b600052604160045260246000fd5b60405161012081016001600160401b038111828210171561435c5761435c614323565b60405290565b604051601f8201601f191681016001600160401b038111828210171561438a5761438a614323565b604052919050565b600082601f8301126143a357600080fd5b81356001600160401b038111156143bc576143bc614323565b6143cf601f8201601f1916602001614362565b8181528460208386010111156143e457600080fd5b816020850160208301376000918101602001919091529392505050565b6000806000806080858703121561441757600080fd5b8435614422816142e2565b93506020850135614432816142e2565b92506040850135915060608501356001600160401b0381111561445457600080fd5b61446087828801614392565b91505092959194509250565b60006020828403121561447e57600080fd5b8135614489816142e2565b9392505050565b6001600160401b038116811461087e57600080fd5b801515811461087e57600080fd5b8035613e20816144a5565b600080604083850312156144d157600080fd5b82356144dc81614490565b915060208301356144ec816144a5565b809150509250929050565b803561ffff81168114613e2057600080fd5b60ff8116811461087e57600080fd5b8035613e2081614509565b6000806000806080858703121561453957600080fd5b614542856144f7565b9350602085013561455281614509565b92506040850135614562816142e2565b9396929550929360600135925050565b6000806040838503121561458557600080fd5b823561459081614490565b915060208301356001600160401b038111156145ab57600080fd5b6145b785828601614392565b9150509250929050565b6000602082840312156145d357600080fd5b813561448981614490565b63ffffffff8116811461087e57600080fd5b6000806000806080858703121561460657600080fd5b8435614611816145de565b93506020850135614621816142e2565b925060408501359150606085013561463881614509565b939692955090935050565b60008060006060848603121561465857600080fd5b8335614663816142e2565b925060208401359150604084013561467a816142e2565b809150509250925092565b60006020828403121561469757600080fd5b5035919050565b600080600080600060a086880312156146b657600080fd5b85356146c1816142e2565b945060208601356146d181614509565b935060408601356146e1816142e2565b94979396509394606081013594506080013592915050565b6000806040838503121561470c57600080fd5b50508035926020909101359150565b6000815180845260005b8181101561474157602081850181015186830182015201614725565b506000602082860101526020601f19601f83011685010191505092915050565b602081526000610621602083018461471b565b6001600160a01b0391909116815260200190565b60008151808452602080850194506020840160005b838110156147c25781516001600160a01b03168752958201959082019060010161479d565b509495945050505050565b60008151808452602080850194506020840160005b838110156147c2578151875295820195908201906001016147e2565b60008151808452602080850194506020840160005b838110156147c257815163ffffffff1687529582019590820190600101614813565b61ffff8c1681528a151560208201526001600160a01b038a1660408201526060810189905263ffffffff88166080820152600061016060ff891660a08401528060c084015261488681840189614788565b905082810360e084015261489a81886147cd565b90508281036101008401526148af81876147cd565b60ff861661012085015290508281036101408401526148ce81856147fe565b9e9d5050505050505050505050505050565b6000806000606084860312156148f557600080fd5b6148fe846144f7565b9250602084013561490e81614509565b9150604084013561467a81614509565b600080600080600080600060e0888a03121561493957600080fd5b8735614944816142e2565b96506020880135955060408801359450606088013561496281614509565b9350608088013561497281614509565b925060a088013561498281614509565b915060c088013561499281614509565b8091505092959891949750929550565b6000806000606084860312156149b757600080fd5b83356149c281614490565b925060208401356149d2816142e2565b929592945050506040919091013590565b600080600080608085870312156149f957600080fd5b8435614a04816142e2565b966020860135965060408601359560600135945092505050565b600060208284031215614a3057600080fd5b610621826144f7565b60008282518085526020808601955060208260051b8401016020860160005b84811015614a8657601f19868403018952614a748383516147fe565b98840198925090830190600101614a58565b5090979650505050505050565b6000815160408452614aa860408501826147cd565b9050602083015184820360208601526107d98282614a39565b6020815260ff825116602082015260006020830151614ae5604084018260ff169052565b50604083015160e06060840152614b006101008401826147fe565b9050606084015160808401526080840151601f19808584030160a0860152614b2883836147cd565b925060a08601519150808584030160c0860152614b4583836147cd565b925060c08601519150808584030160e0860152506107d98282614a93565b60008151808452602080850194506020840160005b838110156147c257815160ff1687529582019590820190600101614b78565b6020815260006106216020830184614b63565b60006001600160401b03821115614bc357614bc3614323565b5060051b60200190565b600082601f830112614bde57600080fd5b81356020614bf3614bee83614baa565b614362565b828152600592831b8501820192828201919087851115614c1257600080fd5b8387015b85811015614a865780356001600160401b03811115614c355760008081fd5b8801603f81018a13614c475760008081fd5b858101356040614c59614bee83614baa565b82815291851b8301810191888101908d841115614c765760008081fd5b938201935b83851015614ca05784359250614c9083614509565b8282529389019390890190614c7b565b885250505093850193508401614c16565b600082601f830112614cc257600080fd5b81356020614cd2614bee83614baa565b8083825260208201915060208460051b870101935086841115614cf457600080fd5b602086015b84811015614d19578035614d0c816145de565b8352918301918301614cf9565b509695505050505050565b600082601f830112614d3557600080fd5b81356020614d45614bee83614baa565b82815260059290921b84018101918181019086841115614d6457600080fd5b8286015b84811015614d195780356001600160401b03811115614d875760008081fd5b614d958986838b0101614cb1565b845250918301918301614d68565b600082601f830112614db457600080fd5b81356020614dc4614bee83614baa565b8083825260208201915060208460051b870101935086841115614de657600080fd5b602086015b84811015614d195780358352918301918301614deb565b600082601f830112614e1357600080fd5b81356020614e23614bee83614baa565b8083825260208201915060208460051b870101935086841115614e4557600080fd5b602086015b84811015614d19578035614e5d81614490565b8352918301918301614e4a565b600082601f830112614e7b57600080fd5b81356020614e8b614bee83614baa565b8083825260208201915060208460051b870101935086841115614ead57600080fd5b602086015b84811015614d19578035614ec5816144a5565b8352918301918301614eb2565b60008060008060008060c08789031215614eeb57600080fd5b614ef4876144f7565b95506020870135614f0481614509565b945060408701356001600160401b0380821115614f2057600080fd5b90880190610120828b031215614f3557600080fd5b614f3d614339565b823582811115614f4c57600080fd5b614f588c828601614bcd565b825250602083013582811115614f6d57600080fd5b614f798c828601614d24565b602083015250604083013582811115614f9157600080fd5b614f9d8c828601614cb1565b604083015250614faf60608401614518565b6060820152614fc060808401614518565b608082015260a083013582811115614fd757600080fd5b614fe38c828601614da3565b60a08301525060c083013582811115614ffb57600080fd5b6150078c828601614e02565b60c08301525060e08301358281111561501f57600080fd5b61502b8c828601614e02565b60e083015250610100808401358381111561504557600080fd5b6150518d828701614e6a565b82840152505080965050505061506960608801614518565b925061507760808801614518565b915061508560a088016144b3565b90509295509295509295565b6000602082840312156150a357600080fd5b8151614489816142e2565b634e487b7160e01b600052601160045260246000fd5b81810381811115610624576106246150ae565b80820180821115610624576106246150ae565b634e487b7160e01b600052602160045260246000fd5b6001600160a01b0386811682528581166020830152604082018590528316606082015260a081016003831061514557634e487b7160e01b600052602160045260246000fd5b8260808301529695505050505050565b60006020828403121561516757600080fd5b815161448981614490565b60008060006060848603121561518757600080fd5b8351925060208401519150604084015190509250925092565b634e487b7160e01b600052603260045260246000fd5b6000602082840312156151c857600080fd5b5051919050565b6001600160a01b039390931683526020830191909152604082015260600190565b8781528660208201526001600160401b0386166040820152600060018060a01b03808716606084015260e0608084015261522d60e084018761471b565b941660a08301525063ffffffff9190911660c09091015295945050505050565b6000806000806080858703121561526357600080fd5b845161526e816144a5565b602086015160408701519195509350615286816145de565b6060860151909250614638816144a5565b6001600160a01b03929092168252602082015260400190565b6000602082840312156152c257600080fd5b815161448981614509565b634e487b7160e01b600052601260045260246000fd5b600060ff8316806152f6576152f66152cd565b8060ff84160491505092915050565b60ff8181168382160190811115610624576106246150ae565b60008261532d5761532d6152cd565b500690565b600060018201615344576153446150ae565b5060010190565b8082028115828204841417610624576106246150ae565b600082615371576153716152cd565b500490565b600181815b808511156153b1578160001904821115615397576153976150ae565b808516156153a457918102915b93841c939080029061537b565b509250929050565b6000826153c857506001610624565b816153d557506000610624565b81600181146153eb57600281146153f557615411565b6001915050610624565b60ff841115615406576154066150ae565b50506001821b610624565b5060208310610133831016604e8410600b8410161715615434575081810a610624565b61543e8383615376565b8060001904821115615452576154526150ae565b029392505050565b600061062183836153b9565b60008282518085526020808601955060208260051b8401016020860160005b84811015614a8657601f198684030189526154a1838351614b63565b98840198925090830190600101615485565b60008151808452602080850194506020840160005b838110156147c25781516001600160401b0316875295820195908201906001016154c8565b60008151808452602080850194506020840160005b838110156147c2578151151587529582019590820190600101615502565b61ffff831681526040602082015260008251610120806040850152615549610160850183615466565b91506020850151603f19808685030160608701526155678483614a39565b9350604087015191508086850301608087015261558484836147fe565b93506060870151915061559c60a087018360ff169052565b608087015160ff1660c087015260a0870151868503820160e088015291506155c484836147cd565b935060c087015191506101008187860301818801526155e385846154b3565b945060e088015192508187860301848801526155ff85846154b3565b9450808801519350508086850301610140870152505061152082826154ed565b60006020828403121561563157600080fd5b8151614489816144a5565b600060a0828403121561564e57600080fd5b60405160a081018181106001600160401b038211171561567057615670614323565b604052825161567e816145de565b8152602083015161568e816145de565b602082015260408301516156a1816145de565b604082015260608301516156b4816145de565b606082015260808301516156c7816145de565b60808201529392505050565b60ff8181168382160290811690818114613398576133986150ae565b60a08152600061570260a0830188614b63565b828103602084015261571481886147fe565b60ff96909616604084015250506001600160a01b0392909216606083015260809091015292915050565b60006020828403121561575057600080fd5b8151614489816145de565b634e487b7160e01b600052603160045260246000fd5b63ffffffff828116828216039080821115613398576133986150ae565b63ffffffff818116838216019080821115613398576133986150ae56fe22573091f17911fb166032a3d9e0554aa73d31b7b7ddea4a4dd2995650af84bda264697066735822122051b07f07a87b4e03d3d5e165d80ccd1a459b4a640c665371780d53ddaa9075e764736f6c63430008170033
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 35 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
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.