Source Code
Overview
S Balance
S Value
$0.00View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Cross-Chain Transactions
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 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 TierForbidden();
error SandboxPrepaidOnly();
error SandboxNgZeroOnly();
error SandboxModeNotAllowed();
error SandboxUpgradeModeRequired();
error SandboxModeRequired();
error SandboxItemOutside();
error SandboxItemNotActive();
error SandboxItemNotRegistered();
error SandboxItemAlreadyEquipped();
error SandboxDifferentHeroesNotAllowed();
error HeroWasTransferredBetweenAccounts();
error SandboxFreeHeroNotAllowed();
//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);
error LevelIsNotEnoughForThisBiome(uint biome, uint lvl);
//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();
error PvpWrongDay(uint day);
error PvpWrongTime(uint time);
error ItemAlreadyUsed(address item, uint itemId, address itemHero, uint itemHeroId);
/// @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.ActionResultEvent 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.ActionResultEvent 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 FreeHeroCreated(address hero, uint heroId);
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);
event GaugeChanged(address gauge);
event NotifyRewardAmount(address token, uint amount);
event UpdatePeriod(uint myrdAmount);
//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.EventActionInfo 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 GuildBannerChanged(uint guildId, string newBanner);
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;
function gauge() external view returns (address);
}// 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,
STORY_ASCRA_36,
STORY_ASCRA_RARE_37,
STORY_BANFOOT_38,
STORY_BANFOOT_RARE_39,
STORY_ENFITILIA_40,
STORY_ENFITILIA_RARE_41,
ENEMY_NPC_ASCRA_42,
ENEMY_NPC_ASCRA_RARE_43,
ENEMY_NPC_BANFOOT_44,
ENEMY_NPC_BANFOOT_RARE_45,
ENEMY_NPC_ENFITILA_46,
ENEMY_NPC_ENFITILA_RARE_47,
STORY_FREE_LAND_48,
STORY_GAME_THEORY_49,
STORY_WEARY_50,
STORY_EXHAUSTED_51,
STORY_TRAPPED_52,
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;
uint32[] mintItemsMF;
int32 heal;
int32 manaRegen;
int32 lifeChancesRecovered;
int32 damage;
int32 manaConsumed;
uint32 objectId;
uint32 experience;
uint heroTokenId;
uint iteration;
uint32[] rewriteNextObject;
}
struct ActionResultEvent {
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;
/// @notice guild id => guildBanner
mapping(uint guildId => string) guildBanner;
}
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;
/// @notice 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 List of packed skill-tokens equipped on the helper at the moment of asking him for help by the hero
/// @dev Packed skill contains item address, item id and slot number (use unpackNftIdWithValue)
/// Size of the array can be 0..3
mapping(bytes32 packedHero => bytes32[] packedSkills) helperSkills;
}
/// @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
/// @dev Only post-paid hero has tier = 0
/// @dev Only free-hero has paidToken != 0 && paidAmount == 0
struct HeroInfo {
/// @notice Hero tier = [0..3].
/// 0 - the hero is post-paid, it can be changed by upgrading the hero to pre-paid
/// always 1 for sandbox-heroes
/// always 1 for free-heroes
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 and for free-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);
function helperSkills(address hero, uint heroId) external view returns (
address[] memory items,
uint[] memory itemIds,
uint[] memory slots
);
}// 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, uint32 magicFind) 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;
function itemControllerHelper() external view returns (address);
}// 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;
/// @dev week => item => hero+heroId
mapping(uint32 week => mapping(bytes32 itemPacked => bytes32)) usedItems;
}
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 a better 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 EventActionInfo {
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";
import "../interfaces/IERC721.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;
}
/// @notice Adjust the dungeon completion reward based on the hero's NG level
function _getAdjustedReward(uint amount, uint heroNgLevel) internal pure returns (uint) {
uint rewardPercent = heroNgLevel == 0
? 40
: heroNgLevel == 1
? 60
: heroNgLevel == 2
? 80
: 100;
return amount * rewardPercent / 100;
}
function _ownerOf(address hero, uint heroId) internal view returns (address) {
return IERC721(hero).ownerOf(heroId);
}
}// 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;
uint private constant BOSS_5_ASCRA_EVENT = 5360522;
uint private constant BOSS_5_BANFOOT_EVENT = 5380513;
uint private constant BOSS_5_ENFITILIA_EVENT = 5400532;
//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);
}
if (dungAttributes.biome == 5) {
uint inDungeonBanfoot = sc.heroCustomData(heroToken, heroTokenId, StatControllerLib.DUNGEON_BANFOOT);
if (inDungeonBanfoot == 1) {
return uint32(BOSS_5_BANFOOT_EVENT);
}
uint inDungeonEnfitilia = sc.heroCustomData(heroToken, heroTokenId, StatControllerLib.DUNGEON_ENFITILIA);
if (inDungeonEnfitilia == 1) {
return uint32(BOSS_5_ENFITILIA_EVENT);
}
uint inDungeonAscra = sc.heroCustomData(heroToken, heroTokenId, StatControllerLib.DUNGEON_ASKRA);
if (inDungeonAscra == 1) {
return uint32(BOSS_5_ASCRA_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";
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 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,
IGOC.ActionResultEvent(
{
kill: c.result.kill,
completed: c.result.completed,
heroToken: c.result.heroToken,
mintItems: c.result.mintItems,
heal: c.result.heal,
manaRegen: c.result.manaRegen,
lifeChancesRecovered: c.result.lifeChancesRecovered,
damage: c.result.damage,
manaConsumed: c.result.manaConsumed,
objectId: c.result.objectId,
experience: c.result.experience,
heroTokenId: c.result.heroTokenId,
iteration: c.result.iteration,
rewriteNextObject: c.result.rewriteNextObject
}
)
, 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), result.mintItemsMF.length > i ? result.mintItemsMF[i] : 0);
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 heroNgLevel = heroController.getHeroInfo(hero, heroId).ngLevel;
IGameToken gameToken = ControllerContextLib.gameToken(cc);
uint amount = IMinter(gameToken.minter()).mintDungeonReward(dungId, biome, lvlForMint);
uint reward = AppLib._getAdjustedReward(amount, heroNgLevel);
if (amount > reward) {
gameToken.burn(amount - reward);
amount = reward;
}
// SCR-1602: we should not combine rewards with treasury tokens ideally
// but historically they are combined, so not rewards but whole combined amount is divided between tax/reinf/hero
_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);
if (payToken == address(0)) revert IAppErrors.ZeroToken(); // old free hero is not supported anymore (i.e. hero 5, F2P)
uint16 dungNum = dungStatus.dungNum;
_setDungeonCompleted(_S(), dungNum, dungId, heroToken, heroTokenId);
if (claim) {
_claimAll(cc, msgSender, dungId, dungNum, dungStatus, heroToken, heroTokenId);
}
_heroExit(_S(), heroController, heroToken, heroTokenId);
// 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 {
(IFightCalculator.AttackInfo memory attackInfo) = abi.decode(data, (IFightCalculator.AttackInfo));
uint8[] memory map = s_.skillSlotsForDurabilityReduction[heroToken.packNftId(heroTokenId)].unpackUint8Array();
if(map.length != 3) {
map = new uint8[](3);
}
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;
}
}
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.
function _claimAll(
ControllerContextLib.ControllerContext memory cc,
address msgSender,
uint64 dungId,
uint16 dungNum,
IDungeonFactory.DungeonStatus storage dungStatus,
address hero,
uint heroId
) 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.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 {
address gameToken = address(ControllerContextLib.gameToken(cc));
treasuryTokens.remove(token);
if (amount != 0) {
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 {
// SCR-1602: we should split only minted rewards between tax/reinf/hero
// but historically treasury tokens are combined with minted rewards
// so, the whole combined amount (of the game tokens) is divided between tax/reinf/hero here
uint amountMinusTax = amount;
if (context.taxPercent != 0 && context.guildBank != address(0) && gameToken == token) {
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) || gameToken != token
? 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)) {
// 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");
/// @notice Custom data of the hero. Value is locate in Banfoot dungeon
bytes32 public constant DUNGEON_BANFOOT = bytes32("DUNG_BF");
/// @notice Custom data of the hero. Value is locate in Enfitilia dungeon
bytes32 public constant DUNGEON_ENFITILIA = bytes32("DUNG_EN");
/// @notice Custom data of the hero. Value is locate in Askra dungeon
bytes32 public constant DUNGEON_ASKRA = bytes32("DUNG_AS");
//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
&& controller_.governance() != sender // gov can change anything
) 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 xy = x * 1e18 / y;
uint base = (uint(4) * 1e18 / uint(3)) * xy / 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": "0x2aa17bf0e417bcede3dc9778d65d27a4cdef34a3"
},
"contracts/lib/DungeonLib.sol": {
"DungeonLib": "0xd9b4f707646f1a162e56b84211bca451de9f5ab5"
}
}
}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
60806040523480156200001157600080fd5b507ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff1615906001600160401b03166000811580156200005d5750825b90506000826001600160401b031660011480156200007a5750303b155b90508115801562000089575080155b15620000a85760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b03191660011785558315620000d757845460ff60401b1916680100000000000000001785555b83156200011e57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050506159d880620001336000396000f3fe608060405234801561001057600080fd5b50600436106102295760003560e01c80639a641e0211610131578063b5a44d04116100b3578063b5a44d0414610509578063b6d2fe6c1461051c578063b701c1aa1461054a578063bd410d641461056a578063be2e0ad81461057d578063d06969c71461059d578063dee1f0e4146105b0578063e76bd825146105c3578063f77c4791146105d6578063fee27cbe146105de578063ffa1ad74146105f157600080fd5b80639a641e02146104355780639d0bcca0146104485780639f3cfa7a1461045d578063a433af6f14610465578063a46374b314610478578063a4efd309146104a2578063a7540ee5146104b5578063ac257b3a146104c8578063ad8c5755146104db578063b2192fdd146104ee578063b429afeb146104f657600080fd5b80634fac6ccd116101ba5780634fac6ccd14610326578063572b6c051461033957806357bd05b01461034c5780635a67eb481461035f5780636a8a5e8c146103725780636bb4b459146103855780637442adbc146103b05780637be89369146103c35780637c00116c146103e95780637cc96380146103fc578063936725ec1461040457600080fd5b80630e30c13e1461022e578063150b7a021461025857806319ab453c1461028457806327dd892f146102995780632fcd5deb146102ac578063325a19f1146102cf57806338430cce146102e55780634593144c146102f857806348b371bf146103005780634d52532c14610313575b600080fd5b61024161023c3660046144ce565b610615565b60405160ff90911681526020015b60405180910390f35b61026b6102663660046145d8565b61062a565b6040516001600160e01b0319909116815260200161024f565b610297610292366004614643565b61063b565b005b6102976102a7366004614695565b610735565b6102bf6102ba3660046146fa565b61075b565b604051901515815260200161024f565b6102d76107e2565b60405190815260200161024f565b6102976102f3366004614749565b61081b565b6102d761083d565b61029761030e366004614798565b61086d565b6102d7610321366004614798565b610881565b610297610334366004614643565b61088c565b6102bf610347366004614643565b61099e565b61029761035a3660046147c7565b610a13565b61029761036d3660046144ce565b610ab6565b61029761038036600461481a565b610ac8565b61039861039336600461481a565b610b44565b6040516001600160401b03909116815260200161024f565b6102d76103be36600461485c565b610c06565b6103d66103d1366004614875565b610c11565b60405161ffff909116815260200161024f565b6103986103f73660046148d0565b610c3f565b6102d7610c4b565b610428604051806040016040528060058152602001640312e302e360dc1b81525081565b60405161024f9190614938565b6103986104433660046144ce565b610c69565b610450610c75565b60405161024f919061494b565b610398610ca5565b6102976104733660046144ce565b610caf565b61048b610486366004614798565b610d34565b60405161024f9b9a99989796959493929190614a0c565b6102976104b0366004614ab7565b610d74565b6102d76104c3366004614643565b610d8c565b6102d76104d6366004614af5565b610d97565b6102bf6104e93660046144ce565b610dc0565b610241610dd4565b6102bf610504366004614643565b610dde565b610297610517366004614b79565b610e03565b61052f61052a366004614bba565b610e8d565b6040805193845260208401929092529082015260600161024f565b61055d610558366004614bf5565b610f49565b60405161024f9190614c98565b6102976105783660046144ce565b610f5a565b61059061058b3660046144ce565b610fa8565b60405161024f9190614d6e565b6103986105ab36600461481a565b610fb4565b6102bf6105be366004614643565b61101b565b6102976105d13660046150a9565b6110a0565b6104506110b6565b6102976105ec366004614798565b6110e6565b61042860405180604001604052806005815260200164322e312e3160d81b81525081565b60006106218383611107565b90505b92915050565b630a85bd0160e11b5b949350505050565b6000610645611144565b805490915060ff600160401b82041615906001600160401b031660008115801561066c5750825b90506000826001600160401b031660011480156106885750303b155b905081158015610696575080155b156106b45760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156106de57845460ff60401b1916600160401b1785555b6106e786611168565b831561072d57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b610757610740611179565b6107486110b6565b610750611191565b85856111b3565b5050565b60006107d961076861124d565b6107706110b6565b6001600160a01b0316628e96916040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107d09190615268565b87878787611271565b95945050505050565b600061081661081260017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b61529b565b5490565b905090565b610757610826611179565b61082e6110b6565b610836611191565b858561152a565b600061081661081260017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f161529b565b61087e6108786110b6565b82611768565b50565b6000610624826117c0565b3330146108de5760405162461bcd60e51b815260206004820152601b60248201527a24b731b932b0b9b2903932bb34b9b4b7b7103337b93134b23232b760291b60448201526064015b60405180910390fd5b60006108fc610812600160008051602061598383398151915261529b565b6109079060016152ae565b905061092981610926600160008051602061598383398151915261529b565b55565b6109588261092660017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e461529b565b604080518281526001600160a01b03841660208201527ff27e2ef832a4eb8ed8ec553b875eecd44764cda95b1c24170e281539e0a869c891015b60405180910390a15050565b60006001600160a01b03821673d8253782c45a12053594b9deb72d8e8ab2fca54c14806109e757506001600160a01b0382167352ceba41da235af367bfc0b0ccd3314cb901bb5f145b8061062457506001600160a01b03821673102f1f556cd9c3d5f820e6920a8931657c5da21b1492915050565b732aa17bf0e417bcede3dc9778d65d27a4cdef34a3637c1ecb8e610a356110b6565b6040516001600160e01b031960e084901b1681526001600160a01b03918216600482015263ffffffff8816602482015290861660448201526064810185905260ff8416608482015260a40160006040518083038186803b158015610a9857600080fd5b505af4158015610aac573d6000803e3d6000fd5b5050505050505050565b610757610ac16110b6565b83836117ec565b732aa17bf0e417bcede3dc9778d65d27a4cdef34a3632ca22aeb610aea6110b6565b85858560016040518663ffffffff1660e01b8152600401610b0f9594939291906152d7565b60006040518083038186803b158015610b2757600080fd5b505af4158015610b3b573d6000803e3d6000fd5b50505050505050565b6000732aa17bf0e417bcede3dc9778d65d27a4cdef34a363987707f9610b68611179565b610b706110b6565b610b78611191565b6040516001600160e01b031960e086901b16815292151560048401526001600160a01b03918216602484015281166044830152808816606483015260848201879052851660a482015260c4015b602060405180830381865af4158015610be2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610633919061532c565b60006106248261188b565b600080610c1d876118ac565b9050610c34610c2a61124d565b82888888886118c4565b979650505050505050565b60006106218383611bf1565b6000610816610812600160008051602061598383398151915261529b565b60006106218383611c14565b600061081661081260017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e461529b565b6000610816611c57565b732aa17bf0e417bcede3dc9778d65d27a4cdef34a363dc01b901610cd16110b6565b6040516001600160e01b031960e084901b1681526001600160a01b0391821660048201529085166024820152604481018490526064015b60006040518083038186803b158015610d2057600080fd5b505af415801561072d573d6000803e3d6000fd5b600080600080600080606080606060006060610d4f8c611c73565b9a509a509a509a509a509a509a509a509a509a509a5091939597999b90929496989a50565b610d87610d7f6110b6565b848484611f1c565b505050565b60006106248261210a565b6000610db48888888860ff168860ff168860ff168860ff16612136565b98975050505050505050565b6000610621610dcd6110b6565b84846122d5565b60006108166123e8565b6000610de86110b6565b6001600160a01b0316826001600160a01b0316149050919050565b732aa17bf0e417bcede3dc9778d65d27a4cdef34a363efa20d9b610e25611179565b610e2d6110b6565b610e35611191565b6040516001600160e01b031960e086901b16815292151560048401526001600160a01b039182166024840152811660448301526001600160401b03871660648301528516608482015260a4810184905260c401610b0f565b6000806000732aa17bf0e417bcede3dc9778d65d27a4cdef34a3633e457e4f610eb46110b6565b6040516001600160e01b031960e084901b1681526001600160a01b039182166004820152908a16602482015260448101899052606481018890526084810187905260a401606060405180830381865af4158015610f15573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f399190615349565b9250925092509450945094915050565b610f5161429a565b610624826123fe565b732aa17bf0e417bcede3dc9778d65d27a4cdef34a3632ca22aeb610f7c6110b6565b8484610f86611191565b60026040518663ffffffff1660e01b8152600401610d089594939291906152d7565b606061062183836126b4565b6000732aa17bf0e417bcede3dc9778d65d27a4cdef34a363c77d56a2610fd86110b6565b6040516001600160e01b031960e084901b1681526001600160a01b0391821660048201528186166024820152908716604482015260648101869052608401610bc5565b6000816001600160a01b031661102f6110b6565b6001600160a01b0316635aa6e6756040518163ffffffff1660e01b8152600401602060405180830381865afa15801561106c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110909190615268565b6001600160a01b03161492915050565b61072d6110ab6110b6565b8787878787876126ed565b600061081661081260017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c361861529b565b61087e6110f1611179565b6110f96110b6565b611101611191565b84612af4565b6000611111612b1c565b60030160006111296001600160a01b03861685612b26565b815260208101919091526040016000205460ff169392505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b611170612b76565b61087e81612b9d565b60006111843361099e565b8061081657505032331490565b600061119c3361099e565b156111ae575060131936013560601c90565b503390565b6111bc85612cb0565b60405163139ed9b560e21b81526001600160a01b0380861660048301526001600160401b038416602483015282151560448301528416606482015273d9b4f707646f1a162e56b84211bca451de9f5ab590634e7b66d49060840160006040518083038186803b15801561122e57600080fd5b505af4158015611242573d6000803e3d6000fd5b505050505050505050565b7fae5971282b317bbed599861775fe0712755bb3b2f655bfe8fb14280d8429f60090565b61ffff84166000908152600d870160205260408120600281015460ff8082169160081c81169087168211806112a85750808760ff16115b156112b95760009350505050611520565b505060008160030180548060200260200160405190810160405280929190818152602001828054801561130b57602002820191906000526020600020905b8154815260200190600101908083116112f7575b5050505050905060008260040180548060200260200160405190810160405280929190818152602001828054801561136257602002820191906000526020600020905b81548152602001906001019080831161134e575b505085519394506000925050505b8181101561151657600084828151811061138c5761138c615377565b602002602001015190506000801b81036113a6575061150e565b60008060006113dd8786815181106113c0576113c0615377565b602002602001015190604082901c90608083901c60ff1660011490565b92509250925060008161145e578f6001600160a01b0316630a82c861866040518263ffffffff1660e01b815260040161141891815260200190565b602060405180830381865afa158015611435573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611459919061538d565b6114cf565b8f6001600160a01b0316631c2aafe78e8e886040518463ffffffff1660e01b815260040161148e939291906153a6565b602060405180830381865afa1580156114ab573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114cf919061538d565b9050836001600160401b03168110806114f05750826001600160401b031681115b156115085760009a5050505050505050505050611520565b50505050505b600101611370565b5060019450505050505b9695505050505050565b6000611534612b1c565b90506000611541866118ac565b6040805160a0810182526000808252602080830182905282840182905260608301829052608083018290526001600160401b0389168252600e870181528382208054600160401b900461ffff168352600d8801909152929020929350916115a78a612cb0565b6115b2828986612cce565b50508160020160009054906101000a900463ffffffff16836040019063ffffffff16908163ffffffff168152505073d9b4f707646f1a162e56b84211bca451de9f5ab56311942d1783838a8c8b8f8a604001516040518863ffffffff1660e01b815260040161162797969594939291906153c7565b608060405180830381865af4158015611644573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116689190615424565b1515602087015263ffffffff1660608601526080850152158015845261169a57815460ff60501b1916600160501b1782555b6080830151156116cb57608083015160028301805460ff909216600160201b0264ff00000000199092169190911790555b826040015163ffffffff16836060015163ffffffff161461170757606083015160028301805463ffffffff191663ffffffff9092169190911790555b826020015115611727578054611727908390610100900460ff1689612df6565b825115801561173e5750600282015463ffffffff16155b801561174c57508260200151155b1561175c5761175c848989612e61565b50505050505050505050565b611771826130d0565b604051632586866b60e11b81526001600160a01b03831660048201526001600160401b038216602482015273d9b4f707646f1a162e56b84211bca451de9f5ab590634b0d0cd690604401610d08565b60006117ca612b1c565b6001600160401b039092166000908152600f9290920160205250604090205490565b6117f5836130d0565b600581101561181a57604051634bebee7f60e01b8152600481018290526024016108d5565b80611823612b1c565b600a016000846001600160a01b03166001600160a01b03168152602001908152602001600020819055507f996f9d801b1a05cd3c7b555e5b6f61db994ea8ef57125b5b5d3b07b9dad4e2cf828260405161187e92919061546e565b60405180910390a1505050565b6000610624611898612b1c565b60008481526020919091526040902061315c565b6118b46142fa565b6001600160a01b03909116815290565b60008460ff166000036118ea5760405163a8473eab60e01b815260040160405180910390fd5b6000806118f688613166565b604051631ebd249360e01b81529091506001600160a01b03821690631ebd249390611927908990899060040161546e565b602060405180830381865afa158015611944573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119689190615487565b915060008960050160006119ff60058b61198291906154ba565b61198d9060016154dc565b604051631789b7b160e01b81526001600160a01b03871690631789b7b1906119b9908e9060040161494b565b602060405180830381865afa1580156119d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119fa9190615487565b613186565b8152602081019190915260400160009081205461ffff169150819003611a5f57896005016000611a4760058b611a3591906154ba565b611a409060016154dc565b6000613186565b815260208101919091526040016000205461ffff1690505b8061ffff16600003611a9457896005016000611a7c600080613186565b815260208101919091526040016000205461ffff1690505b61ffff811615611b1a576001600160a01b03871660a087901b67ffffffffffffffff60a01b161760f082901b6001600160f01b03191617600090815260028b01602052604090205460ff16158015611b0b575061ffff81166000908152600d8b01602052604090205460ff84811661010090920416145b15611b1a579250611520915050565b505060ff81166000908152600889016020526040812090611b3a8261315c565b905080600003611b61576040516283533360e01b815260ff841660048201526024016108d5565b6000611b6c8a61319b565b9050600080611b7b84896154f5565b905060005b84811015611bd757611b9286836131ba565b9250611ba28e85858f8f8f611271565b15611bb65782975050505050505050611520565b81611bc081615509565b925050848210611bcf57600091505b600101611b80565b5060405163b940612960e01b815260040160405180910390fd5b600061062183611bff612b1c565b600085815260209190915260409020906131ba565b6000611c1e612b1c565b6004016000611c366001600160a01b03861685612b26565b81526020810191909152604001600020546001600160401b03169392505050565b6000611c61612b1c565b600c01546001600160401b0316919050565b6000806000806000806060806060600060606000611c8f612b1c565b600e0160008e6001600160401b03166001600160401b0316815260200190815260200160002090508060000160089054906101000a900461ffff169b5080600001600a9054906101000a900460ff169a5080600001600b9054906101000a90046001600160a01b03169950806001015498508060020160009054906101000a900463ffffffff1697508060020160049054906101000a900460ff16965080600601805480602002602001604051908101604052809291908181526020018280548015611d7a57602002820191906000526020600020905b815481526020019060010190808311611d66575b505050600784015460088501805460408051602080840282018101909252828152969a5060ff90931698509093509150830182828015611e0557602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff1681526020019060040190602082600301049283019260010382029150808411611dc85790505b505050505091506000611e1a826003016131c6565b9050806001600160401b03811115611e3457611e346144fa565b604051908082528060200260200182016040528015611e5d578160200160208202803683370190505b509650806001600160401b03811115611e7857611e786144fa565b604051908082528060200260200182016040528015611ea1578160200160208202803683370190505b50955060005b81811015611f0a57611ebc60038401826131d1565b898381518110611ece57611ece615377565b60200260200101898481518110611ee757611ee7615377565b60209081029190910101919091526001600160a01b039091169052600101611ea7565b50505091939597999b90929496989a50565b611f25846130d0565b6000611f2f612b1c565b61ffff85166000908152600d820160205260408120805461ffff198116825592935061010090920460ff169190611f69600183018261431d565b6002820160009055600382016000611f819190614342565b611f8f600483016000614342565b600582016000611f9f8282614342565b611fad600183016000614360565b50505060ff821660009081526008840160205260409020611fd3915061ffff87166131ed565b156120335760ff811660009081526008830160205260409020611ffa9061ffff8716613205565b5060405161ffff861681527f6ee9dd6501e6ec9a0a3e60e773ac5fd9d48414213bc6a16588c88bda44f492899060200160405180910390a15b6120446006830161ffff87166131ed565b1561072d5760006120558585613186565b600081815260058501602052604090205490915061ffff87811691161461208f5760405163066a129160e31b815260040160405180910390fd5b60008181526005840160205260409020805461ffff191690556120b96006840161ffff8816613205565b506040805161ffff8816815260ff878116602083015286168183015290517f9c5c01904f703f1392e848da84f3f506975241d8d8d0f67550729c5f5080c94e9181900360600190a150505050505050565b6000612114612b1c565b6001600160a01b039092166000908152600a9290920160205250604090205490565b60008684108061214557508282105b1561215257506000610c34565b600061215c61124d565b6001600160a01b038a166000908152600a9190910160205260409020549050801580159061218957508086105b15612198576000915050610c34565b60638611156121bd57604051633505ce1b60e11b8152600481018790526024016108d5565b60138511156121e257604051637d34ca9360e01b8152600481018690526024016108d5565b60006121ef600587615522565b9050675c33b801024d7e4a6000606461222e61220c85606361529b565b6122179060016152ae565b61222990670de0b6b3a7640000615522565b613211565b612238908461529b565b6122429190615539565b9050670de0b6b3a7640000811061226f57604051630dd7f01960e01b8152600481018290526024016108d5565b6000670de0b6b3a7640000612284838d615522565b61228e9190615539565b9050898410156122c5576122a2848b61529b565b6122ad90600a6152ae565b6122b8906002615631565b6122c29082615539565b90505b9c9b505050505050505050505050565b600080846001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612316573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061233a9190615268565b6001600160a01b0316631ebd249385856040518363ffffffff1660e01b815260040161236792919061546e565b602060405180830381865afa158015612384573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123a89190615487565b90506123b2612b1c565b60010160006123cb6001600160a01b03871686856132f2565b815260208101919091526040016000205460ff1695945050505050565b60006123f2612b1c565b6009015460ff16919050565b61240661429a565b61240e612b1c565b61ffff83166000908152600d919091016020908152604091829020825160e081018452815460ff80821683526101009091041681840152600182018054855181860281018601875281815292959394938601938301828280156124bc57602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff168152602001906004019060208260030104928301926001038202915080841161247f5790505b50505050508152602001600282015481526020016003820180548060200260200160405190810160405280929190818152602001828054801561251e57602002820191906000526020600020905b81548152602001906001019080831161250a575b505050505081526020016004820180548060200260200160405190810160405280929190818152602001828054801561257657602002820191906000526020600020905b815481526020019060010190808311612562575b5050505050815260200160058201604051806040016040529081600082018054806020026020016040519081016040528092919081815260200182805480156125de57602002820191906000526020600020905b8154815260200190600101908083116125ca575b5050505050815260200160018201805480602002602001604051908101604052809291908181526020016000905b828210156126a45760008481526020908190208301805460408051828502810185019091528181529283018282801561269057602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff16815260200190600401906020826003010492830192600103820291508084116126535790505b50505050508152602001906001019061260c565b5050509152505090525092915050565b60606106216126c1612b1c565b600b0160006126d96001600160a01b03871686612b26565b815260200190815260200160002054613327565b6126f6876130d0565b6000612700612b1c565b602086015151865151919250908114158061272057508560400151518114155b1561273e5760405163c81acf3360e01b815260040160405180910390fd5b60005b818110156127ab57865180518290811061275d5761275d615377565b6020026020010151518760200151828151811061277c5761277c615377565b602002602001015151146127a35760405163f001029b60e01b815260040160405180910390fd5b600101612741565b5061ffff88166000908152600d830160205260409020600983015460ff90811690891611156127e65760098301805460ff191660ff8a161790555b805460ff8981166101000261ffff19909216908416171781556040870151805161281a91600184019160209091019061437e565b5061282d87606001518860800151613186565b600282015560a0870151805161284d91600384019160209091019061442d565b506004810160005b8860c00151518110156128e157816128c68a60c00151838151811061287c5761287c615377565b60200260200101518b60e00151848151811061289a5761289a615377565b60200260200101518c610100015185815181106128b9576128b9615377565b602002602001015161339f565b81546001818101845560009384526020909320015501612855565b5060005b8381101561298257885180516005850191612917918490811061290a5761290a615377565b60200260200101516133da565b81546001810183556000928352602092839020015589015180516006850191908390811061294757612947615377565b60209081029190910181015182546001810184556000938452928290208151612979949190910192919091019061437e565b506001016128e5565b508415612a845760006129958888613186565b600081815260058701602052604090205490915061ffff16156129d1576040516366e6550f60e01b815261ffff8c1660048201526024016108d5565b60008181526005860160205260409020805461ffff191661ffff8d16908117909155612a019060068701906131ed565b15612a255760405163ce9c78df60e01b815261ffff8c1660048201526024016108d5565b612a366006860161ffff8d16613459565b506040805161ffff8d16815260ff8a8116602083015289168183015290517fc27376e37bec2cd9acd6957a46ca5cba20a88a21fd0eff1b02cf8536462304769181900360600190a150612aae565b8154610100900460ff1660009081526008850160205260409020612aac9061ffff8c16613459565b505b7fa8932b9bd78638467b3e5c0f0ffa9c72761db9f7f605b8df2f7a899770d064ae8a89604051612adf9291906156f7565b60405180910390a15050505050505050505050565b612afd84612cb0565b6000612b08846118ac565b9050612b15818484612e61565b5050505050565b600061081661124d565b60006001600160401b03821115612b5357604051633995b34160e01b8152600481018390526024016108d5565b5067ffffffffffffffff60a01b60a09190911b166001600160a01b039091161790565b612b7e613465565b612b9b57604051631afcd79f60e31b815260040160405180910390fd5b565b6001600160a01b038116612be55760405162461bcd60e51b815260206004820152600f60248201526e2d32b9379031b7b73a3937b63632b960891b60448201526064016108d5565b612c148161092660017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c361861529b565b612c434261092660017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b61529b565b612c724361092660017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f161529b565b7f1a2dd071001ebf6e03174e3df5b305795a4ad5d41d8fdb9ba41dbbe236713426814243604051612ca5939291906153a6565b60405180910390a150565b8061087e57604051636221cab960e01b815260040160405180910390fd5b82546001840154600160581b82046001600160a01b031691600160501b900460ff1615612d0e57604051636927c40b60e11b815260040160405180910390fd5b612d1a8282868661347f565b612d238361319b565b6001600160a01b031663f16a306683836040518363ffffffff1660e01b8152600401612d5092919061546e565b602060405180830381865afa158015612d6d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d9191906157f6565b612db257818160405163033aace360e41b81526004016108d592919061546e565b84546001600160401b0316612dc78383611c14565b6001600160401b031614612dee576040516337bcc65160e01b815260040160405180910390fd5b935093915050565b8254600160581b600160f81b03191683556000600184015560028301805464ffffffffff19169055612e28828261363c565b6040516001600160401b03821681527f883aa0807cbcd64d238a9795b0433f37884de37e569aff04d512cb1df62c6e019060200161187e565b6000612e6b612b1c565b6001600160401b0383166000908152600e8201602090815260408083208054600160401b900461ffff168452600d85018352818420825160a081018452858152938401859052918301849052606083018490526080830193909352929350909190612ed5876136c8565b6001600160a01b03166040820152600283015463ffffffff1615612f0c5760405163282404cd60e01b815260040160405180910390fd5b612f17838789612cce565b602083018190526001600160a01b03909116808352600285015460ff600160201b90910416606084018190526040840151612f6093869388939291612f5b8e61319b565b6136e8565b63ffffffff1660808201819052600003612f8d57604051638a874ec160e01b815260040160405180910390fd5b608081015160028401805463ffffffff191663ffffffff909216918217905560408083015183516020850151925163f2c6c6fb60e01b81526001600160a01b03918216600482015260248101939093526044830193909352600092169063f2c6c6fb906064016020604051808303816000875af1158015613012573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613036919061538d565b90507f7a40f4421a71ae931ce048b2171197c34a78510813f1d3f4202935dd9f105b1a868360000151846020015185608001518587606001516040516130be969594939291906001600160401b039690961686526001600160a01b03949094166020860152604085019290925263ffffffff166060840152608083015260a082015260c00190565b60405180910390a15050505050505050565b604051631430d62960e21b81526001600160a01b038216906350c358a4906130fc90339060040161494b565b602060405180830381865afa158015613119573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061313d91906157f6565b61087e573360405163451cea1760e11b81526004016108d5919061494b565b6000610624825490565b600061062482600884600001516001600160a01b031663016dff5d613cba565b600060ff8316600883901b61ff001617610621565b600061062482600084600001516001600160a01b0316628e9691613cba565b60006106218383613dc6565b600061062482613df0565b60008080806131e08686613dfb565b9097909650945050505050565b60008181526001830160205260408120541515610621565b60006106218383613e26565b6000670de0b6b3a764000082101561323f57604051637046c4a960e01b8152600481018390526024016108d5565b600061325b613256670de0b6b3a764000085615539565b613f19565b9050600061327182670de0b6b3a7640000615522565b905083821c670de0b6b3a764000081900361328e57509392505050565b6706f05b59d3b200005b80156132e857670de0b6b3a76400006132b18380615522565b6132bb9190615539565b9150671bc16d674ec8000082106132e0576132d681846152ae565b9250600182901c91505b60011c613298565b5090949350505050565b60609290921b6001600160601b03191660209190911b6bffffffffffffffff00000000161760189190911b63ff000000161790565b6040805160208082526104208201909252606091600091908082016104008036833701905050905060005b602081101561339857613366816008615522565b8460001c901c82828151811061337e5761337e615377565b60ff90921660209283029190910190910152600101613352565b5092915050565b67ffffffffffffffff60401b604083901b166001600160401b038416176080826133ca5760006133cd565b60015b60ff16901b179392505050565b8051600090602081111561340b57604051633d71388b60e21b815260048101829052602060248201526044016108d5565b6000805b8281101561345157613422816008615522565b85828151811061343457613434615377565b602090810291909101015160ff16901b919091179060010161340f565b509392505050565b60006106218383613ffc565b600061346f611144565b54600160401b900460ff16919050565b6040516331a9108f60e11b8152600481018490526001600160a01b038084169190861690636352211e90602401602060405180830381865afa1580156134c9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134ed9190615268565b6001600160a01b03161461351857838360405163547208b960e11b81526004016108d592919061546e565b61352181613166565b6001600160a01b0316631789b7b1856040518263ffffffff1660e01b815260040161354c919061494b565b602060405180830381865afa158015613569573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061358d9190615487565b60ff166000036135b2578360405163adc7fced60e01b81526004016108d5919061494b565b80600001516001600160a01b03166318d928316040518163ffffffff1660e01b8152600401602060405180830381865afa1580156135f4573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061361891906157f6565b1561363657604051635e1633d360e11b815260040160405180910390fd5b50505050565b61366a816001600160401b0316613651612b1c565b60ff851660009081526020919091526040902090613459565b613687576040516313e5d5f160e11b815260040160405180910390fd5b6040805160ff841681526001600160401b03831660208201527f3e9a7af2aeebce9d24ef707bdb61adf2708293e374a19f8e9c31f905c68b43c19101610992565b600061062482600584600001516001600160a01b03166389dd9f13613cba565b600786015460009060ff168610613715576040516315f7ec4560e21b8152600481018790526024016108d5565b86600801868154811061372a5761372a615377565b60009182526020822060088204015460079091166004026101000a900463ffffffff169150819003610c3457604051631a95890960e31b81526000906001600160a01b0384169063d4ac484890613787908890889060040161546e565b60a060405180830381865afa1580156137a4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137c89190615813565b602081015181518b549293506137f29263ffffffff9283169290911690610100900460ff1661404b565b158015906138435750613803612b1c565b89546001919091019060009061382d906001600160a01b038916908890610100900460ff166132f2565b815260208101919091526040016000205460ff16155b80156138705750885461385f90610100900460ff1660056158aa565b60ff16816000015163ffffffff1610155b15613ac0578854610100900460ff166001036138925762138884915050610c34565b8854610100900460ff166002036138af576222f238915050610c34565b8854610100900460ff166003036138cc57623234dc915050610c34565b8854610100900460ff166004036138e95762417780915050610c34565b8854610100900460ff16600503613ac057604051631c2aafe760e01b81526000906001600160a01b03851690631c2aafe790613937908990899066222aa723afa12360c91b906004016153a6565b602060405180830381865afa158015613954573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613978919061538d565b90508060010361398f57625219a192505050610c34565b604051631c2aafe760e01b81526000906001600160a01b03861690631c2aafe7906139cc908a908a9066222aa723afa2a760c91b906004016153a6565b602060405180830381865afa1580156139e9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613a0d919061538d565b905080600103613a2557625267d49350505050610c34565b604051631c2aafe760e01b81526000906001600160a01b03871690631c2aafe790613a62908b908b906644554e475f415360c81b906004016153a6565b602060405180830381865afa158015613a7f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613aa3919061538d565b905080600103613abc576251cb8a945050505050610c34565b5050505b6040805160058b01805460606020820284018101855293830181815260009484928491840182828015613b1257602002820191906000526020600020905b815481526020019060010190808311613afe575b5050505050815260200160018201805480602002602001604051908101604052809291908181526020016000905b82821015613bd857600084815260209081902083018054604080518285028101850190915281815292830182828015613bc457602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff1681526020019060040190602082600301049283019260010382029150808411613b875790505b505050505081526020019060010190613b40565b50505050815250509050866001600160a01b0316636f383c0f613c1783600001518b81518110613c0a57613c0a615377565b60200260200101516140a1565b83602001518b81518110613c2d57613c2d615377565b60200260200101518d60000160019054906101000a900460ff168a8a6040518663ffffffff1660e01b8152600401613c699594939291906158c6565b6020604051808303816000875af1158015613c88573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613cac9190615915565b9a9950505050505050505050565b600080856020015185600e811115613cd457613cd46152c1565b600f8110613ce457613ce4615377565b602002015190506001600160a01b03811615613d01579050610633565b83836040518163ffffffff1660e01b8152600401602060405180830381865afa158015613d32573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613d569190615268565b866020015186600e811115613d6d57613d6d6152c1565b600f8110613d7d57613d7d615377565b6001600160a01b039092166020928302919091015286015185600e811115613da757613da76152c1565b600f8110613db757613db7615377565b60200201519695505050505050565b6000826000018281548110613ddd57613ddd615377565b9060005260206000200154905092915050565b60006106248261315c565b60008080613e0985856131ba565b600081815260029690960160205260409095205494959350505050565b60008181526001830160205260408120548015613f0f576000613e4a60018361529b565b8554909150600090613e5e9060019061529b565b9050808214613ec3576000866000018281548110613e7e57613e7e615377565b9060005260206000200154905080876000018481548110613ea157613ea1615377565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080613ed457613ed4615932565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610624565b6000915050610624565b6000600160801b8210613f3957608091821c91613f3690826152ae565b90505b600160401b8210613f5757604091821c91613f5490826152ae565b90505b600160201b8210613f7557602091821c91613f7290826152ae565b90505b620100008210613f9257601091821c91613f8f90826152ae565b90505b6101008210613fae57600891821c91613fab90826152ae565b90505b60108210613fc957600491821c91613fc690826152ae565b90505b60048210613fe457600291821c91613fe190826152ae565b90505b60028210613ff7576106246001826152ae565b919050565b600081815260018301602052604081205461404357508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610624565b506000610624565b600080600561405c86866001614195565b6140669190615539565b6140719060016152ae565b90508281116140815760006107d9565b61408b838261529b565b61409690600a6152ae565b6107d9906002615631565b606060006140ae83613327565b90506000805b82518110156140f7578281815181106140cf576140cf615377565b602002602001015160ff16600003156140f757816140ec81615509565b9250506001016140b4565b50806001600160401b03811115614110576141106144fa565b604051908082528060200260200182016040528015614139578160200160208202803683370190505b50925060005b8181101561418d5782818151811061415957614159615377565b602002602001015184828151811061417357614173615377565b60ff9092166020928302919091019091015260010161413f565b505050919050565b6000806141a285856141cb565b90508280156141bb575060026141b98260016152ae565b115b15610633576107d960028261529b565b805b6063811015610624576141e18360016152ae565b6141ea826141fe565b63ffffffff161015610624576001016141cd565b600063ffffffff8216158061421a5750606363ffffffff831610155b1561422757506000919050565b670de0b6b3a764000061426161423e846063615948565b614249906002615965565b6122299063ffffffff16670de0b6b3a7640000615522565b61427390675cfb2e807b1e000061529b565b614286620186a063ffffffff8616615522565b6142909190615522565b6106249190615539565b6040518060e00160405280600060ff168152602001600060ff168152602001606081526020016000801916815260200160608152602001606081526020016142f5604051806040016040528060608152602001606081525090565b905290565b604051806040016040528060006001600160a01b031681526020016142f5614468565b50805460008255600701600890049060005260206000209081019061087e9190614487565b508054600082559060005260206000209081019061087e9190614487565b508054600082559060005260206000209081019061087e919061449c565b8280548282559060005260206000209060070160089004810192821561441d5791602002820160005b838211156143eb57835183826101000a81548163ffffffff021916908363ffffffff16021790555092602001926004016020816003010492830192600103026143a7565b801561441b5782816101000a81549063ffffffff02191690556004016020816003010492830192600103026143eb565b505b50614429929150614487565b5090565b82805482825590600052602060002090810192821561441d579160200282015b8281111561441d57825182559160200191906001019061444d565b604051806101e00160405280600f906020820280368337509192915050565b5b808211156144295760008155600101614488565b808211156144295760006144b0828261431d565b5060010161449c565b6001600160a01b038116811461087e57600080fd5b600080604083850312156144e157600080fd5b82356144ec816144b9565b946020939093013593505050565b634e487b7160e01b600052604160045260246000fd5b60405161012081016001600160401b0381118282101715614533576145336144fa565b60405290565b604051601f8201601f191681016001600160401b0381118282101715614561576145616144fa565b604052919050565b600082601f83011261457a57600080fd5b81356001600160401b03811115614593576145936144fa565b6145a6601f8201601f1916602001614539565b8181528460208386010111156145bb57600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080608085870312156145ee57600080fd5b84356145f9816144b9565b93506020850135614609816144b9565b92506040850135915060608501356001600160401b0381111561462b57600080fd5b61463787828801614569565b91505092959194509250565b60006020828403121561465557600080fd5b8135614660816144b9565b9392505050565b6001600160401b038116811461087e57600080fd5b801515811461087e57600080fd5b8035613ff78161467c565b600080604083850312156146a857600080fd5b82356146b381614667565b915060208301356146c38161467c565b809150509250929050565b803561ffff81168114613ff757600080fd5b60ff8116811461087e57600080fd5b8035613ff7816146e0565b6000806000806080858703121561471057600080fd5b614719856146ce565b93506020850135614729816146e0565b92506040850135614739816144b9565b9396929550929360600135925050565b6000806040838503121561475c57600080fd5b823561476781614667565b915060208301356001600160401b0381111561478257600080fd5b61478e85828601614569565b9150509250929050565b6000602082840312156147aa57600080fd5b813561466081614667565b63ffffffff8116811461087e57600080fd5b600080600080608085870312156147dd57600080fd5b84356147e8816147b5565b935060208501356147f8816144b9565b925060408501359150606085013561480f816146e0565b939692955090935050565b60008060006060848603121561482f57600080fd5b833561483a816144b9565b9250602084013591506040840135614851816144b9565b809150509250925092565b60006020828403121561486e57600080fd5b5035919050565b600080600080600060a0868803121561488d57600080fd5b8535614898816144b9565b945060208601356148a8816146e0565b935060408601356148b8816144b9565b94979396509394606081013594506080013592915050565b600080604083850312156148e357600080fd5b50508035926020909101359150565b6000815180845260005b81811015614918576020818501810151868301820152016148fc565b506000602082860101526020601f19601f83011685010191505092915050565b60208152600061062160208301846148f2565b6001600160a01b0391909116815260200190565b60008151808452602080850194506020840160005b838110156149995781516001600160a01b031687529582019590820190600101614974565b509495945050505050565b60008151808452602080850194506020840160005b83811015614999578151875295820195908201906001016149b9565b60008151808452602080850194506020840160005b8381101561499957815163ffffffff16875295820195908201906001016149ea565b61ffff8c1681528a151560208201526001600160a01b038a1660408201526060810189905263ffffffff88166080820152600061016060ff891660a08401528060c0840152614a5d8184018961495f565b905082810360e0840152614a7181886149a4565b9050828103610100840152614a8681876149a4565b60ff86166101208501529050828103610140840152614aa581856149d5565b9e9d5050505050505050505050505050565b600080600060608486031215614acc57600080fd5b614ad5846146ce565b92506020840135614ae5816146e0565b91506040840135614851816146e0565b600080600080600080600060e0888a031215614b1057600080fd5b8735614b1b816144b9565b965060208801359550604088013594506060880135614b39816146e0565b93506080880135614b49816146e0565b925060a0880135614b59816146e0565b915060c0880135614b69816146e0565b8091505092959891949750929550565b600080600060608486031215614b8e57600080fd5b8335614b9981614667565b92506020840135614ba9816144b9565b929592945050506040919091013590565b60008060008060808587031215614bd057600080fd5b8435614bdb816144b9565b966020860135965060408601359560600135945092505050565b600060208284031215614c0757600080fd5b610621826146ce565b60008282518085526020808601955060208260051b8401016020860160005b84811015614c5d57601f19868403018952614c4b8383516149d5565b98840198925090830190600101614c2f565b5090979650505050505050565b6000815160408452614c7f60408501826149a4565b9050602083015184820360208601526107d98282614c10565b6020815260ff825116602082015260006020830151614cbc604084018260ff169052565b50604083015160e06060840152614cd76101008401826149d5565b9050606084015160808401526080840151601f19808584030160a0860152614cff83836149a4565b925060a08601519150808584030160c0860152614d1c83836149a4565b925060c08601519150808584030160e0860152506107d98282614c6a565b60008151808452602080850194506020840160005b8381101561499957815160ff1687529582019590820190600101614d4f565b6020815260006106216020830184614d3a565b60006001600160401b03821115614d9a57614d9a6144fa565b5060051b60200190565b600082601f830112614db557600080fd5b81356020614dca614dc583614d81565b614539565b828152600592831b8501820192828201919087851115614de957600080fd5b8387015b85811015614c5d5780356001600160401b03811115614e0c5760008081fd5b8801603f81018a13614e1e5760008081fd5b858101356040614e30614dc583614d81565b82815291851b8301810191888101908d841115614e4d5760008081fd5b938201935b83851015614e775784359250614e67836146e0565b8282529389019390890190614e52565b885250505093850193508401614ded565b600082601f830112614e9957600080fd5b81356020614ea9614dc583614d81565b8083825260208201915060208460051b870101935086841115614ecb57600080fd5b602086015b84811015614ef0578035614ee3816147b5565b8352918301918301614ed0565b509695505050505050565b600082601f830112614f0c57600080fd5b81356020614f1c614dc583614d81565b82815260059290921b84018101918181019086841115614f3b57600080fd5b8286015b84811015614ef05780356001600160401b03811115614f5e5760008081fd5b614f6c8986838b0101614e88565b845250918301918301614f3f565b600082601f830112614f8b57600080fd5b81356020614f9b614dc583614d81565b8083825260208201915060208460051b870101935086841115614fbd57600080fd5b602086015b84811015614ef05780358352918301918301614fc2565b600082601f830112614fea57600080fd5b81356020614ffa614dc583614d81565b8083825260208201915060208460051b87010193508684111561501c57600080fd5b602086015b84811015614ef057803561503481614667565b8352918301918301615021565b600082601f83011261505257600080fd5b81356020615062614dc583614d81565b8083825260208201915060208460051b87010193508684111561508457600080fd5b602086015b84811015614ef057803561509c8161467c565b8352918301918301615089565b60008060008060008060c087890312156150c257600080fd5b6150cb876146ce565b955060208701356150db816146e0565b945060408701356001600160401b03808211156150f757600080fd5b90880190610120828b03121561510c57600080fd5b615114614510565b82358281111561512357600080fd5b61512f8c828601614da4565b82525060208301358281111561514457600080fd5b6151508c828601614efb565b60208301525060408301358281111561516857600080fd5b6151748c828601614e88565b604083015250615186606084016146ef565b6060820152615197608084016146ef565b608082015260a0830135828111156151ae57600080fd5b6151ba8c828601614f7a565b60a08301525060c0830135828111156151d257600080fd5b6151de8c828601614fd9565b60c08301525060e0830135828111156151f657600080fd5b6152028c828601614fd9565b60e083015250610100808401358381111561521c57600080fd5b6152288d828701615041565b828401525050809650505050615240606088016146ef565b925061524e608088016146ef565b915061525c60a0880161468a565b90509295509295509295565b60006020828403121561527a57600080fd5b8151614660816144b9565b634e487b7160e01b600052601160045260246000fd5b8181038181111561062457610624615285565b8082018082111561062457610624615285565b634e487b7160e01b600052602160045260246000fd5b6001600160a01b0386811682528581166020830152604082018590528316606082015260a081016003831061531c57634e487b7160e01b600052602160045260246000fd5b8260808301529695505050505050565b60006020828403121561533e57600080fd5b815161466081614667565b60008060006060848603121561535e57600080fd5b8351925060208401519150604084015190509250925092565b634e487b7160e01b600052603260045260246000fd5b60006020828403121561539f57600080fd5b5051919050565b6001600160a01b039390931683526020830191909152604082015260600190565b8781528660208201526001600160401b0386166040820152600060018060a01b03808716606084015260e0608084015261540460e08401876148f2565b941660a08301525063ffffffff9190911660c09091015295945050505050565b6000806000806080858703121561543a57600080fd5b84516154458161467c565b60208601516040870151919550935061545d816147b5565b606086015190925061480f8161467c565b6001600160a01b03929092168252602082015260400190565b60006020828403121561549957600080fd5b8151614660816146e0565b634e487b7160e01b600052601260045260246000fd5b600060ff8316806154cd576154cd6154a4565b8060ff84160491505092915050565b60ff818116838216019081111561062457610624615285565b600082615504576155046154a4565b500690565b60006001820161551b5761551b615285565b5060010190565b808202811582820484141761062457610624615285565b600082615548576155486154a4565b500490565b600181815b8085111561558857816000190482111561556e5761556e615285565b8085161561557b57918102915b93841c9390800290615552565b509250929050565b60008261559f57506001610624565b816155ac57506000610624565b81600181146155c257600281146155cc576155e8565b6001915050610624565b60ff8411156155dd576155dd615285565b50506001821b610624565b5060208310610133831016604e8410600b841016171561560b575081810a610624565b615615838361554d565b806000190482111561562957615629615285565b029392505050565b60006106218383615590565b60008282518085526020808601955060208260051b8401016020860160005b84811015614c5d57601f19868403018952615678838351614d3a565b9884019892509083019060010161565c565b60008151808452602080850194506020840160005b838110156149995781516001600160401b03168752958201959082019060010161569f565b60008151808452602080850194506020840160005b838110156149995781511515875295820195908201906001016156d9565b61ffff83168152604060208201526000825161012080604085015261572061016085018361563d565b91506020850151603f198086850301606087015261573e8483614c10565b9350604087015191508086850301608087015261575b84836149d5565b93506060870151915061577360a087018360ff169052565b608087015160ff1660c087015260a0870151868503820160e0880152915061579b84836149a4565b935060c087015191506101008187860301818801526157ba858461568a565b945060e088015192508187860301848801526157d6858461568a565b9450808801519350508086850301610140870152505061152082826156c4565b60006020828403121561580857600080fd5b81516146608161467c565b600060a0828403121561582557600080fd5b60405160a081018181106001600160401b0382111715615847576158476144fa565b6040528251615855816147b5565b81526020830151615865816147b5565b60208201526040830151615878816147b5565b6040820152606083015161588b816147b5565b6060820152608083015161589e816147b5565b60808201529392505050565b60ff818116838216029081169081811461339857613398615285565b60a0815260006158d960a0830188614d3a565b82810360208401526158eb81886149d5565b60ff96909616604084015250506001600160a01b0392909216606083015260809091015292915050565b60006020828403121561592757600080fd5b8151614660816147b5565b634e487b7160e01b600052603160045260246000fd5b63ffffffff82811682821603908082111561339857613398615285565b63ffffffff8181168382160190808211156133985761339861528556fe22573091f17911fb166032a3d9e0554aa73d31b7b7ddea4a4dd2995650af84bda26469706673582212207e6bbc9423d671047c4b23fa353fd59b026680d1f19383d3556dfd50fae2abb564736f6c63430008170033
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106102295760003560e01c80639a641e0211610131578063b5a44d04116100b3578063b5a44d0414610509578063b6d2fe6c1461051c578063b701c1aa1461054a578063bd410d641461056a578063be2e0ad81461057d578063d06969c71461059d578063dee1f0e4146105b0578063e76bd825146105c3578063f77c4791146105d6578063fee27cbe146105de578063ffa1ad74146105f157600080fd5b80639a641e02146104355780639d0bcca0146104485780639f3cfa7a1461045d578063a433af6f14610465578063a46374b314610478578063a4efd309146104a2578063a7540ee5146104b5578063ac257b3a146104c8578063ad8c5755146104db578063b2192fdd146104ee578063b429afeb146104f657600080fd5b80634fac6ccd116101ba5780634fac6ccd14610326578063572b6c051461033957806357bd05b01461034c5780635a67eb481461035f5780636a8a5e8c146103725780636bb4b459146103855780637442adbc146103b05780637be89369146103c35780637c00116c146103e95780637cc96380146103fc578063936725ec1461040457600080fd5b80630e30c13e1461022e578063150b7a021461025857806319ab453c1461028457806327dd892f146102995780632fcd5deb146102ac578063325a19f1146102cf57806338430cce146102e55780634593144c146102f857806348b371bf146103005780634d52532c14610313575b600080fd5b61024161023c3660046144ce565b610615565b60405160ff90911681526020015b60405180910390f35b61026b6102663660046145d8565b61062a565b6040516001600160e01b0319909116815260200161024f565b610297610292366004614643565b61063b565b005b6102976102a7366004614695565b610735565b6102bf6102ba3660046146fa565b61075b565b604051901515815260200161024f565b6102d76107e2565b60405190815260200161024f565b6102976102f3366004614749565b61081b565b6102d761083d565b61029761030e366004614798565b61086d565b6102d7610321366004614798565b610881565b610297610334366004614643565b61088c565b6102bf610347366004614643565b61099e565b61029761035a3660046147c7565b610a13565b61029761036d3660046144ce565b610ab6565b61029761038036600461481a565b610ac8565b61039861039336600461481a565b610b44565b6040516001600160401b03909116815260200161024f565b6102d76103be36600461485c565b610c06565b6103d66103d1366004614875565b610c11565b60405161ffff909116815260200161024f565b6103986103f73660046148d0565b610c3f565b6102d7610c4b565b610428604051806040016040528060058152602001640312e302e360dc1b81525081565b60405161024f9190614938565b6103986104433660046144ce565b610c69565b610450610c75565b60405161024f919061494b565b610398610ca5565b6102976104733660046144ce565b610caf565b61048b610486366004614798565b610d34565b60405161024f9b9a99989796959493929190614a0c565b6102976104b0366004614ab7565b610d74565b6102d76104c3366004614643565b610d8c565b6102d76104d6366004614af5565b610d97565b6102bf6104e93660046144ce565b610dc0565b610241610dd4565b6102bf610504366004614643565b610dde565b610297610517366004614b79565b610e03565b61052f61052a366004614bba565b610e8d565b6040805193845260208401929092529082015260600161024f565b61055d610558366004614bf5565b610f49565b60405161024f9190614c98565b6102976105783660046144ce565b610f5a565b61059061058b3660046144ce565b610fa8565b60405161024f9190614d6e565b6103986105ab36600461481a565b610fb4565b6102bf6105be366004614643565b61101b565b6102976105d13660046150a9565b6110a0565b6104506110b6565b6102976105ec366004614798565b6110e6565b61042860405180604001604052806005815260200164322e312e3160d81b81525081565b60006106218383611107565b90505b92915050565b630a85bd0160e11b5b949350505050565b6000610645611144565b805490915060ff600160401b82041615906001600160401b031660008115801561066c5750825b90506000826001600160401b031660011480156106885750303b155b905081158015610696575080155b156106b45760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156106de57845460ff60401b1916600160401b1785555b6106e786611168565b831561072d57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b610757610740611179565b6107486110b6565b610750611191565b85856111b3565b5050565b60006107d961076861124d565b6107706110b6565b6001600160a01b0316628e96916040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107d09190615268565b87878787611271565b95945050505050565b600061081661081260017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b61529b565b5490565b905090565b610757610826611179565b61082e6110b6565b610836611191565b858561152a565b600061081661081260017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f161529b565b61087e6108786110b6565b82611768565b50565b6000610624826117c0565b3330146108de5760405162461bcd60e51b815260206004820152601b60248201527a24b731b932b0b9b2903932bb34b9b4b7b7103337b93134b23232b760291b60448201526064015b60405180910390fd5b60006108fc610812600160008051602061598383398151915261529b565b6109079060016152ae565b905061092981610926600160008051602061598383398151915261529b565b55565b6109588261092660017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e461529b565b604080518281526001600160a01b03841660208201527ff27e2ef832a4eb8ed8ec553b875eecd44764cda95b1c24170e281539e0a869c891015b60405180910390a15050565b60006001600160a01b03821673d8253782c45a12053594b9deb72d8e8ab2fca54c14806109e757506001600160a01b0382167352ceba41da235af367bfc0b0ccd3314cb901bb5f145b8061062457506001600160a01b03821673102f1f556cd9c3d5f820e6920a8931657c5da21b1492915050565b732aa17bf0e417bcede3dc9778d65d27a4cdef34a3637c1ecb8e610a356110b6565b6040516001600160e01b031960e084901b1681526001600160a01b03918216600482015263ffffffff8816602482015290861660448201526064810185905260ff8416608482015260a40160006040518083038186803b158015610a9857600080fd5b505af4158015610aac573d6000803e3d6000fd5b5050505050505050565b610757610ac16110b6565b83836117ec565b732aa17bf0e417bcede3dc9778d65d27a4cdef34a3632ca22aeb610aea6110b6565b85858560016040518663ffffffff1660e01b8152600401610b0f9594939291906152d7565b60006040518083038186803b158015610b2757600080fd5b505af4158015610b3b573d6000803e3d6000fd5b50505050505050565b6000732aa17bf0e417bcede3dc9778d65d27a4cdef34a363987707f9610b68611179565b610b706110b6565b610b78611191565b6040516001600160e01b031960e086901b16815292151560048401526001600160a01b03918216602484015281166044830152808816606483015260848201879052851660a482015260c4015b602060405180830381865af4158015610be2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610633919061532c565b60006106248261188b565b600080610c1d876118ac565b9050610c34610c2a61124d565b82888888886118c4565b979650505050505050565b60006106218383611bf1565b6000610816610812600160008051602061598383398151915261529b565b60006106218383611c14565b600061081661081260017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e461529b565b6000610816611c57565b732aa17bf0e417bcede3dc9778d65d27a4cdef34a363dc01b901610cd16110b6565b6040516001600160e01b031960e084901b1681526001600160a01b0391821660048201529085166024820152604481018490526064015b60006040518083038186803b158015610d2057600080fd5b505af415801561072d573d6000803e3d6000fd5b600080600080600080606080606060006060610d4f8c611c73565b9a509a509a509a509a509a509a509a509a509a509a5091939597999b90929496989a50565b610d87610d7f6110b6565b848484611f1c565b505050565b60006106248261210a565b6000610db48888888860ff168860ff168860ff168860ff16612136565b98975050505050505050565b6000610621610dcd6110b6565b84846122d5565b60006108166123e8565b6000610de86110b6565b6001600160a01b0316826001600160a01b0316149050919050565b732aa17bf0e417bcede3dc9778d65d27a4cdef34a363efa20d9b610e25611179565b610e2d6110b6565b610e35611191565b6040516001600160e01b031960e086901b16815292151560048401526001600160a01b039182166024840152811660448301526001600160401b03871660648301528516608482015260a4810184905260c401610b0f565b6000806000732aa17bf0e417bcede3dc9778d65d27a4cdef34a3633e457e4f610eb46110b6565b6040516001600160e01b031960e084901b1681526001600160a01b039182166004820152908a16602482015260448101899052606481018890526084810187905260a401606060405180830381865af4158015610f15573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f399190615349565b9250925092509450945094915050565b610f5161429a565b610624826123fe565b732aa17bf0e417bcede3dc9778d65d27a4cdef34a3632ca22aeb610f7c6110b6565b8484610f86611191565b60026040518663ffffffff1660e01b8152600401610d089594939291906152d7565b606061062183836126b4565b6000732aa17bf0e417bcede3dc9778d65d27a4cdef34a363c77d56a2610fd86110b6565b6040516001600160e01b031960e084901b1681526001600160a01b0391821660048201528186166024820152908716604482015260648101869052608401610bc5565b6000816001600160a01b031661102f6110b6565b6001600160a01b0316635aa6e6756040518163ffffffff1660e01b8152600401602060405180830381865afa15801561106c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110909190615268565b6001600160a01b03161492915050565b61072d6110ab6110b6565b8787878787876126ed565b600061081661081260017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c361861529b565b61087e6110f1611179565b6110f96110b6565b611101611191565b84612af4565b6000611111612b1c565b60030160006111296001600160a01b03861685612b26565b815260208101919091526040016000205460ff169392505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b611170612b76565b61087e81612b9d565b60006111843361099e565b8061081657505032331490565b600061119c3361099e565b156111ae575060131936013560601c90565b503390565b6111bc85612cb0565b60405163139ed9b560e21b81526001600160a01b0380861660048301526001600160401b038416602483015282151560448301528416606482015273d9b4f707646f1a162e56b84211bca451de9f5ab590634e7b66d49060840160006040518083038186803b15801561122e57600080fd5b505af4158015611242573d6000803e3d6000fd5b505050505050505050565b7fae5971282b317bbed599861775fe0712755bb3b2f655bfe8fb14280d8429f60090565b61ffff84166000908152600d870160205260408120600281015460ff8082169160081c81169087168211806112a85750808760ff16115b156112b95760009350505050611520565b505060008160030180548060200260200160405190810160405280929190818152602001828054801561130b57602002820191906000526020600020905b8154815260200190600101908083116112f7575b5050505050905060008260040180548060200260200160405190810160405280929190818152602001828054801561136257602002820191906000526020600020905b81548152602001906001019080831161134e575b505085519394506000925050505b8181101561151657600084828151811061138c5761138c615377565b602002602001015190506000801b81036113a6575061150e565b60008060006113dd8786815181106113c0576113c0615377565b602002602001015190604082901c90608083901c60ff1660011490565b92509250925060008161145e578f6001600160a01b0316630a82c861866040518263ffffffff1660e01b815260040161141891815260200190565b602060405180830381865afa158015611435573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611459919061538d565b6114cf565b8f6001600160a01b0316631c2aafe78e8e886040518463ffffffff1660e01b815260040161148e939291906153a6565b602060405180830381865afa1580156114ab573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114cf919061538d565b9050836001600160401b03168110806114f05750826001600160401b031681115b156115085760009a5050505050505050505050611520565b50505050505b600101611370565b5060019450505050505b9695505050505050565b6000611534612b1c565b90506000611541866118ac565b6040805160a0810182526000808252602080830182905282840182905260608301829052608083018290526001600160401b0389168252600e870181528382208054600160401b900461ffff168352600d8801909152929020929350916115a78a612cb0565b6115b2828986612cce565b50508160020160009054906101000a900463ffffffff16836040019063ffffffff16908163ffffffff168152505073d9b4f707646f1a162e56b84211bca451de9f5ab56311942d1783838a8c8b8f8a604001516040518863ffffffff1660e01b815260040161162797969594939291906153c7565b608060405180830381865af4158015611644573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116689190615424565b1515602087015263ffffffff1660608601526080850152158015845261169a57815460ff60501b1916600160501b1782555b6080830151156116cb57608083015160028301805460ff909216600160201b0264ff00000000199092169190911790555b826040015163ffffffff16836060015163ffffffff161461170757606083015160028301805463ffffffff191663ffffffff9092169190911790555b826020015115611727578054611727908390610100900460ff1689612df6565b825115801561173e5750600282015463ffffffff16155b801561174c57508260200151155b1561175c5761175c848989612e61565b50505050505050505050565b611771826130d0565b604051632586866b60e11b81526001600160a01b03831660048201526001600160401b038216602482015273d9b4f707646f1a162e56b84211bca451de9f5ab590634b0d0cd690604401610d08565b60006117ca612b1c565b6001600160401b039092166000908152600f9290920160205250604090205490565b6117f5836130d0565b600581101561181a57604051634bebee7f60e01b8152600481018290526024016108d5565b80611823612b1c565b600a016000846001600160a01b03166001600160a01b03168152602001908152602001600020819055507f996f9d801b1a05cd3c7b555e5b6f61db994ea8ef57125b5b5d3b07b9dad4e2cf828260405161187e92919061546e565b60405180910390a1505050565b6000610624611898612b1c565b60008481526020919091526040902061315c565b6118b46142fa565b6001600160a01b03909116815290565b60008460ff166000036118ea5760405163a8473eab60e01b815260040160405180910390fd5b6000806118f688613166565b604051631ebd249360e01b81529091506001600160a01b03821690631ebd249390611927908990899060040161546e565b602060405180830381865afa158015611944573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119689190615487565b915060008960050160006119ff60058b61198291906154ba565b61198d9060016154dc565b604051631789b7b160e01b81526001600160a01b03871690631789b7b1906119b9908e9060040161494b565b602060405180830381865afa1580156119d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119fa9190615487565b613186565b8152602081019190915260400160009081205461ffff169150819003611a5f57896005016000611a4760058b611a3591906154ba565b611a409060016154dc565b6000613186565b815260208101919091526040016000205461ffff1690505b8061ffff16600003611a9457896005016000611a7c600080613186565b815260208101919091526040016000205461ffff1690505b61ffff811615611b1a576001600160a01b03871660a087901b67ffffffffffffffff60a01b161760f082901b6001600160f01b03191617600090815260028b01602052604090205460ff16158015611b0b575061ffff81166000908152600d8b01602052604090205460ff84811661010090920416145b15611b1a579250611520915050565b505060ff81166000908152600889016020526040812090611b3a8261315c565b905080600003611b61576040516283533360e01b815260ff841660048201526024016108d5565b6000611b6c8a61319b565b9050600080611b7b84896154f5565b905060005b84811015611bd757611b9286836131ba565b9250611ba28e85858f8f8f611271565b15611bb65782975050505050505050611520565b81611bc081615509565b925050848210611bcf57600091505b600101611b80565b5060405163b940612960e01b815260040160405180910390fd5b600061062183611bff612b1c565b600085815260209190915260409020906131ba565b6000611c1e612b1c565b6004016000611c366001600160a01b03861685612b26565b81526020810191909152604001600020546001600160401b03169392505050565b6000611c61612b1c565b600c01546001600160401b0316919050565b6000806000806000806060806060600060606000611c8f612b1c565b600e0160008e6001600160401b03166001600160401b0316815260200190815260200160002090508060000160089054906101000a900461ffff169b5080600001600a9054906101000a900460ff169a5080600001600b9054906101000a90046001600160a01b03169950806001015498508060020160009054906101000a900463ffffffff1697508060020160049054906101000a900460ff16965080600601805480602002602001604051908101604052809291908181526020018280548015611d7a57602002820191906000526020600020905b815481526020019060010190808311611d66575b505050600784015460088501805460408051602080840282018101909252828152969a5060ff90931698509093509150830182828015611e0557602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff1681526020019060040190602082600301049283019260010382029150808411611dc85790505b505050505091506000611e1a826003016131c6565b9050806001600160401b03811115611e3457611e346144fa565b604051908082528060200260200182016040528015611e5d578160200160208202803683370190505b509650806001600160401b03811115611e7857611e786144fa565b604051908082528060200260200182016040528015611ea1578160200160208202803683370190505b50955060005b81811015611f0a57611ebc60038401826131d1565b898381518110611ece57611ece615377565b60200260200101898481518110611ee757611ee7615377565b60209081029190910101919091526001600160a01b039091169052600101611ea7565b50505091939597999b90929496989a50565b611f25846130d0565b6000611f2f612b1c565b61ffff85166000908152600d820160205260408120805461ffff198116825592935061010090920460ff169190611f69600183018261431d565b6002820160009055600382016000611f819190614342565b611f8f600483016000614342565b600582016000611f9f8282614342565b611fad600183016000614360565b50505060ff821660009081526008840160205260409020611fd3915061ffff87166131ed565b156120335760ff811660009081526008830160205260409020611ffa9061ffff8716613205565b5060405161ffff861681527f6ee9dd6501e6ec9a0a3e60e773ac5fd9d48414213bc6a16588c88bda44f492899060200160405180910390a15b6120446006830161ffff87166131ed565b1561072d5760006120558585613186565b600081815260058501602052604090205490915061ffff87811691161461208f5760405163066a129160e31b815260040160405180910390fd5b60008181526005840160205260409020805461ffff191690556120b96006840161ffff8816613205565b506040805161ffff8816815260ff878116602083015286168183015290517f9c5c01904f703f1392e848da84f3f506975241d8d8d0f67550729c5f5080c94e9181900360600190a150505050505050565b6000612114612b1c565b6001600160a01b039092166000908152600a9290920160205250604090205490565b60008684108061214557508282105b1561215257506000610c34565b600061215c61124d565b6001600160a01b038a166000908152600a9190910160205260409020549050801580159061218957508086105b15612198576000915050610c34565b60638611156121bd57604051633505ce1b60e11b8152600481018790526024016108d5565b60138511156121e257604051637d34ca9360e01b8152600481018690526024016108d5565b60006121ef600587615522565b9050675c33b801024d7e4a6000606461222e61220c85606361529b565b6122179060016152ae565b61222990670de0b6b3a7640000615522565b613211565b612238908461529b565b6122429190615539565b9050670de0b6b3a7640000811061226f57604051630dd7f01960e01b8152600481018290526024016108d5565b6000670de0b6b3a7640000612284838d615522565b61228e9190615539565b9050898410156122c5576122a2848b61529b565b6122ad90600a6152ae565b6122b8906002615631565b6122c29082615539565b90505b9c9b505050505050505050505050565b600080846001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612316573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061233a9190615268565b6001600160a01b0316631ebd249385856040518363ffffffff1660e01b815260040161236792919061546e565b602060405180830381865afa158015612384573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123a89190615487565b90506123b2612b1c565b60010160006123cb6001600160a01b03871686856132f2565b815260208101919091526040016000205460ff1695945050505050565b60006123f2612b1c565b6009015460ff16919050565b61240661429a565b61240e612b1c565b61ffff83166000908152600d919091016020908152604091829020825160e081018452815460ff80821683526101009091041681840152600182018054855181860281018601875281815292959394938601938301828280156124bc57602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff168152602001906004019060208260030104928301926001038202915080841161247f5790505b50505050508152602001600282015481526020016003820180548060200260200160405190810160405280929190818152602001828054801561251e57602002820191906000526020600020905b81548152602001906001019080831161250a575b505050505081526020016004820180548060200260200160405190810160405280929190818152602001828054801561257657602002820191906000526020600020905b815481526020019060010190808311612562575b5050505050815260200160058201604051806040016040529081600082018054806020026020016040519081016040528092919081815260200182805480156125de57602002820191906000526020600020905b8154815260200190600101908083116125ca575b5050505050815260200160018201805480602002602001604051908101604052809291908181526020016000905b828210156126a45760008481526020908190208301805460408051828502810185019091528181529283018282801561269057602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff16815260200190600401906020826003010492830192600103820291508084116126535790505b50505050508152602001906001019061260c565b5050509152505090525092915050565b60606106216126c1612b1c565b600b0160006126d96001600160a01b03871686612b26565b815260200190815260200160002054613327565b6126f6876130d0565b6000612700612b1c565b602086015151865151919250908114158061272057508560400151518114155b1561273e5760405163c81acf3360e01b815260040160405180910390fd5b60005b818110156127ab57865180518290811061275d5761275d615377565b6020026020010151518760200151828151811061277c5761277c615377565b602002602001015151146127a35760405163f001029b60e01b815260040160405180910390fd5b600101612741565b5061ffff88166000908152600d830160205260409020600983015460ff90811690891611156127e65760098301805460ff191660ff8a161790555b805460ff8981166101000261ffff19909216908416171781556040870151805161281a91600184019160209091019061437e565b5061282d87606001518860800151613186565b600282015560a0870151805161284d91600384019160209091019061442d565b506004810160005b8860c00151518110156128e157816128c68a60c00151838151811061287c5761287c615377565b60200260200101518b60e00151848151811061289a5761289a615377565b60200260200101518c610100015185815181106128b9576128b9615377565b602002602001015161339f565b81546001818101845560009384526020909320015501612855565b5060005b8381101561298257885180516005850191612917918490811061290a5761290a615377565b60200260200101516133da565b81546001810183556000928352602092839020015589015180516006850191908390811061294757612947615377565b60209081029190910181015182546001810184556000938452928290208151612979949190910192919091019061437e565b506001016128e5565b508415612a845760006129958888613186565b600081815260058701602052604090205490915061ffff16156129d1576040516366e6550f60e01b815261ffff8c1660048201526024016108d5565b60008181526005860160205260409020805461ffff191661ffff8d16908117909155612a019060068701906131ed565b15612a255760405163ce9c78df60e01b815261ffff8c1660048201526024016108d5565b612a366006860161ffff8d16613459565b506040805161ffff8d16815260ff8a8116602083015289168183015290517fc27376e37bec2cd9acd6957a46ca5cba20a88a21fd0eff1b02cf8536462304769181900360600190a150612aae565b8154610100900460ff1660009081526008850160205260409020612aac9061ffff8c16613459565b505b7fa8932b9bd78638467b3e5c0f0ffa9c72761db9f7f605b8df2f7a899770d064ae8a89604051612adf9291906156f7565b60405180910390a15050505050505050505050565b612afd84612cb0565b6000612b08846118ac565b9050612b15818484612e61565b5050505050565b600061081661124d565b60006001600160401b03821115612b5357604051633995b34160e01b8152600481018390526024016108d5565b5067ffffffffffffffff60a01b60a09190911b166001600160a01b039091161790565b612b7e613465565b612b9b57604051631afcd79f60e31b815260040160405180910390fd5b565b6001600160a01b038116612be55760405162461bcd60e51b815260206004820152600f60248201526e2d32b9379031b7b73a3937b63632b960891b60448201526064016108d5565b612c148161092660017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c361861529b565b612c434261092660017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b61529b565b612c724361092660017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f161529b565b7f1a2dd071001ebf6e03174e3df5b305795a4ad5d41d8fdb9ba41dbbe236713426814243604051612ca5939291906153a6565b60405180910390a150565b8061087e57604051636221cab960e01b815260040160405180910390fd5b82546001840154600160581b82046001600160a01b031691600160501b900460ff1615612d0e57604051636927c40b60e11b815260040160405180910390fd5b612d1a8282868661347f565b612d238361319b565b6001600160a01b031663f16a306683836040518363ffffffff1660e01b8152600401612d5092919061546e565b602060405180830381865afa158015612d6d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d9191906157f6565b612db257818160405163033aace360e41b81526004016108d592919061546e565b84546001600160401b0316612dc78383611c14565b6001600160401b031614612dee576040516337bcc65160e01b815260040160405180910390fd5b935093915050565b8254600160581b600160f81b03191683556000600184015560028301805464ffffffffff19169055612e28828261363c565b6040516001600160401b03821681527f883aa0807cbcd64d238a9795b0433f37884de37e569aff04d512cb1df62c6e019060200161187e565b6000612e6b612b1c565b6001600160401b0383166000908152600e8201602090815260408083208054600160401b900461ffff168452600d85018352818420825160a081018452858152938401859052918301849052606083018490526080830193909352929350909190612ed5876136c8565b6001600160a01b03166040820152600283015463ffffffff1615612f0c5760405163282404cd60e01b815260040160405180910390fd5b612f17838789612cce565b602083018190526001600160a01b03909116808352600285015460ff600160201b90910416606084018190526040840151612f6093869388939291612f5b8e61319b565b6136e8565b63ffffffff1660808201819052600003612f8d57604051638a874ec160e01b815260040160405180910390fd5b608081015160028401805463ffffffff191663ffffffff909216918217905560408083015183516020850151925163f2c6c6fb60e01b81526001600160a01b03918216600482015260248101939093526044830193909352600092169063f2c6c6fb906064016020604051808303816000875af1158015613012573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613036919061538d565b90507f7a40f4421a71ae931ce048b2171197c34a78510813f1d3f4202935dd9f105b1a868360000151846020015185608001518587606001516040516130be969594939291906001600160401b039690961686526001600160a01b03949094166020860152604085019290925263ffffffff166060840152608083015260a082015260c00190565b60405180910390a15050505050505050565b604051631430d62960e21b81526001600160a01b038216906350c358a4906130fc90339060040161494b565b602060405180830381865afa158015613119573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061313d91906157f6565b61087e573360405163451cea1760e11b81526004016108d5919061494b565b6000610624825490565b600061062482600884600001516001600160a01b031663016dff5d613cba565b600060ff8316600883901b61ff001617610621565b600061062482600084600001516001600160a01b0316628e9691613cba565b60006106218383613dc6565b600061062482613df0565b60008080806131e08686613dfb565b9097909650945050505050565b60008181526001830160205260408120541515610621565b60006106218383613e26565b6000670de0b6b3a764000082101561323f57604051637046c4a960e01b8152600481018390526024016108d5565b600061325b613256670de0b6b3a764000085615539565b613f19565b9050600061327182670de0b6b3a7640000615522565b905083821c670de0b6b3a764000081900361328e57509392505050565b6706f05b59d3b200005b80156132e857670de0b6b3a76400006132b18380615522565b6132bb9190615539565b9150671bc16d674ec8000082106132e0576132d681846152ae565b9250600182901c91505b60011c613298565b5090949350505050565b60609290921b6001600160601b03191660209190911b6bffffffffffffffff00000000161760189190911b63ff000000161790565b6040805160208082526104208201909252606091600091908082016104008036833701905050905060005b602081101561339857613366816008615522565b8460001c901c82828151811061337e5761337e615377565b60ff90921660209283029190910190910152600101613352565b5092915050565b67ffffffffffffffff60401b604083901b166001600160401b038416176080826133ca5760006133cd565b60015b60ff16901b179392505050565b8051600090602081111561340b57604051633d71388b60e21b815260048101829052602060248201526044016108d5565b6000805b8281101561345157613422816008615522565b85828151811061343457613434615377565b602090810291909101015160ff16901b919091179060010161340f565b509392505050565b60006106218383613ffc565b600061346f611144565b54600160401b900460ff16919050565b6040516331a9108f60e11b8152600481018490526001600160a01b038084169190861690636352211e90602401602060405180830381865afa1580156134c9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134ed9190615268565b6001600160a01b03161461351857838360405163547208b960e11b81526004016108d592919061546e565b61352181613166565b6001600160a01b0316631789b7b1856040518263ffffffff1660e01b815260040161354c919061494b565b602060405180830381865afa158015613569573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061358d9190615487565b60ff166000036135b2578360405163adc7fced60e01b81526004016108d5919061494b565b80600001516001600160a01b03166318d928316040518163ffffffff1660e01b8152600401602060405180830381865afa1580156135f4573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061361891906157f6565b1561363657604051635e1633d360e11b815260040160405180910390fd5b50505050565b61366a816001600160401b0316613651612b1c565b60ff851660009081526020919091526040902090613459565b613687576040516313e5d5f160e11b815260040160405180910390fd5b6040805160ff841681526001600160401b03831660208201527f3e9a7af2aeebce9d24ef707bdb61adf2708293e374a19f8e9c31f905c68b43c19101610992565b600061062482600584600001516001600160a01b03166389dd9f13613cba565b600786015460009060ff168610613715576040516315f7ec4560e21b8152600481018790526024016108d5565b86600801868154811061372a5761372a615377565b60009182526020822060088204015460079091166004026101000a900463ffffffff169150819003610c3457604051631a95890960e31b81526000906001600160a01b0384169063d4ac484890613787908890889060040161546e565b60a060405180830381865afa1580156137a4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137c89190615813565b602081015181518b549293506137f29263ffffffff9283169290911690610100900460ff1661404b565b158015906138435750613803612b1c565b89546001919091019060009061382d906001600160a01b038916908890610100900460ff166132f2565b815260208101919091526040016000205460ff16155b80156138705750885461385f90610100900460ff1660056158aa565b60ff16816000015163ffffffff1610155b15613ac0578854610100900460ff166001036138925762138884915050610c34565b8854610100900460ff166002036138af576222f238915050610c34565b8854610100900460ff166003036138cc57623234dc915050610c34565b8854610100900460ff166004036138e95762417780915050610c34565b8854610100900460ff16600503613ac057604051631c2aafe760e01b81526000906001600160a01b03851690631c2aafe790613937908990899066222aa723afa12360c91b906004016153a6565b602060405180830381865afa158015613954573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613978919061538d565b90508060010361398f57625219a192505050610c34565b604051631c2aafe760e01b81526000906001600160a01b03861690631c2aafe7906139cc908a908a9066222aa723afa2a760c91b906004016153a6565b602060405180830381865afa1580156139e9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613a0d919061538d565b905080600103613a2557625267d49350505050610c34565b604051631c2aafe760e01b81526000906001600160a01b03871690631c2aafe790613a62908b908b906644554e475f415360c81b906004016153a6565b602060405180830381865afa158015613a7f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613aa3919061538d565b905080600103613abc576251cb8a945050505050610c34565b5050505b6040805160058b01805460606020820284018101855293830181815260009484928491840182828015613b1257602002820191906000526020600020905b815481526020019060010190808311613afe575b5050505050815260200160018201805480602002602001604051908101604052809291908181526020016000905b82821015613bd857600084815260209081902083018054604080518285028101850190915281815292830182828015613bc457602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff1681526020019060040190602082600301049283019260010382029150808411613b875790505b505050505081526020019060010190613b40565b50505050815250509050866001600160a01b0316636f383c0f613c1783600001518b81518110613c0a57613c0a615377565b60200260200101516140a1565b83602001518b81518110613c2d57613c2d615377565b60200260200101518d60000160019054906101000a900460ff168a8a6040518663ffffffff1660e01b8152600401613c699594939291906158c6565b6020604051808303816000875af1158015613c88573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613cac9190615915565b9a9950505050505050505050565b600080856020015185600e811115613cd457613cd46152c1565b600f8110613ce457613ce4615377565b602002015190506001600160a01b03811615613d01579050610633565b83836040518163ffffffff1660e01b8152600401602060405180830381865afa158015613d32573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613d569190615268565b866020015186600e811115613d6d57613d6d6152c1565b600f8110613d7d57613d7d615377565b6001600160a01b039092166020928302919091015286015185600e811115613da757613da76152c1565b600f8110613db757613db7615377565b60200201519695505050505050565b6000826000018281548110613ddd57613ddd615377565b9060005260206000200154905092915050565b60006106248261315c565b60008080613e0985856131ba565b600081815260029690960160205260409095205494959350505050565b60008181526001830160205260408120548015613f0f576000613e4a60018361529b565b8554909150600090613e5e9060019061529b565b9050808214613ec3576000866000018281548110613e7e57613e7e615377565b9060005260206000200154905080876000018481548110613ea157613ea1615377565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080613ed457613ed4615932565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610624565b6000915050610624565b6000600160801b8210613f3957608091821c91613f3690826152ae565b90505b600160401b8210613f5757604091821c91613f5490826152ae565b90505b600160201b8210613f7557602091821c91613f7290826152ae565b90505b620100008210613f9257601091821c91613f8f90826152ae565b90505b6101008210613fae57600891821c91613fab90826152ae565b90505b60108210613fc957600491821c91613fc690826152ae565b90505b60048210613fe457600291821c91613fe190826152ae565b90505b60028210613ff7576106246001826152ae565b919050565b600081815260018301602052604081205461404357508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610624565b506000610624565b600080600561405c86866001614195565b6140669190615539565b6140719060016152ae565b90508281116140815760006107d9565b61408b838261529b565b61409690600a6152ae565b6107d9906002615631565b606060006140ae83613327565b90506000805b82518110156140f7578281815181106140cf576140cf615377565b602002602001015160ff16600003156140f757816140ec81615509565b9250506001016140b4565b50806001600160401b03811115614110576141106144fa565b604051908082528060200260200182016040528015614139578160200160208202803683370190505b50925060005b8181101561418d5782818151811061415957614159615377565b602002602001015184828151811061417357614173615377565b60ff9092166020928302919091019091015260010161413f565b505050919050565b6000806141a285856141cb565b90508280156141bb575060026141b98260016152ae565b115b15610633576107d960028261529b565b805b6063811015610624576141e18360016152ae565b6141ea826141fe565b63ffffffff161015610624576001016141cd565b600063ffffffff8216158061421a5750606363ffffffff831610155b1561422757506000919050565b670de0b6b3a764000061426161423e846063615948565b614249906002615965565b6122299063ffffffff16670de0b6b3a7640000615522565b61427390675cfb2e807b1e000061529b565b614286620186a063ffffffff8616615522565b6142909190615522565b6106249190615539565b6040518060e00160405280600060ff168152602001600060ff168152602001606081526020016000801916815260200160608152602001606081526020016142f5604051806040016040528060608152602001606081525090565b905290565b604051806040016040528060006001600160a01b031681526020016142f5614468565b50805460008255600701600890049060005260206000209081019061087e9190614487565b508054600082559060005260206000209081019061087e9190614487565b508054600082559060005260206000209081019061087e919061449c565b8280548282559060005260206000209060070160089004810192821561441d5791602002820160005b838211156143eb57835183826101000a81548163ffffffff021916908363ffffffff16021790555092602001926004016020816003010492830192600103026143a7565b801561441b5782816101000a81549063ffffffff02191690556004016020816003010492830192600103026143eb565b505b50614429929150614487565b5090565b82805482825590600052602060002090810192821561441d579160200282015b8281111561441d57825182559160200191906001019061444d565b604051806101e00160405280600f906020820280368337509192915050565b5b808211156144295760008155600101614488565b808211156144295760006144b0828261431d565b5060010161449c565b6001600160a01b038116811461087e57600080fd5b600080604083850312156144e157600080fd5b82356144ec816144b9565b946020939093013593505050565b634e487b7160e01b600052604160045260246000fd5b60405161012081016001600160401b0381118282101715614533576145336144fa565b60405290565b604051601f8201601f191681016001600160401b0381118282101715614561576145616144fa565b604052919050565b600082601f83011261457a57600080fd5b81356001600160401b03811115614593576145936144fa565b6145a6601f8201601f1916602001614539565b8181528460208386010111156145bb57600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080608085870312156145ee57600080fd5b84356145f9816144b9565b93506020850135614609816144b9565b92506040850135915060608501356001600160401b0381111561462b57600080fd5b61463787828801614569565b91505092959194509250565b60006020828403121561465557600080fd5b8135614660816144b9565b9392505050565b6001600160401b038116811461087e57600080fd5b801515811461087e57600080fd5b8035613ff78161467c565b600080604083850312156146a857600080fd5b82356146b381614667565b915060208301356146c38161467c565b809150509250929050565b803561ffff81168114613ff757600080fd5b60ff8116811461087e57600080fd5b8035613ff7816146e0565b6000806000806080858703121561471057600080fd5b614719856146ce565b93506020850135614729816146e0565b92506040850135614739816144b9565b9396929550929360600135925050565b6000806040838503121561475c57600080fd5b823561476781614667565b915060208301356001600160401b0381111561478257600080fd5b61478e85828601614569565b9150509250929050565b6000602082840312156147aa57600080fd5b813561466081614667565b63ffffffff8116811461087e57600080fd5b600080600080608085870312156147dd57600080fd5b84356147e8816147b5565b935060208501356147f8816144b9565b925060408501359150606085013561480f816146e0565b939692955090935050565b60008060006060848603121561482f57600080fd5b833561483a816144b9565b9250602084013591506040840135614851816144b9565b809150509250925092565b60006020828403121561486e57600080fd5b5035919050565b600080600080600060a0868803121561488d57600080fd5b8535614898816144b9565b945060208601356148a8816146e0565b935060408601356148b8816144b9565b94979396509394606081013594506080013592915050565b600080604083850312156148e357600080fd5b50508035926020909101359150565b6000815180845260005b81811015614918576020818501810151868301820152016148fc565b506000602082860101526020601f19601f83011685010191505092915050565b60208152600061062160208301846148f2565b6001600160a01b0391909116815260200190565b60008151808452602080850194506020840160005b838110156149995781516001600160a01b031687529582019590820190600101614974565b509495945050505050565b60008151808452602080850194506020840160005b83811015614999578151875295820195908201906001016149b9565b60008151808452602080850194506020840160005b8381101561499957815163ffffffff16875295820195908201906001016149ea565b61ffff8c1681528a151560208201526001600160a01b038a1660408201526060810189905263ffffffff88166080820152600061016060ff891660a08401528060c0840152614a5d8184018961495f565b905082810360e0840152614a7181886149a4565b9050828103610100840152614a8681876149a4565b60ff86166101208501529050828103610140840152614aa581856149d5565b9e9d5050505050505050505050505050565b600080600060608486031215614acc57600080fd5b614ad5846146ce565b92506020840135614ae5816146e0565b91506040840135614851816146e0565b600080600080600080600060e0888a031215614b1057600080fd5b8735614b1b816144b9565b965060208801359550604088013594506060880135614b39816146e0565b93506080880135614b49816146e0565b925060a0880135614b59816146e0565b915060c0880135614b69816146e0565b8091505092959891949750929550565b600080600060608486031215614b8e57600080fd5b8335614b9981614667565b92506020840135614ba9816144b9565b929592945050506040919091013590565b60008060008060808587031215614bd057600080fd5b8435614bdb816144b9565b966020860135965060408601359560600135945092505050565b600060208284031215614c0757600080fd5b610621826146ce565b60008282518085526020808601955060208260051b8401016020860160005b84811015614c5d57601f19868403018952614c4b8383516149d5565b98840198925090830190600101614c2f565b5090979650505050505050565b6000815160408452614c7f60408501826149a4565b9050602083015184820360208601526107d98282614c10565b6020815260ff825116602082015260006020830151614cbc604084018260ff169052565b50604083015160e06060840152614cd76101008401826149d5565b9050606084015160808401526080840151601f19808584030160a0860152614cff83836149a4565b925060a08601519150808584030160c0860152614d1c83836149a4565b925060c08601519150808584030160e0860152506107d98282614c6a565b60008151808452602080850194506020840160005b8381101561499957815160ff1687529582019590820190600101614d4f565b6020815260006106216020830184614d3a565b60006001600160401b03821115614d9a57614d9a6144fa565b5060051b60200190565b600082601f830112614db557600080fd5b81356020614dca614dc583614d81565b614539565b828152600592831b8501820192828201919087851115614de957600080fd5b8387015b85811015614c5d5780356001600160401b03811115614e0c5760008081fd5b8801603f81018a13614e1e5760008081fd5b858101356040614e30614dc583614d81565b82815291851b8301810191888101908d841115614e4d5760008081fd5b938201935b83851015614e775784359250614e67836146e0565b8282529389019390890190614e52565b885250505093850193508401614ded565b600082601f830112614e9957600080fd5b81356020614ea9614dc583614d81565b8083825260208201915060208460051b870101935086841115614ecb57600080fd5b602086015b84811015614ef0578035614ee3816147b5565b8352918301918301614ed0565b509695505050505050565b600082601f830112614f0c57600080fd5b81356020614f1c614dc583614d81565b82815260059290921b84018101918181019086841115614f3b57600080fd5b8286015b84811015614ef05780356001600160401b03811115614f5e5760008081fd5b614f6c8986838b0101614e88565b845250918301918301614f3f565b600082601f830112614f8b57600080fd5b81356020614f9b614dc583614d81565b8083825260208201915060208460051b870101935086841115614fbd57600080fd5b602086015b84811015614ef05780358352918301918301614fc2565b600082601f830112614fea57600080fd5b81356020614ffa614dc583614d81565b8083825260208201915060208460051b87010193508684111561501c57600080fd5b602086015b84811015614ef057803561503481614667565b8352918301918301615021565b600082601f83011261505257600080fd5b81356020615062614dc583614d81565b8083825260208201915060208460051b87010193508684111561508457600080fd5b602086015b84811015614ef057803561509c8161467c565b8352918301918301615089565b60008060008060008060c087890312156150c257600080fd5b6150cb876146ce565b955060208701356150db816146e0565b945060408701356001600160401b03808211156150f757600080fd5b90880190610120828b03121561510c57600080fd5b615114614510565b82358281111561512357600080fd5b61512f8c828601614da4565b82525060208301358281111561514457600080fd5b6151508c828601614efb565b60208301525060408301358281111561516857600080fd5b6151748c828601614e88565b604083015250615186606084016146ef565b6060820152615197608084016146ef565b608082015260a0830135828111156151ae57600080fd5b6151ba8c828601614f7a565b60a08301525060c0830135828111156151d257600080fd5b6151de8c828601614fd9565b60c08301525060e0830135828111156151f657600080fd5b6152028c828601614fd9565b60e083015250610100808401358381111561521c57600080fd5b6152288d828701615041565b828401525050809650505050615240606088016146ef565b925061524e608088016146ef565b915061525c60a0880161468a565b90509295509295509295565b60006020828403121561527a57600080fd5b8151614660816144b9565b634e487b7160e01b600052601160045260246000fd5b8181038181111561062457610624615285565b8082018082111561062457610624615285565b634e487b7160e01b600052602160045260246000fd5b6001600160a01b0386811682528581166020830152604082018590528316606082015260a081016003831061531c57634e487b7160e01b600052602160045260246000fd5b8260808301529695505050505050565b60006020828403121561533e57600080fd5b815161466081614667565b60008060006060848603121561535e57600080fd5b8351925060208401519150604084015190509250925092565b634e487b7160e01b600052603260045260246000fd5b60006020828403121561539f57600080fd5b5051919050565b6001600160a01b039390931683526020830191909152604082015260600190565b8781528660208201526001600160401b0386166040820152600060018060a01b03808716606084015260e0608084015261540460e08401876148f2565b941660a08301525063ffffffff9190911660c09091015295945050505050565b6000806000806080858703121561543a57600080fd5b84516154458161467c565b60208601516040870151919550935061545d816147b5565b606086015190925061480f8161467c565b6001600160a01b03929092168252602082015260400190565b60006020828403121561549957600080fd5b8151614660816146e0565b634e487b7160e01b600052601260045260246000fd5b600060ff8316806154cd576154cd6154a4565b8060ff84160491505092915050565b60ff818116838216019081111561062457610624615285565b600082615504576155046154a4565b500690565b60006001820161551b5761551b615285565b5060010190565b808202811582820484141761062457610624615285565b600082615548576155486154a4565b500490565b600181815b8085111561558857816000190482111561556e5761556e615285565b8085161561557b57918102915b93841c9390800290615552565b509250929050565b60008261559f57506001610624565b816155ac57506000610624565b81600181146155c257600281146155cc576155e8565b6001915050610624565b60ff8411156155dd576155dd615285565b50506001821b610624565b5060208310610133831016604e8410600b841016171561560b575081810a610624565b615615838361554d565b806000190482111561562957615629615285565b029392505050565b60006106218383615590565b60008282518085526020808601955060208260051b8401016020860160005b84811015614c5d57601f19868403018952615678838351614d3a565b9884019892509083019060010161565c565b60008151808452602080850194506020840160005b838110156149995781516001600160401b03168752958201959082019060010161569f565b60008151808452602080850194506020840160005b838110156149995781511515875295820195908201906001016156d9565b61ffff83168152604060208201526000825161012080604085015261572061016085018361563d565b91506020850151603f198086850301606087015261573e8483614c10565b9350604087015191508086850301608087015261575b84836149d5565b93506060870151915061577360a087018360ff169052565b608087015160ff1660c087015260a0870151868503820160e0880152915061579b84836149a4565b935060c087015191506101008187860301818801526157ba858461568a565b945060e088015192508187860301848801526157d6858461568a565b9450808801519350508086850301610140870152505061152082826156c4565b60006020828403121561580857600080fd5b81516146608161467c565b600060a0828403121561582557600080fd5b60405160a081018181106001600160401b0382111715615847576158476144fa565b6040528251615855816147b5565b81526020830151615865816147b5565b60208201526040830151615878816147b5565b6040820152606083015161588b816147b5565b6060820152608083015161589e816147b5565b60808201529392505050565b60ff818116838216029081169081811461339857613398615285565b60a0815260006158d960a0830188614d3a565b82810360208401526158eb81886149d5565b60ff96909616604084015250506001600160a01b0392909216606083015260809091015292915050565b60006020828403121561592757600080fd5b8151614660816147b5565b634e487b7160e01b600052603160045260246000fd5b63ffffffff82811682821603908082111561339857613398615285565b63ffffffff8181168382160190808211156133985761339861528556fe22573091f17911fb166032a3d9e0554aa73d31b7b7ddea4a4dd2995650af84bda26469706673582212207e6bbc9423d671047c4b23fa353fd59b026680d1f19383d3556dfd50fae2abb564736f6c63430008170033
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in S
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
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.