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:
GameObjectController
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 "../openzeppelin/EnumerableSet.sol";
import "../interfaces/IGOC.sol";
import "../proxy/Controllable.sol";
import "../lib/GameObjectControllerLib.sol";
import "../lib/PackingLib.sol";
import "../lib/EventLib.sol";
import "../lib/StoryLib.sol";
import "../lib/MonsterLib.sol";
import "../lib/GOCLib.sol";
contract GameObjectController is Controllable, IGOC {
using EnumerableSet for EnumerableSet.UintSet;
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 ------------------------ CONSTANTS
/// @notice Version of the contract
string public constant VERSION = "1.1.5";
//endregion ------------------------ CONSTANTS
//region ------------------------ INITIALIZER
function init(address controller_) external initializer {
__Controllable_init(controller_);
}
//endregion ------------------------ INITIALIZER
//region ------------------------ VIEWS
function getObjectMeta(uint32 objectId) external view override returns (uint8 biome, uint8 objectSubType) {
return GameObjectControllerLib.getObjectMeta(objectId);
}
function isAvailableForHero(address heroToken, uint heroTokenId, uint32 objId) external view returns (bool) {
return GameObjectControllerLib.isAvailableForHero(IController(controller()), heroToken, heroTokenId, objId);
}
function isBattleObject(uint32 objectId) external view override returns (bool) {
return GameObjectControllerLib.isBattleObject(objectId);
}
function getObjectTypeBySubType(uint32 objectId) external view returns (ObjectType) {
return GameObjectControllerLib.getObjectTypeBySubType(objectId);
}
function getMonsterInfo(address hero, uint heroId, uint32 objectId) external view returns (IGOC.MonsterGenInfo memory mGenInfo, IGOC.GeneratedMonster memory gen) {
return GameObjectControllerLib.getMonsterInfo(hero, heroId, objectId);
}
function getIteration(address heroToken, uint heroTokenId, uint32 objId) external view returns (uint) {
return GameObjectControllerLib.getIteration(heroToken, heroTokenId, objId);
}
function getLastHeroFightTs(address heroToken, uint heroTokenId) external view returns (uint) {
return GameObjectControllerLib.getLastHeroFightTs(heroToken, heroTokenId);
}
function getFightDelay() external view returns (uint) {
return GameObjectControllerLib.getFightDelay();
}
function getStoryId(uint32 objectId) external view returns (uint16) {
return GameObjectControllerLib.getStoryId(objectId);
}
function getEventInfo(uint32 objectId) external view returns (EventInfo memory) {
return GameObjectControllerLib.getEventInfo(objectId);
}
function getObjectIds(uint8 biome, ObjectSubType subType) external view returns (uint[] memory) {
return GameObjectControllerLib.getObjectIds(biome, subType);
}
function getMonsterMultiplier(uint8 heroNgLevel) external pure returns (uint) {
return MonsterLib.monsterMultiplier(heroNgLevel);
}
//endregion ------------------------ VIEWS
//region ------------------------ REGISTRATION
function registerEvent(EventRegInfo memory regInfo) external {
GameObjectControllerLib.registerEvent(IController(controller()), regInfo);
}
function registerStory(uint16 storyId, uint8 biome, ObjectSubType subType) external {
GameObjectControllerLib.registerStory(IController(controller()), storyId, biome, subType);
}
function registerMonster(MonsterGenInfo memory monsterGenInfo) external {
GameObjectControllerLib.registerMonster(IController(controller()), monsterGenInfo);
}
function removeObject(uint32 objectId) external {
GameObjectControllerLib.removeObject(IController(controller()), objectId);
}
//endregion ------------------------ REGISTRATION
//region ------------------------ OBJECT
/// @dev Chances in range 0-1e9
function getRandomObject(
uint8[] memory cTypes,
uint32[] memory chances,
uint8 biome,
address heroToken,
uint heroTokenId
) external override returns (uint32 objectId) {
return GameObjectControllerLib.getRandomObject(IController(controller()), cTypes, chances, biome, heroToken, heroTokenId);
}
function open(address heroToken, uint heroTokenId, uint32 objectId) external override returns (uint iteration) {
return GameObjectControllerLib.open(IController(controller()), heroToken, heroTokenId, objectId);
}
function action(
address sender,
uint64 dungeonId,
uint32 objectId,
address heroToken,
uint heroTokenId,
uint8 stageId,
bytes memory data
) external override returns (ActionResult memory) {
return GameObjectControllerLib.action(IController(controller()), sender, dungeonId, objectId, heroToken, heroTokenId, stageId, data);
}
//endregion ------------------------ OBJECT
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
/// @notice All errors of the app
interface IAppErrors {
//region ERC20Errors
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
//endregion ERC20Errors
//region ERC721Errors
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
//endregion ERC721Errors
error ZeroAddress();
error ZeroValueNotAllowed();
error ZeroToken();
error LengthsMismatch();
error NotEnoughBalance();
error NotEnoughAllowance();
error EmptyNameNotAllowed();
error NotInitialized();
error AlreadyInitialized();
error ReentrancyGuardReentrantCall();
error TooLongString();
error AlreadyDeployed(address deployed);
//region Restrictions
error ErrorNotDeployer(address sender);
error ErrorNotGoc();
error NotGovernance(address sender);
error ErrorOnlyEoa();
error NotEOA(address sender);
error ErrorForbidden(address sender);
error AdminOnly();
error ErrorNotItemController(address sender);
error ErrorNotHeroController(address sender);
error ErrorNotDungeonFactory(address sender);
error ErrorNotObjectController(address sender);
error ErrorNotStoryController();
error ErrorNotAllowedSender();
error MintNotAllowed();
//endregion Restrictions
//region PackingLib
error TooHighValue(uint value);
error IntValueOutOfRange(int value);
error OutOfBounds(uint index, uint length);
error UnexpectedValue(uint expected, uint actual);
error WrongValue(uint newValue, uint actual);
error IntOutOfRange(int value);
error ZeroValue();
/// @notice packCustomDataChange requires an input string with two zero bytes at the beginning
/// 0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0000
/// This error happens if these bytes are not zero
error IncompatibleInputString();
error IncorrectOtherItemTypeKind(uint8 kind);
//endregion PackingLib
//region Hero
error ErrorHeroIsNotRegistered(address heroToken);
error ErrorHeroIsDead(address heroToken, uint heroTokenId);
error ErrorHeroNotInDungeon();
error HeroInDungeon();
error ErrorNotOwner(address token, uint tokenId);
error Staked(address heroToken, uint heroId);
error NameTaken();
error TooBigName();
error WrongSymbolsInTheName();
error NoPayToken(address token, uint payTokenAmount);
error AlreadyHaveReinforcement();
/// @notice SIP-001 - Reinforcement requires 3 skills
error ErrorReinforcementRequiresThreeSkills();
error WrongTier(uint tier);
error NotEnoughNgLevel(uint8 ngLevel);
error NgpNotActive(address hero);
error RebornNotAllowed();
error AlreadyPrePaidHero();
//endregion Hero
//region Dungeon
error ErrorDungeonIsFreeAlready();
error ErrorNoEligibleDungeons();
error ErrorDungeonBusy();
error ErrorNoDungeonsForBiome(uint8 heroBiome);
error ErrorDungeonCompleted();
error ErrorAlreadyInDungeon();
error NotEnoughTokens(uint balance, uint expectedBalance);
error DungeonAlreadySpecific(uint16 dungNum);
error DungeonAlreadySpecific2(uint16 dungNum);
error WrongSpecificDungeon();
//endregion Dungeon
//region Items
error ErrorItemNotEligibleForTheSlot(uint itemType, uint8 itemSlot);
error ErrorItemSlotBusyHand(uint8 slot);
error ErrorItemSlotBusy();
error ErrorItemNotInSlot();
error ErrorConsumableItemIsUsed(address item);
error ErrorCannotRemoveItemFromMap();
error ErrorCannotRemoveDataFromMap();
error EquippedItemsExist();
error ItemEquipped(address item, uint itemId);
error ZeroItemMetaType();
error NotZeroOtherItemMetaType();
error ZeroLevel();
error ItemTypeChanged();
error ItemMetaTypeChanged();
error UnknownItem(address item);
error ErrorEquipForbidden();
error EquipForbiddenInDungeon();
error TakeOffForbiddenInDungeon();
error Consumable(address item);
error NotConsumable(address item);
error Broken(address item);
error ZeroLife();
error RequirementsToItemAttributes();
error NotEquipped(address item);
error ZeroDurability();
error ZeroAugmentation();
error TooHighAgLevel(uint8 augmentationLevel);
error UseForbiddenZeroPayToken();
error IncorrectMinMaxAttributeRange(int32 min, int32 max);
error SameIdsNotAllowed();
error ZeroFragility();
error OtherTypeItemNotRepairable();
error NotOther();
error DoubleItemUsageForbidden(uint itemIndex, address[] items);
error ItemAlreadyUsedInSlot(address item, uint8 equippedSlot);
error WrongWayToRegisterItem();
error UnionItemNotFound(address item);
error WrongListUnionItemTokens(address item, uint countTokens, uint requiredCountTokens);
error UnknownUnionConfig(uint unionConfigId);
error UserHasNoKeyPass(address user, address keyPassItem);
error MaxValue(uint value);
error UnexpectedOtherItem(address item);
error NotExist();
//endregion Items
//region Stages
error ErrorWrongStage(uint stage);
error ErrorNotStages();
//endregion Stages
//region Level
error ErrorWrongLevel(uint heroLevel);
error ErrorLevelTooLow(uint heroLevel);
error ErrorHeroLevelStartFrom1();
error ErrorWrongLevelUpSum();
error ErrorMaxLevel();
//endregion Level
//region Treasure
error ErrorNotValidTreasureToken(address treasureToken);
//endregion Treasure
//region State
error ErrorPaused();
error ErrorNotReady();
error ErrorNotObject1();
error ErrorNotObject2();
error ErrorNotCompleted();
//endregion State
//region Biome
error ErrorNotBiome();
error ErrorIncorrectBiome(uint biome);
error TooHighBiome(uint biome);
//endregion Biome
//region Misc
error ErrorWrongMultiplier(uint multiplier);
error ErrorNotEnoughMana(uint32 mana, uint requiredMana);
error ErrorExperienceMustNotDecrease();
error ErrorNotEnoughExperience();
error ErrorNotChances();
error ErrorNotEligible(address heroToken, uint16 dungNum);
error ErrorZeroKarmaNotAllowed();
//endregion Misc
//region GOC
error GenObjectIdBiomeOverflow(uint8 biome);
error GenObjectIdSubTypeOverflow(uint subType);
error GenObjectIdIdOverflow(uint id);
error UnknownObjectTypeGoc1(uint8 objectType);
error UnknownObjectTypeGoc2(uint8 objectType);
error UnknownObjectTypeGocLib1(uint8 objectType);
error UnknownObjectTypeGocLib2(uint8 objectType);
error UnknownObjectTypeForSubtype(uint8 objectSubType);
error FightDelay();
error ZeroChance();
error TooHighChance(uint32 chance);
error TooHighRandom(uint random);
error EmptyObjects();
error ObjectNotFound();
error WrongGetObjectTypeInput();
error WrongChances(uint32 chances, uint32 maxChances);
//endregion GOC
//region Story
error PageNotRemovedError(uint pageId);
error NotItem1();
error NotItem2();
error NotRandom(uint32 random);
error NotHeroData();
error NotGlobalData();
error ZeroStoryIdRemoveStory();
error ZeroStoryIdStoryAction();
error ZeroStoryIdAction();
error NotEnoughAmount(uint balance, uint requiredAmount);
error NotAnswer();
error AnswerStoryIdMismatch(uint16 storyId, uint16 storyIdFromAnswerHash);
error AnswerPageIdMismatch(uint16 pageId, uint16 pageIdFromAnswerHash);
//endregion Story
//region FightLib
error NotMagic();
error NotAType(uint atype);
//endregion FightLib
//region MonsterLib
error NotYourDebuffItem();
error UnknownAttackType(uint attackType);
error NotYourAttackItem();
/// @notice The skill item cannot be used because it doesn't belong either to the hero or to the hero's helper
error NotYourBuffItem();
//endregion MonsterLib
//region GameToken
error ApproveToZeroAddress();
error MintToZeroAddress();
error TransferToZeroAddress();
error TransferAmountExceedsBalance(uint balance, uint value);
error InsufficientAllowance();
error BurnAmountExceedsBalance();
error NotMinter(address sender);
//endregion GameToken
//region NFT
error TokenTransferNotAllowed();
error IdOverflow(uint id);
error NotExistToken(uint tokenId);
error EquippedItemIsNotAllowedToTransfer(uint tokenId);
//endregion NFT
//region CalcLib
error TooLowX(uint x);
//endregion CalcLib
//region Controller
error NotFutureGovernance(address sender);
//endregion Controller
//region Oracle
error OracleWrongInput();
//endregion Oracle
//region ReinforcementController
error AlreadyStaked();
error MaxFee(uint8 fee);
error MinFee(uint8 fee);
error StakeHeroNotStats();
error NotStaked();
error NoStakedHeroes();
error GuildHelperNotAvailable(uint guildId, address helper, uint helperId);
error HelperNotAvailableInGivenBiome();
//endregion ReinforcementController
//region SponsoredHero
error InvalidHeroClass();
error ZeroAmount();
error InvalidProof();
error NoHeroesAvailable();
error AlreadyRegistered();
//endregion SponsoredHero
//region SacraRelay
error SacraRelayNotOwner();
error SacraRelayNotDelegator();
error SacraRelayNotOperator();
error SacraRelayInvalidChainId(uint callChainId, uint blockChainId);
error SacraRelayInvalidNonce(uint callNonce, uint txNonce);
error SacraRelayDeadline();
error SacraRelayDelegationExpired();
error SacraRelayNotAllowed();
error SacraRelayInvalidSignature();
/// @notice This error is generated when custom error is caught
/// There is no info about custom error in SacraRelay
/// but you can decode custom error by selector, see tests
error SacraRelayNoErrorSelector(bytes4 selector, string tracingInfo);
/// @notice This error is generated when custom error is caught
/// There is no info about custom error in SacraRelay
/// but you can decode custom error manually from {errorBytes} as following:
/// if (keccak256(abi.encodeWithSignature("MyError()")) == keccak256(errorBytes)) { ... }
error SacraRelayUnexpectedReturnData(bytes errorBytes, string tracingInfo);
error SacraRelayCallToNotContract(address notContract, string tracingInfo);
//endregion SacraRelay
//region Misc
error UnknownHeroClass(uint heroClass);
error AbsDiff(int32 a, int32 b);
//region Misc
//region ------------------------ UserController
error NoAvailableLootBox(address msgSender, uint lootBoxKind);
error FameHallHeroAlreadyRegistered(uint8 openedNgLevel);
//endregion ------------------------ UserController
//region ------------------------ Guilds
error AlreadyGuildMember();
error NotGuildMember();
error WrongGuild();
error GuildActionForbidden(uint right);
error GuildHasMaxSize(uint guildSize);
error GuildHasMaxLevel(uint level);
error TooLongUrl();
error TooLongDescription();
error CannotRemoveGuildOwnerFromNotEmptyGuild();
error GuildControllerOnly();
error GuildAlreadyHasShelter();
error ShelterIsBusy();
error ShelterIsNotRegistered();
error ShelterIsNotOwnedByTheGuild();
error ShelterIsInUse();
error GuildHasNoShelter();
error ShelterBidIsNotAllowedToBeUsed();
error ShelterHasHeroesInside();
error SecondGuildAdminIsNotAllowed();
error NotEnoughGuildBankBalance(uint guildId);
error GuildReinforcementCooldownPeriod();
error NoStakedGuildHeroes();
error NotStakedInGuild();
error ShelterHasNotEnoughLevelForReinforcement();
error NotBusyGuildHelper();
error GuildRequestNotActive();
error GuildRequestNotAvailable();
error NotAdminCannotAddMemberWithNotZeroRights();
//endregion ------------------------ Guilds
//region ------------------------ Shelters
error ErrorNotShelterController();
error ErrorNotGuildController();
error ShelterHasNotItem(uint shelterId, address item);
error MaxNumberItemsSoldToday(uint numSoldItems, uint limit);
error GuildHasNotEnoughPvpPoints(uint64 pointsAvailable, uint pointRequired);
error FreeShelterItemsAreNotAllowed(uint shelterId, address item);
error TooLowShelterLevel(uint8 shelterLevel, uint8 allowedShelterLevel);
error NotEnoughPvpPointsCapacity(address user, uint usedPoints, uint pricePvpPoints, uint64 capactiy);
error IncorrectShelterLevel(uint8 shelterLevel);
//endregion ------------------------ Shelters
//region ------------------------ Auction
error WrongAuctionPosition();
error AuctionPositionClosed();
error AuctionBidOpened(uint positionId);
error TooLowAmountToBid();
error AuctionEnded();
error TooLowAmountForNewBid();
error AuctionSellerOnly();
error AuctionBuyerOnly();
error AuctionBidNotFound();
error AuctionBidClosed();
error OnlyShelterAuction();
error CannotCloseLastBid();
error AuctionNotEnded();
error NotShelterAuction();
error AuctionPositionOpened(uint positionId);
error AuctionSellerCannotBid();
error CannotApplyNotLastBid();
error AuctionGuildWithShelterCannotBid();
//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";
/// @notice All events of the app
interface IApplicationEvents {
//region ------------------ StatController
event HeroItemSlotChanged(
address heroToken,
uint heroTokenId,
uint itemType,
uint itemSlot,
address itemToken,
uint itemTokenId,
bool equip,
address caller
);
event CurrentStatsChanged(
address heroToken,
uint heroTokenId,
IStatController.ChangeableStats change,
bool increase,
address caller
);
event BonusAttributesChanged(
address heroToken,
uint heroTokenId,
bool add,
bool temporally,
address caller
);
event TemporallyAttributesCleared(address heroToken, uint heroTokenId, address caller);
event NewHeroInited(address heroToken, uint heroTokenId, IStatController.ChangeableStats stats);
event LevelUp(
address heroToken,
uint heroTokenId,
uint heroClass,
IStatController.CoreAttributes change
);
event ConsumableUsed(address heroToken, uint heroTokenId, address item);
event RemoveConsumableUsage(address heroToken, uint heroTokenId, address item);
event HeroCustomDataChanged(address token, uint tokenId, bytes32 index, uint value);
event HeroCustomDataChangedNg(address token, uint tokenId, bytes32 index, uint value, uint8 ngLevel);
event HeroCustomDataCleared(address token, uint tokenId);
event GlobalCustomDataChanged(bytes32 index, uint value);
//endregion ------------------ StatController
//region ------------------ DungeonFactoryController
event DungeonLaunched(
uint16 dungeonLogicNum,
uint64 dungeonId,
address heroToken,
uint heroTokenId,
address treasuryToken,
uint treasuryAmount
);
event BossCompleted(uint32 objectId, uint biome, address hero, uint heroId);
event FreeDungeonAdded(uint8 biome, uint64 dungeonId);
event ObjectOpened(uint64 dungId, address hero, uint id, uint32 objId, uint iteration, uint currentStage);
event Clear(uint64 dungId);
event DungeonLogicRegistered(uint16 dungLogicId, IDungeonFactory.DungeonGenerateInfo info);
event DungeonLogicRemoved(uint16 dungLogicId);
event DungeonSpecificLogicRegistered(uint16 dungLogicId, uint biome, uint heroCls);
event DungeonSpecificLogicRemoved(uint16 dungLogicId, uint heroLvl, uint heroCls);
event DungeonRegistered(uint16 dungLogicId, uint64 dungeonId);
event DungeonRemoved(uint16 dungLogicId, uint64 dungeonId);
event MinLevelForTreasuryChanged(address token, uint level);
event ObjectAction(
uint64 dungId,
IGOC.ActionResult result,
uint currentStage,
address heroToken,
uint heroTokenId,
uint newStage
);
/// @notice On add the item to the dungeon
event AddTreasuryItem(uint64 dungId, address itemAdr, uint itemId);
event AddTreasuryToken(uint64 dungId, address token, uint amount);
event ClaimToken(uint64 dungId, address token, uint amount);
event ClaimItem(uint64 dungId, address token, uint id);
event Entered(uint64 dungId, address hero, uint id);
event DungeonCompleted(uint16 dungLogicNum, uint64 dungId, address hero, uint heroId);
event Exit(uint64 dungId, bool claim);
event ExitForcibly(uint64 dungId, address hero, uint heroId);
event FreeDungeonRemoved(uint8 biome, uint64 dungeonId);
event HeroCurrentDungeonChanged(address hero, uint heroId, uint64 dungeonId);
//endregion ------------------ DungeonFactoryController
//region ------------------ GameObjectController
event EventRegistered(uint32 objectId, IGOC.EventRegInfo eventRegInfo);
event StoryRegistered(uint32 objectId, uint16 storyId);
event MonsterRegistered(uint32 objectId, IGOC.MonsterGenInfo monsterGenInfo);
event ObjectRemoved(uint32 objectId);
event ObjectResultEvent(
uint64 dungeonId,
uint32 objectId,
IGOC.ObjectType objectType,
address hero,
uint heroId,
uint8 stageId,
uint iteration,
bytes data,
IGOC.ActionResult result,
uint salt
);
//endregion ------------------ GameObjectController
//region ------------------ StoryController
event SetBurnItemsMeta(uint storyId, IStoryController.AnswerBurnRandomItemMeta meta);
event SetNextObjRewriteMeta(uint storyId, IStoryController.NextObjRewriteMeta meta);
event SetAnswersMeta(uint storyId, uint16[] answerPageIds, uint8[] answerHeroClasses, uint16[] answerIds);
event SetAnswerNextPageMeta(uint storyId, IStoryController.AnswerNextPageMeta meta);
event SetAnswerAttributeRequirements(uint storyId, IStoryController.AnswerAttributeRequirementsMeta meta);
event SetAnswerItemRequirements(uint storyId, IStoryController.AnswerItemRequirementsMeta meta);
event SetAnswerTokenRequirementsMeta(uint storyId, IStoryController.AnswerTokenRequirementsMeta meta);
event SetAnswerAttributes(uint storyId, IStoryController.AnswerAttributesMeta meta);
event SetAnswerHeroCustomDataRequirementMeta(uint storyId, IStoryController.AnswerCustomDataMeta meta);
event SetAnswerGlobalCustomDataRequirementMeta(uint storyId, IStoryController.AnswerCustomDataMeta meta);
event SetSuccessInfo(uint storyId, IStoryController.AnswerResultMeta meta);
event SetFailInfo(uint storyId, IStoryController.AnswerResultMeta meta);
event SetCustomDataResult(uint storyId, IStoryController.AnswerCustomDataResultMeta meta, IStoryController.CustomDataResult _type);
event StoryCustomDataRequirements(uint storyId, bytes32 requiredCustomDataIndex, uint requiredCustomDataMinValue, uint requiredCustomDataMaxValue, bool requiredCustomDataIsHero);
event StoryRequiredLevel(uint storyId, uint requiredLevel);
event StoryFinalized(uint32 objectId, uint storyId);
event StoryRemoved(uint32 objectId, uint storyId);
event ItemBurned(
address heroToken,
uint heroTokenId,
uint64 dungeonId,
uint objectId,
address nftToken,
uint nftId,
uint stageId,
uint iteration
);
/// @notice Durability of the item was reduced to 0
event ItemBroken(
address heroToken,
uint heroTokenId,
uint64 dungeonId,
uint objectId,
address nftToken,
uint nftId,
uint stageId,
uint iteration
);
event NotEquippedItemBurned(
address heroToken,
uint heroTokenId,
uint64 dungeonId,
uint storyId,
address nftToken,
uint nftId,
uint stageId,
uint iteration
);
event StoryChangeAttributes(
uint32 objectId,
address heroToken,
uint heroTokenId,
uint64 dungeonId,
uint storyId,
uint stageId,
uint iteration,
int32[] attributes
);
//endregion ------------------ StoryController
//region ------------------------ HeroController
event HeroRegistered(address hero, uint8 heroClass, address payToken, uint payAmount);
event HeroCreatedNgp(address hero, uint heroId, string name, address owner, string refCode, uint8 tier, uint8 ngLevel);
event BiomeChanged(address hero, uint heroId, uint8 biome);
event LevelUp(address hero, uint heroId, address owner, IStatController.CoreAttributes change);
event ReinforcementAsked(address hero, uint heroId, address helpHeroToken, uint helpHeroId);
event GuildReinforcementAsked(address hero, uint heroId, address helpHeroToken, uint helpHeroId);
event OtherItemGuildReinforcement(address item, uint itemId, address hero, uint heroId, address helpHeroToken, uint helpHeroId);
event ReinforcementReleased(address hero, uint heroId, address helperToken, uint helperId);
event GuildReinforcementReleased(address hero, uint heroId, address helperToken, uint helperId);
event Killed(address hero, uint heroId, address killer, bytes32[] dropItems, uint dropTokenAmount);
event Reborn(address hero, uint heroId, uint8 newNgLevel);
event BossKilled(address account, address hero, uint heroId, uint8 biome, uint8 newNgLevel, bool reborn, uint rewardAmount);
event TierSetup(uint8 tier, address hero, uint72 payAmount, uint8[] slots, address[][] items);
//endregion ------------------------ HeroController
//region ------------------------ FightLib
event FightResultProcessed(
address sender,
IFightCalculator.FightInfoInternal result,
IFightCalculator.FightCall callData,
uint iteration
);
//endregion ------------------------ FightLib
//region ------------------------ Oracle
event Random(uint number, uint max);
//endregion ------------------------ Oracle
//region ------------------------ Controller
event OfferGovernance(address newGov);
event GovernanceAccepted(address gov);
event StatControllerChanged(address value);
event StoryControllerChanged(address value);
event GameObjectControllerChanged(address value);
event ReinforcementControllerChanged(address value);
event OracleChanged(address value);
event TreasuryChanged(address value);
event ItemControllerChanged(address value);
event HeroControllerChanged(address value);
event GameTokenChanged(address value);
event DungeonFactoryChanged(address value);
event ProxyUpdated(address proxy, address logic);
event Claimed(address token, uint amount);
event TokenStatusChanged(address token, bool status);
event UserControllerChanged(address value);
event GuildControllerChanged(address value);
event GameTokenPriceChanged(uint value);
event RewardsPoolChanged(address value);
event Process(address token, uint amount, address from, uint toBurn, uint toTreasury, uint toGov);
//endregion ------------------------ Controller
//region ------------------------ ReinforcementController
event HeroStaked(address heroToken, uint heroId, uint biome, uint score);
event HeroStakedV2(address heroToken, uint heroId, uint biome, uint rewardAmount);
event HeroWithdraw(address heroToken, uint heroId);
event HeroAsk(address heroToken, uint heroId);
event HeroAskV2(address heroToken, uint heroId, uint hitsLast24h, uint fixedFee, uint helperRewardAmount);
event TokenRewardRegistered(address heroToken, uint heroId, address token, uint amountAdded, uint totalAmount);
event GuildTokenRewardRegistered(address heroToken, uint heroId, address token, uint amountAdded, uint guildId);
event NftRewardRegistered(address heroToken, uint heroId, address token, uint id);
event GuildNftRewardRegistered(address heroToken, uint heroId, address token, uint id, uint guildId);
event ToHelperRatioChanged(uint value);
event ClaimedToken(address heroToken, uint heroId, address token, uint amount, address recipient);
event ClaimedItem(address heroToken, uint heroId, address item, uint itemId, address recipient);
event MinLevelChanged(uint8 value);
event MinLifeChancesChanged(uint value);
//endregion ------------------------ ReinforcementController
//region ------------------------ Treasury, reward pool
event AssetsSentToDungeon(address dungeon, address token, uint amount);
event RewardSentToUser(address receiver, address token, uint rewardAmount);
event NotEnoughReward(address receiver, address token, uint rewardAmountToPay);
event BaseAmountChanged(uint oldValue, uint newValue);
//endregion ------------------------ Treasury, reward pool
//region ------------------------ EventLib
event EventResult(uint64 dungeonId, address heroToken, uint heroTokenId, uint8 stageId, IStatController.ActionInternalInfo gen, uint iteration);
//endregion ------------------------ EventLib
//region ------------------------ Item controller and helper contracts
event ItemRegistered(address item, IItemController.RegisterItemParams info);
event OtherItemRegistered(address item, IItemController.ItemMeta meta, bytes packedItemMetaData);
event ItemRemoved(address item);
event OtherItemRemoved(address item);
event NewItemMinted(address item, uint itemId, IItemController.MintInfo info);
event Equipped(address item, uint itemId, address heroToken, uint heroTokenId, uint8 itemSlot);
event TakenOff(address item, uint itemId, address heroToken, uint heroTokenId, uint8 itemSlot, address destination);
event ItemRepaired(address item, uint itemId, uint consumedItemId, uint16 baseDurability);
event FailedToRepairItem(address item, uint itemId, uint consumedItemId, uint16 itemDurability);
event Augmented(address item, uint itemId, uint consumedItemId, uint8 augLevel, IItemController.AugmentInfo info);
event NotAugmented(address item, uint itemId, uint consumedItemId, uint8 augLevel);
event ReduceDurability(address item, uint itemId, uint newDurability);
event Used(address item, uint tokenId, address heroToken, uint heroTokenId);
event Destroyed(address item, uint itemId);
event FragilityReduced(address item, uint itemId, address consumedItem, uint consumedItemId, uint fragility);
event ItemControllerHelper(address helper);
event SetUnionConfig(uint configId, address[] items, uint[] count, address itemToMint);
event RemoveUnionConfig(uint configId);
event SetUnionKeyPass(address keyPassItem);
event CombineItems(address msgSender, uint configId, address[] items, uint[][] itemIds, address mintedItem, uint mintedItemId);
//endregion ------------------------ Item controller and helper contracts
//region ------------------------ NFT and GameToken (only custom events, not ERC20/721 standards)
event ChangePauseStatus(bool value);
event MinterChanged(address value);
event UniqueUriChanged(uint id, string uri);
event BaseUriChanged(string uri);
event HeroMinted(uint heroId);
event HeroBurned(uint heroId);
event HeroUriByStatusChanged(string uri, uint statusLvl);
event ItemMinted(uint tokenId);
event ItemBurned(uint tokenId);
event UriByRarityChanged(string uri, uint rarity);
event SponsoredHeroCreated(address msgSender, address heroAddress, uint heroId, string heroName);
//endregion ------------------------ NFT and GameToken (only custom events, not ERC20/721 standards)
//region ------------------------ User controller
event SetUserName(address user, string name);
event SetUserAvatar(address user, string avatar);
event LootBoxOpened(address user, uint lootBoxKind, address[] itemTokens, uint[] itemTokenIds);
event LootBoxConfigChanged(uint lootBoxKind, address[] mintItems, uint32[] mintItemsChances, uint maxDropItems);
event SetFeeRenaming(uint feeRenaming);
event ActivityCompleted(address user, bool daily, bool weekly);
event FameHallHeroRegistered(address hero, uint heroId, address heroOwner, uint8 openedNgLevel);
//endregion ------------------------ User controller
//region ------------------------ Guild
event GuildCreated(address owner, uint guildId, string name, string urlLogo);
event AddToGuild(uint guildId, address newUser);
event ChangeGuildRights(uint guildId, address user, uint rights);
event RemoveFromGuild(uint guildId, address user);
event GuildDeleted(uint guildId);
event GuildLevelUp(uint guildId, uint8 newLevel);
event GuildRename(uint guildId, string newName);
event GuildLogoChanged(uint guildId, string newLogoUrl);
event GuildDescriptionChanged(uint guildId, string newDescription);
event SetGuildRelation(uint guildId1, uint guildId2, bool peace);
event TransferFromGuildBank(address user, address token, uint amount, address recipient);
event TransferNftFromGuildBank(address user, address[] nfts, uint[] tokenIds, address recipient);
event GuildBankDeployed(uint guildId, address guildBank);
event SetToHelperRatio(uint guildId, uint8 value, address user);
event TopUpGuildBank(address msgSender, uint guildId, address guildBank, uint amount);
event GuildRequestRegistered(address msgSender, uint guildId, string userMessage, uint depositAmount);
event GuildRequestStatusChanged(address msgSender, uint guildRequestId, uint8 newStatus, address user);
event SetToHelperRatio(uint guildId, address msgSender, uint8 toHelperRatio);
event SetGuildRequestDepositAmount(uint guildId, address msgSender, uint amount);
event SetGuildBaseFee(uint fee);
event SetPvpPointsCapacity(address msgSender, uint64 capacityPvpPoints, address[] users);
event SetShelterController(address shelterController);
event SetShelterAuction(address shelterAuction);
event PayForBidFromGuildBank(uint guildId, uint amount, uint bid);
//endregion ------------------------ Guild
//region ------------------------ Guild shelter
event RegisterShelter(uint sheleterId, uint price);
event SetShelterItems(
uint shelterId,
address[] items,
uint64[] pricesInPvpPoints,
uint128[] pricesInGameTokens,
uint16[] maxItemsPerDayThresholds
);
event RemoveShelterItems(uint shelterId, address[] items);
event BuyShelter(uint guidlId, uint shelterId);
event LeaveShelter(uint guildId, uint shelterId);
event NewShelterBid(uint shelterId, uint buyerGuildId, uint amount);
event RevokeShelterBid(uint shelterId);
event UseShelterBid(uint shelterId, uint sellerGuildId, uint buyerGuidId, uint amount);
event PurchaseShelterItem(address msgSender, address item, uint numSoldItems, uint priceInPvpPoints, uint priceInGameToken);
event ChangeShelterOwner(uint shelterId, uint fromGuildId, uint toGuildId);
event RestInShelter(address msgSender, address heroToken, uint heroTokenId);
//endregion ------------------------ Guild shelter
//region ------------------------ Guild reinforcement
event GuildHeroStaked(address heroToken, uint heroId, uint guildId);
event GuildHeroWithdrawn(address heroToken, uint heroId, uint guildId);
event GuildHeroAsked(address heroToken, uint heroId, uint guildId, address user);
/// @param user Address can be 0 if heroId was already burnt at the moment of reinforcement releasing
event GuildHeroReleased(address heroToken, uint heroId, uint guildId, address user);
//endregion ------------------------ Guild reinforcement
//region ------------------------ Guild auction
event AuctionPositionOpened(uint positionId, uint shelterId, uint sellerGuildId, address msgSender, uint minAuctionPrice);
event AuctionPositionClosed(uint positionId, address msgSender);
event AuctionBidOpened(uint bidId, uint positionId, uint amount, address msgSender);
//endregion ------------------------ Guild auction
//region ------------------------ Guild bank
event GuildBankTransfer(address token, address recipient, uint amount);
event GuildBankTransferNft(address to, address nft, uint tokenId);
event GuildBankTransferNftMulti(address to, address[] nfts, uint[] tokenIds);
//endregion ------------------------ Guild bank
//region ------------------------ Pawnshop
event PawnShopRouterDeployed(address pawnShop, address gameToken, address routerOwner, address deployed);
event PawnShopRouterTransfer(address token, uint amount, address receiver);
event PawnShopRouterBulkSell(address[] nfts, uint[] nftIds, uint[] prices, address nftOwner, uint[] positionIds);
event PawnShopRouterClosePositions(uint[] positionIds, address receiver);
event PawnShopRouterBulkBuy(uint[] positionIds, address receiver);
//endregion ------------------------ Pawnshop
}// 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 rewardsPool() external view returns (address);
function gameTokenPrice() external view returns (uint);
function process(address token, uint amount, address from) external;
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../openzeppelin/EnumerableSet.sol";
import "../openzeppelin/EnumerableMap.sol";
interface IDungeonFactory {
/// @custom:storage-location erc7201:dungeon.factory.main
struct MainState {
/// @dev biome => dungeonLaunchedId
mapping(uint => EnumerableSet.UintSet) freeDungeons;
/// @dev hero + heroId + biome (packMapObject) -> completed
mapping(bytes32 => bool) bossCompleted;
/// @dev hero + heroId + dungNum (packDungeonKey) -> completed
mapping(bytes32 => bool) specificDungeonCompleted;
/// @notice Max biome completed by the hero
/// @dev hero + heroId (nftPacked) -> max biome completed
mapping(bytes32 => uint8) maxBiomeCompleted;
/// @notice which dungeon the hero is currently in
/// @dev hero+id => current DungeonId
mapping(bytes32 => uint64) heroCurrentDungeon;
// ---
/// @notice Specific dungeon for the given pair of hero level + hero class
/// ALl specific dungeons are listed also in allSpecificDungeons
/// @dev packUint8Array(specReqBiome, specReqHeroClass) => dungNum
mapping(bytes32 => uint16) dungeonSpecific;
/// @dev contains all specific dungNum for easy management
EnumerableSet.UintSet allSpecificDungeons;
/// @dev biome => dungNum
mapping(uint8 => EnumerableSet.UintSet) dungeonsLogicByBiome;
// ---
/// @dev max available biome. auto-increment with new dung deploy
uint8 maxBiome;
/// @notice Address of treasure token => min hero level required
/// @dev manual threshold for treasury
mapping(address => uint) minLevelForTreasury;
/// @notice Contains arrays for SKILL_1, SKILL_2, SKILL_3 with 0 or 1
/// i.e. [0, 1, 0] means that durability of SKILL_2 should be reduced
/// @dev hero + heroId => uint8[] array where idx = slotNum
mapping(bytes32 => bytes32) skillSlotsForDurabilityReduction;
/// @notice Counter of dungeons, it's incremented on launch of a new dungeon
uint64 dungeonCounter;
/// @dev dungNum = init attributes
mapping(uint16 => DungeonAttributes) dungeonAttributes;
/// @dev dungeonId => status
mapping(uint64 => DungeonStatus) dungeonStatuses;
}
struct ObjectGenerateInfo {
/// @notice List of chamber types for each unique object
/// @dev uint8 types, packed using PackingLib.packUint8Array
bytes32[] objTypesByStages;
/// @notice List of chances for each chamber type
/// @dev uint64 chances
uint32[][] objChancesByStages;
}
struct DungeonGenerateInfo {
/// @notice List of chamber types for each unique object
uint8[][] objTypesByStages;
/// @notice List of chances for each chamber type
uint32[][] objChancesByStages;
uint32[] uniqObjects;
uint8 minLevel;
uint8 maxLevel;
bytes32[] requiredCustomDataIndex;
uint64[] requiredCustomDataMinValue;
uint64[] requiredCustomDataMaxValue;
bool[] requiredCustomDataIsHero;
}
/// @notice Attributes of the given dungeon logic
struct DungeonAttributes {
/// @notice Total number of stages that should be passed to complete the dungeon
uint8 stages;
uint8 biome;
/// @notice Default list of objects that should be passed in the dungeon
uint32[] uniqObjects;
/// @dev min+max (packUint8Array)
bytes32 minMaxLevel;
bytes32[] requiredCustomDataIndex;
/// @notice Packed DungeonGenerateInfo.requiredCustomData: MinValue, MaxValue, IsHero
/// @dev min+max+isHero(packStoryCustomDataRequirements)
bytes32[] requiredCustomDataValue;
ObjectGenerateInfo info;
}
/// @notice Current status of the given dungeon
struct DungeonStatus {
uint64 dungeonId;
/// @notice Dungeon logic id
uint16 dungNum;
/// @notice True if the dungeon is completed by the hero
bool isCompleted;
/// @notice Hero in the dungeon or 0
address heroToken;
uint heroTokenId;
/// @notice Current object that should be passed by the hero. 0 - new object is not opened
uint32 currentObject;
/// @notice Current stage in the dungeon that should be passed by the hero.
uint8 currentStage;
EnumerableMap.AddressToUintMap treasuryTokens;
/// @notice All items that were minted on result of made actions
bytes32[] treasuryItems;
/// @notice Total number of stages that should be passed to complete the dungeon
/// This value can be bigger than length of uniqObjects
uint8 stages;
/// @notice List of objects to be passed in the stage. The list can be dynamically changed during passing the stages
uint32[] uniqObjects;
}
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
function launchForNewHero(address heroToken, uint heroTokenId, address owner) external returns (uint64 dungeonId);
function maxBiomeCompleted(address heroToken, uint heroTokenId) external view returns (uint8);
function currentDungeon(address heroToken, uint heroTokenId) external view returns (uint64);
function skillSlotsForDurabilityReduction(address heroToken, uint heroTokenId) external view returns (uint8[] memory result);
function setBossCompleted(uint32 objectId, address heroToken, uint heroTokenId, uint8 heroBiome) external;
/// @notice Hero exists current dungeon forcibly same as when dying but without loosing life chance
function exitForcibly(address heroToken, uint heroTokenId, address msgSender) external;
function maxAvailableBiome() external view returns (uint8);
function reborn(address hero, uint heroId) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool _approved) external;
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)
pragma solidity ^0.8.0;
import "./IERC721.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Enumerable is IERC721 {
/**
* @dev Returns the total amount of tokens stored by the contract.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns a token ID owned by `owner` at a given `index` of its token list.
* Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
*/
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
/**
* @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
* Use along with {totalSupply} to enumerate all tokens.
*/
function tokenByIndex(uint256 index) external view returns (uint256);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "./IStatController.sol";
import "./IItemController.sol";
interface IFightCalculator {
enum AttackType {
UNKNOWN, // 0
MELEE, // 1
MAGIC, // 2
SLOT_3,
SLOT_4,
SLOT_5,
SLOT_6,
SLOT_7,
SLOT_8,
SLOT_9,
SLOT_10
}
/// @notice Attacker info: suitable both for hero and monsters
struct AttackInfo {
/// @notice Type of the attack
/// by default, if attack token presents, it's magic attack and not-magic otherwise
/// but this logic can become more complicated after introducing new attack types
AttackType attackType;
/// @notice NFT selected by hero for attack, it should be equip on.
/// If attacker is a monster, this is a special case (stub NFT with zero ID is used)
address attackToken;
uint attackTokenId;
address[] skillTokens;
uint[] skillTokenIds;
}
struct FighterInfo {
int32[] fighterAttributes;
IStatController.ChangeableStats fighterStats;
AttackType attackType;
address attackToken;
uint attackTokenId;
uint race;
}
struct Statuses {
bool stun;
bool burn;
bool freeze;
bool confuse;
bool curse;
bool poison;
bool gotCriticalHit;
bool missed;
bool hitBlocked;
}
struct FightResult {
int32 healthA;
int32 healthB;
int32 manaConsumedA;
int32 manaConsumedB;
}
struct FightCall {
FighterInfo fighterA;
FighterInfo fighterB;
uint64 dungeonId;
uint32 objectId;
address heroAdr;
uint heroId;
uint8 stageId;
uint iteration;
uint8 turn;
}
struct SkillSlots {
bool slot1;
bool slot2;
bool slot3;
}
//region ------------------------ FightLib-internal (FightInfoInternal is required by IApplicationEvents..)
struct FightInfoInternal {
Fighter fighterA;
Fighter fighterB;
}
struct Fighter {
IFightCalculator.FighterInfo info;
IItemController.AttackInfo magicAttack;
int32 health;
int32 manaConsumed;
int32 damage;
int32 damagePoison;
int32 damageReflect;
IFightCalculator.Statuses statuses;
}
//endregion ------------------------ FightLib-internal
function fight(FightCall memory callData) external returns (FightResult memory);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "./IERC20.sol";
interface IGameToken is IERC20 {
function minter() external view returns (address);
function mint(address account, uint amount) external returns (bool);
function burn(uint amount) external returns (bool);
function setMinter(address minter_) external;
function pause(bool value) external;
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../openzeppelin/EnumerableSet.sol";
import "./IController.sol";
interface IGOC {
enum ObjectType {
UNKNOWN, // 0
EVENT, // 1
MONSTER, // 2
STORY, // 3
END_SLOT
}
enum ObjectSubType {
UNKNOWN_0, // 0
ENEMY_NPC_1, // 1
ENEMY_NPC_SUPER_RARE_2, // 2
BOSS_3, // 3
SHRINE_4, // 4
CHEST_5, // 5
STORY_6, // 6
STORY_UNIQUE_7, // 7
SHRINE_UNIQUE_8, // 8
CHEST_UNIQUE_9, // 9
ENEMY_NPC_UNIQUE_10, // 10
STORY_ON_ROAD_11, // 11
STORY_UNDERGROUND_12, // 12
STORY_NIGHT_CAMP_13, // 13
STORY_MOUNTAIN_14, // 14
STORY_WATER_15, // 15
STORY_CASTLE_16, // 16
STORY_HELL_17, // 17
STORY_SPACE_18, // 18
STORY_WOOD_19, // 19
STORY_CATACOMBS_20, // 20
STORY_BAD_HOUSE_21, // 21
STORY_GOOD_TOWN_22, // 22
STORY_BAD_TOWN_23, // 23
STORY_BANDIT_CAMP_24, // 24
STORY_BEAST_LAIR_25, // 25
STORY_PRISON_26, // 26
STORY_SWAMP_27, // 27
STORY_INSIDE_28, // 28
STORY_OUTSIDE_29, // 29
STORY_INSIDE_RARE_30,
STORY_OUTSIDE_RARE_31,
ENEMY_NPC_INSIDE_32,
ENEMY_NPC_INSIDE_RARE_33,
ENEMY_NPC_OUTSIDE_34,
ENEMY_NPC_OUTSIDE_RARE_35,
END_SLOT
}
/// @custom:storage-location erc7201:game.object.controller.main
struct MainState {
/// @dev objId = biome(00) type(00) id(0000) => biome(uint8) + objType(uint8)
/// Id is id of the event, story or monster.
mapping(uint32 => bytes32) objectMeta;
/// @dev biome(uint8) + objType(uint8) => set of object id
mapping(bytes32 => EnumerableSet.UintSet) objectIds;
/// @dev heroAdr180 + heroId64 + cType8 + biome8 => set of already played objects. Should be cleared periodically
mapping(bytes32 => EnumerableSet.UintSet) playedObjects;
/// @dev HeroAdr(160) + heroId(uint64) + objId(uint32) => iteration count. It needs for properly emit events for every new entrance.
mapping(bytes32 => uint) iterations;
/// @dev objId(uint32) => EventInfo
mapping(uint32 => EventInfo) eventInfos;
/// @dev objId(uint32) => storyId
mapping(uint32 => uint16) storyIds;
/// @dev objId(uint32) => MonsterInfo
mapping(uint32 => MonsterInfo) monsterInfos;
/// @dev hero+id => last fight action timestamp
mapping(bytes32 => uint) lastHeroFightTs;
/// @dev delay for user actions in fight (suppose to prevent bot actions)
uint fightDelay;
}
struct ActionResult {
bool kill;
bool completed;
address heroToken;
address[] mintItems;
int32 heal;
int32 manaRegen;
int32 lifeChancesRecovered;
int32 damage;
int32 manaConsumed;
uint32 objectId;
uint32 experience;
uint heroTokenId;
uint iteration;
uint32[] rewriteNextObject;
}
struct EventInfo {
/// @dev chance to use good or bad attributes/stats
uint32 goodChance;
/// @dev toBytes32ArrayWithIds
bytes32[] goodAttributes;
bytes32[] badAttributes;
/// @dev experience(uint32) + heal(int32) + manaRegen(int32) + lifeChancesRecovered(int32) + damage(int32) + manaConsume(int32) packStatsChange
bytes32 statsChange;
/// @dev item+chance packItemMintInfo
bytes32[] mintItems;
}
struct MonsterInfo {
/// @dev toBytes32ArrayWithIds
bytes32[] attributes;
/// @dev level(uint8) + race(uint8) + experience(uint32) + maxDropItems(uint8) packMonsterStats
bytes32 stats;
/// @dev attackToken(160) + attackTokenId(uint64) + attackType(uint8) packAttackInfo
bytes32 attackInfo;
/// @dev item+chance packItemMintInfo
bytes32[] mintItems;
/// @dev heroAdr(160) + heroId(uint64) => iteration => GeneratedMonster packed
mapping(bytes32 => mapping(uint => bytes32)) _generatedMonsters;
}
struct MultiplierInfo {
uint8 biome;
/// @notice NG_LEVEL of the hero who is going to fight with the given monster
/// Use type(uint8).max for !NG+
uint8 heroNgLevel;
}
struct GeneratedMonster {
bool generated;
uint8 turnCounter;
int32 hp;
uint32 amplifier;
}
struct MonsterGenInfo {
uint16 monsterId;
uint8 biome;
ObjectSubType subType;
uint8[] attributeIds;
int32[] attributeValues;
uint8 level;
uint8 race;
uint32 experience;
uint8 maxDropItems;
address attackToken;
uint64 attackTokenId;
uint8 attackType;
address[] mintItems;
uint32[] mintItemsChances;
}
struct ActionContext {
address sender;
address heroToken;
IController controller;
uint8 biome;
uint8 objectSubType;
uint8 stageId;
uint8 heroNgLevel;
uint32 objectId;
uint64 dungeonId;
uint heroTokenId;
uint salt;
uint iteration;
bytes data;
}
struct EventRegInfo {
uint8 biome;
uint16 eventId;
ObjectSubType subType;
uint32 goodChance;
AttributeGenerateInfo goodAttributes;
AttributeGenerateInfo badAttributes;
uint32 experience;
int32 heal;
int32 manaRegen;
int32 lifeChancesRecovered;
int32 damage;
int32 manaConsumed;
address[] mintItems;
uint32[] mintItemsChances;
}
struct AttributeGenerateInfo {
uint8[] ids;
int32[] values;
}
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
/// @dev represent object registration if non zero values
function getObjectMeta(uint32 objectId) external view returns (uint8 biome, uint8 objectSubType);
function isBattleObject(uint32 objectId) external view returns (bool);
function getRandomObject(
uint8[] memory cTypes,
uint32[] memory chances,
uint8 biomeLevel,
address heroToken,
uint heroTokenId
) external returns (uint32 objectId);
function open(address heroToken, uint heroTokenId, uint32 objectId) external returns (uint iteration);
function action(
address sender,
uint64 dungeonId,
uint32 objectId,
address heroToken,
uint heroTokenId,
uint8 stageId,
bytes memory data
) external returns (ActionResult memory);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../interfaces/IAppErrors.sol";
import "../interfaces/IGuildController.sol";
import "../interfaces/IERC20.sol";
import "../interfaces/IERC721.sol";
interface IGuildBank {
function transfer(address token, address recipient, uint amount) external;
function approve(address token, address spender, uint256 amount) external returns (bool);
function transferNft(address to, address nft, uint256 tokenId) external;
function transferNftMulti(address to, address[] memory nfts, uint256[] memory tokenIds) external;
function approveNft(address to, address nft, uint256 tokenId) external;
function approveNftMulti(address to, address[] memory nfts, uint256[] memory tokenIds) external;
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../openzeppelin/EnumerableSet.sol";
interface IGuildController {
enum GuildRightBits {
ADMIN_0,
RENAME_1,
CHANGE_LOGO_2,
CHANGE_SHELTER_3,
ADD_MEMBER_4,
REMOVE_MEMBER_5,
BANK_TOKENS_OPERATION_6,
CHANGE_ROLES_7,
LEVEL_UP_8,
SET_RELATION_KIND_9,
BANK_ITEMS_OPERATION_10,
SET_GUILD_PARAMS_11,
CHANGE_PURCHASING_SHELTER_ITEMS_CAPACITY_12
}
enum GuildsParams {
NONE_0,
COUNTER_GUILD_IDS_1,
BASE_FEE_2,
COUNTER_GUILD_REQUESTS_3,
REENTRANT_STATUS_4,
SHELTER_CONTROLLER_5,
SHELTER_AUCTION_6
}
enum GuildRequestStatus {
NONE_0,
ACCEPTED_1,
REJECTED_2,
CANCELED_3
}
/// @custom:storage-location erc7201:guild.controller.main
struct MainState {
/// @notice Mapping to store various guilds params (with global values for all guilds)
mapping(GuildsParams param => uint value) guildsParam;
/// @notice guildId => address of instance of GuildBank contract
mapping(uint guildId => address) guildBanks;
/// @notice guild id => guild data (owner, name, logo, etc)
mapping(uint guildId => GuildData) guildData;
/// @notice name => guild id
mapping(string guildName => uint guildId) nameToGuild;
/// @notice EOA => guild id, EOA can be a member of a single guild only
mapping(address member => uint guildId) memberToGuild;
/// @notice List of participants of guilds
/// @dev Allowed number of members is 20 + 5 * guildLevel
mapping(uint guildId => EnumerableSet.AddressSet listEoa) members;
/// @notice Rights of the member in the guild, mask of GuildRightBits
mapping(address member => uint maskRights) rights;
/// @notice _getGuildsPairKey(guild1, guild2) => status (false - war, true - peace)
mapping(bytes32 guildsPairKey => bool) relationsPeaceful;
// ---------------------------- Request to join to the guild
/// @notice Full list of requests registered for the guild
mapping(uint guildId => mapping(GuildRequestStatus status => EnumerableSet.UintSet guildRequestIds)) guildRequests;
/// @notice List of active requests created by the given user.
/// "Active" => deposit should be returned to the user.
/// All not-active requests are removed from here automatically.
mapping(address user => EnumerableSet.UintSet guildRequestIds) userActiveGuildRequests;
/// @notice Data of all guild requests ever created
mapping(uint guildRequestId => GuildRequestData) guildRequestData;
/// @notice Deposit amount required to create a guild request
mapping(uint guildId => GuildRequestDeposit) guildRequestDepositAmounts;
/// @notice Counter of spent pvp points + number of guild pvp-points allowed to be used by the guild member
mapping(uint guildId => mapping(address member => UserPvpPoints)) userPvpPoints;
/// @notice guild id => guildDescription
mapping(uint guildId => string) guildDescription;
}
struct GuildData {
/// @notice Not empty unique guild name
string guildName;
/// @notice URL of guild logo (empty is allowed)
string urlLogo;
/// @notice Creator (owner) of the guild
address owner;
/// @notice Guild level [1...10]
uint8 guildLevel;
/// @notice Percent of guild reinforcement fee Value in range [_FEE_MIN ... _TO_HELPER_RATIO_MAX], i.e. [10..50]
uint8 toHelperRatio;
/// @notice Global guild points counter, it's incremented on each victory in php-fight.
/// @dev Assume here, that uint64 is enough to store any sums of scores
uint64 pvpCounter;
}
struct GuildRequestData {
GuildRequestStatus status;
/// @notice Creator of the guild request that asks to include him to the guild
address user;
/// @notice Message to the guild owner from the user
string userMessage;
uint guildId;
}
struct GuildRequestDeposit {
bool initialized;
uint192 amount;
}
struct UserPvpPoints {
/// @notice How many guild pvp-points the user is allowed to use
uint64 capacityPvpPoints;
/// @notice How many guild pvp-points the user has used
uint64 spentPvpPoints;
}
/// ----------------------------------------------------------------------------------------------
function memberOf(address user) external view returns (uint guildId);
function guildToShelter(uint guildId) external view returns (uint shelterId);
function getGuildData(uint guildId) external view returns (
string memory guildName,
string memory urlLogo,
address owner,
uint8 guildLevel,
uint64 pvpCounter,
uint toHelperRatio
);
function getRights(address user) external view returns (uint);
function getGuildBank(uint guildId) external view returns (address);
function shelterController() external view returns (address);
function usePvpPoints(uint guildId, address user, uint64 priceInPvpPoints) external;
function payFromGuildBank(uint guildId, uint shelterPrice) external;
function payFromBalance(uint amount, address user) external;
/// @notice Ensure that the {user} has given {right}, revert otherwise
function checkPermissions(address user, uint right) external view returns (uint guildId, uint rights);
function shelterAuctionController() external view returns (address);
function payForAuctionBid(uint guildId, uint amount, uint bid) external;
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../openzeppelin/EnumerableSet.sol";
import "../openzeppelin/EnumerableMap.sol";
interface IHeroController {
/// @custom:storage-location erc7201:hero.controller.main
struct MainState {
/// @dev A central place for all hero tokens
/// @dev Deprecated. Controller is used instead.
address heroTokensVault;
/// @dev heroAdr => packed tokenAdr160+ amount96
mapping(address => bytes32) payToken;
/// @dev heroAdr => heroCls8
mapping(address => uint8) heroClass;
// ---
/// @dev hero+id => individual hero name
mapping(bytes32 => string) heroName;
/// @dev name => hero+id, needs for checking uniq names
mapping(string => bytes32) nameToHero;
// ---
/// @dev hero+id => biome
mapping(bytes32 => uint8) heroBiome;
/// @notice Exist reinforcement of any kind for the given hero
/// @dev hero+id => packed reinforcement helper+id
mapping(bytes32 => bytes32) reinforcementHero;
/// @dev hero+id => reinforcement packed attributes
mapping(bytes32 => bytes32[]) reinforcementHeroAttributes;
/// @notice packedHero (hero + id) => count of calls of beforeTokenTransfer
mapping(bytes32 => uint) countHeroTransfers;
// ------------------------------------ NG plus
/// @notice (tier, hero address) => TierInfo, where tier = [2, 3]
/// @dev For tier=1 no data is required. Amount for tier 1 is stored in {payToken}, no items are minted
/// Token from {payToken} is equal for all tiers
mapping(bytes32 packedTierHero => TierInfo) tiers;
mapping(bytes32 packedHero => HeroInfo) heroInfo;
/// @notice Max NG_LVL reached by the heroes of a given account
mapping(address user => uint8 maxNgLevel) maxUserNgLevel;
/// @notice When the hero has killed boss on the given biome first time
/// packedBiomeNgLevel = packed (biome, NG_LEVEL)
mapping(bytes32 packedHero => mapping (bytes32 packedBiomeNgLevel => uint timestamp)) killedBosses;
/// @notice Max NG_LEVEL reached by any user
uint maxOpenedNgLevel;
}
/// @notice Tier = hero creation cost option
/// There are 3 tiers:
/// 1: most chip option, just pay fixed amount {payTokens} - new hero is created
/// 2: pay bigger amount - random skill is equipped on the newly created hero
/// 3: pay even more amount - random sill + some random items are equipped on the newly created hero
struct TierInfo {
/// @notice Cost of the hero creation using the given tier in terms of the token stored in {payToken}
/// This amount is used for tiers 2, 3. For tier 1 the amount is taken from {payToken}
uint amount;
/// @notice All slots for which items-to-mint are registered in {itemsToMint}
EnumerableSet.UintSet slots;
/// @notice slot => items that can be minted and equipped on the hero to the given {slot} after hero creation
mapping(uint8 slot => address[] items) itemsToMint;
}
/// @notice Current NG+-related values
struct HeroInfo {
/// @notice Hero tier = [0..3]. 0 - the hero is post-paid, it can be changed by upgrading the hero to pre-paid
uint8 tier;
/// @notice NG_LVL of the hero
uint8 ngLevel;
/// @notice True if hero has passed last biome on current NG+ and so NG_LEVEL can be incremented (reborn is allowed)
bool rebornAllowed;
/// @notice Amount paid for the hero on creation OR on upgrade to NG+
/// Amount paid for creation of the hero in terms of game token (!NG+) is NOT stored here.
/// @dev uint72 is used here to pack the whole struct to single slot
uint72 paidAmount;
/// @notice Pay token used to pay {paidAmount}
address paidToken;
}
/// @notice Input data to create new hero
struct HeroCreationData {
/// @notice Desired NG_LVL of the hero
uint8 ngLevel;
/// @notice Desired tire of the newly created hero. Allowed values: [1..3]
uint8 tier;
/// @notice Enter to the dungeon after creation
bool enter;
/// @notice Desired hero name
string heroName;
/// @notice Optional: user account for which the hero is created
address targetUserAccount;
/// @notice Optional: ref-code to be passed to the hero-creation-related event
string refCode;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function heroClass(address hero) external view returns (uint8);
function heroBiome(address hero, uint heroId) external view returns (uint8);
function payTokenInfo(address hero) external view returns (address token, uint amount);
function heroReinforcementHelp(address hero, uint heroId) external view returns (address helperHeroToken, uint helperHeroId);
function score(address hero, uint heroId) external view returns (uint);
function isAllowedToTransfer(address hero, uint heroId) external view returns (bool);
function beforeTokenTransfer(address hero, uint heroId) external returns (bool);
// ---
function create(address hero, string memory heroName_, bool enter) external returns (uint);
function kill(address hero, uint heroId) external returns (bytes32[] memory dropItems);
/// @notice Take off all items from the hero, reduce life to 1. The hero is NOT burnt.
/// Optionally reduce mana to zero and/or decrease life chance.
function softKill(address hero, uint heroId, bool decLifeChances, bool resetMana) external returns (bytes32[] memory dropItems);
function releaseReinforcement(address hero, uint heroId) external returns (address helperToken, uint helperId);
function resetLifeAndMana(address hero, uint heroId) external;
function countHeroTransfers(address hero, uint heroId) external view returns (uint);
function askGuildReinforcement(address hero, uint heroId, address helper, uint helperId) external;
function getHeroInfo(address hero, uint heroId) external view returns (IHeroController.HeroInfo memory data);
function registerKilledBoss(address hero, uint heroId, uint32 objectId) external;
function maxOpenedNgLevel() external view returns (uint);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
interface IItem {
function isItem() external pure returns (bool);
function mintFor(address recipient) external returns (uint tokenId);
function burn(uint tokenId) external;
function controlledTransfer(address from, address to, uint tokenId) 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;
}
struct RegisterItemParams {
ItemMeta itemMeta;
address augmentToken;
uint augmentAmount;
ItemGenerateInfo commonAttributes;
IGOC.AttributeGenerateInfo consumableAttributes;
IStatController.ChangeableStats consumableStats;
ItemGenerateInfo casterAttributes;
ItemGenerateInfo targetAttributes;
AttackInfo genAttackInfo;
/// @notice Bit mask of ConsumableActionBits
uint consumableActionMask;
}
/// @notice Possible actions that can be triggered by using the consumable item
enum ConsumableActionBits {
CLEAR_TEMPORARY_ATTRIBUTES_0
// other items are used instead this mask
}
struct ItemGenerateInfo {
/// @notice Attribute ids
uint8[] ids;
/// @notice Min value of the attribute, != 0
int32[] mins;
/// @notice Max value of the attribute, != 0
int32[] maxs;
/// @notice Chance of the selection [0..MAX_CHANCES]
uint32[] chances;
}
struct ItemMeta {
uint8 itemMetaType;
// Level in range 1-99. Reducing durability in low level dungeons. lvl/5+1 = biome
uint8 itemLevel;
IItemController.ItemType itemType;
uint16 baseDurability;
uint8 defaultRarity;
uint32 manaCost;
// it doesn't include positions with 100% chance
uint8 minRandomAttributes;
uint8 maxRandomAttributes;
IStatController.CoreAttributes requirements;
}
// Deprecated. Todo - remove
enum FeeType {
UNKNOWN,
REPAIR,
AUGMENT,
STORY,
END_SLOT
}
enum ItemRarity {
UNKNOWN, // 0
NORMAL, // 1
MAGIC, // 2
RARE, // 3
SET, // 4
UNIQUE, // 5
END_SLOT
}
enum ItemType {
NO_SLOT, // 0
HEAD, // 1
BODY, // 2
GLOVES, // 3
BELT, // 4
AMULET, // 5
RING, // 6
OFF_HAND, // 7
BOOTS, // 8
ONE_HAND, // 9
TWO_HAND, // 10
SKILL, // 11
OTHER, // 12
END_SLOT
}
enum ItemMetaType {
UNKNOWN, // 0
COMMON, // 1
ATTACK, // 2
BUFF, // 3
CONSUMABLE, // 4
END_SLOT
}
enum AttackType {
UNKNOWN, // 0
FIRE, // 1
COLD, // 2
LIGHTNING, // 3
CHAOS, // 4
END_SLOT
}
struct AttackInfo {
AttackType aType;
int32 min;
int32 max;
// if not zero - activate attribute factor for the attribute
IStatController.CoreAttributes attributeFactors;
}
struct ItemInfo {
ItemRarity rarity;
uint8 augmentationLevel;
uint16 durability;
}
/// @dev The struct is used in events, so it's moved here from the lib
struct MintInfo {
IItemController.ItemMeta meta;
uint8[] attributesIds;
int32[] attributesValues;
IItemController.ItemRarity itemRarity;
IItemController.AttackInfo attackInfo;
uint8[] casterIds;
int32[] casterValues;
uint8[] targetIds;
int32[] targetValues;
}
/// @dev The struct is used in events, so it's moved here from the lib
struct AugmentInfo {
uint8[] attributesIds;
int32[] attributesValues;
IItemController.AttackInfo attackInfo;
uint8[] casterIds;
int32[] casterValues;
uint8[] targetIds;
int32[] targetValues;
}
///region ------------------------ Item type "Other"
/// @notice Possible kinds of "Other" items
/// Each "Other" item has each own structure for metadata, see OtherItemXXX
enum OtherSubtypeKind {
UNKNOWN_0,
/// @notice Item to reduce fragility, see SCB-1014. Metadata is {OtherItemReduceFragility}
REDUCE_FRAGILITY_1,
/// @notice This item allows asking guild reinforcement to the guild member
USE_GUILD_REINFORCEMENT_2,
/// @notice Exit from dungeon (shelter of level 3 is required)
EXIT_FROM_DUNGEON_3,
/// @notice Rest in the shelter: restore of hp & mp, clear temporally attributes, clear used consumables (shelter of level 3 is required)
REST_IN_SHELTER_4,
/// @notice Stub item (i.e. OTHER_4) that has no logic in contracts, but it has correct (not empty) packedMetaData
EMPTY_NO_LOGIC_5,
END_SLOT
}
struct OtherItemReduceFragility {
/// @notice "Other" item kind. It MUST BE first field in the struct.
uint8 kind;
/// @notice Value on which the fragility will be reduced.
/// @dev [0...100%], decimals 3, so the value is in the range [0...10_000]
uint248 value;
}
///endregion ------------------------ Item type "Other"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function itemMeta(address item) external view returns (ItemMeta memory meta);
function augmentInfo(address item) external view returns (address token, uint amount);
function genAttributeInfo(address item) external view returns (ItemGenerateInfo memory info);
function genCasterAttributeInfo(address item) external view returns (ItemGenerateInfo memory info);
function genTargetAttributeInfo(address item) external view returns (ItemGenerateInfo memory info);
function genAttackInfo(address item) external view returns (AttackInfo memory info);
function itemInfo(address item, uint itemId) external view returns (ItemInfo memory info);
function equippedOn(address item, uint itemId) external view returns (address hero, uint heroId);
function itemAttributes(address item, uint itemId) external view returns (int32[] memory values, uint8[] memory ids);
function consumableAttributes(address item) external view returns (int32[] memory values, uint8[] memory ids);
function consumableStats(address item) external view returns (IStatController.ChangeableStats memory stats);
function casterAttributes(address item, uint itemId) external view returns (int32[] memory values, uint8[] memory ids);
function targetAttributes(address item, uint itemId) external view returns (int32[] memory values, uint8[] memory ids);
function itemAttackInfo(address item, uint itemId) external view returns (AttackInfo memory info);
function score(address item, uint tokenId) external view returns (uint);
function isAllowedToTransfer(address item, uint tokenId) external view returns (bool);
// ---
function mint(address item, address recipient) external returns (uint itemId);
function reduceDurability(address hero, uint heroId, uint8 biome, bool reduceDurabilityAllSkills) external;
function destroy(address item, uint tokenId) external;
function takeOffDirectly(
address item,
uint itemId,
address hero,
uint heroId,
uint8 itemSlot,
address destination,
bool broken
) external;
/// @notice SIP-003: item fragility counter that displays the chance of an unsuccessful repair.
/// @dev [0...100%], decimals 3, so the value is in the range [0...10_000]
function itemFragility(address item, uint itemId) external view returns (uint);
/// @notice SIP-003: The quest mechanic that previously burned the item will increase its fragility by 1%
function incBrokenItemFragility(address item, uint itemId) external;
function equip(
address hero,
uint heroId,
address[] calldata items,
uint[] calldata itemIds,
uint8[] calldata itemSlots
) external;
}// 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 "./IStatController.sol";
import "../openzeppelin/EnumerableMap.sol";
/// @notice Terms
/// Reinforcement v1: helper is selected randomly in askHero, fixed part of rewards (tokens and NFT) is sent to the helper.
/// Guild reinforcement: helper is selected from guild heroes. Rewards are sent to guild bank.
/// Reinforcement v2: helper is selected manually in askHeroV2, helper receives fixed amount.
interface IReinforcementController {
enum ConfigParams {
/// @notice Packed MinMaxBoardV2
V2_MIN_MAX_BOARD_0
}
/// @custom:storage-location erc7201:reinforcement.controller.main
struct MainState {
// ------------------------ Reinforcement v1
/// @dev minLvl8 + minLifeChances8
bytes32 config;
/// @dev hero token + hero id => heroInfo(biome8 + score128 + fee8 + stakeTs64)
mapping(bytes32 => bytes32) _stakedHeroes;
/// @dev biome => helperAdr+id
mapping(uint => EnumerableSet.Bytes32Set) _internalIdsByBiomes;
/// @dev biome => score // The field is deprecated and not updated any more
mapping(uint => uint) maxScore;
/// @dev heroAdr+id => itemAdr+id
mapping(bytes32 => bytes32[]) _heroNftRewards;
/// @dev heroAdr+id => tokenAdr and amount map
mapping(bytes32 => EnumerableMap.AddressToUintMap) _heroTokenRewards;
// ------------------------ Guild reinforcement
/// @notice All staked guild heroes for the given guild
/// @dev helper (hero token + hero id) => guild
mapping(bytes32 packedHero => uint guildId) stakedGuildHeroes;
/// @notice All guild heroes that are currently in use by guild reinforcement
/// It's allowed to withdraw a hero before reinforcement releasing,
/// so it's possible to have !0 in {guildBusyHelpers} and 0 in {stakedGuildHeroes} simultaneously.
/// @dev helper (hero token + hero id) => guildId (guild at the moment of askGuildReinforcement)
mapping(bytes32 packedHero => uint guildId) busyGuildHelpers;
/// @notice All (free and busy) staked guild heroes per guild.
/// guild => (packed helper => guild where the helper is busy currently)
/// @dev There is a chance that guilds are different here
/// i.e. hero can be:
/// 1) added to G1 2) staked in G1 3) asked for help 4) withdrawn 5) G1=>G2 6) staked in G2
/// In such case guildHelpers[G2][hero] = G1, guildHelpers[G1][hero] = 0
/// After releasing guildHelpers[G2][hero] = 0
mapping(uint guildId => EnumerableMap.Bytes32ToUintMap) guildHelpers;
/// @notice Moment of withdrawing the hero from staking. Next staking is possible in 1 day since withdrawing
mapping(bytes32 packedHero => uint lastWithdrawTimestamp) lastGuildHeroWithdrawTs;
// ------------------------ Reinforcement v2
/// @notice Map to store various config params
mapping(ConfigParams paramId => uint) configParams;
mapping(bytes32 packedHero => HeroInfoV2) stakedHeroesV2;
/// @notice biome => set of packedHero. All staked heroes (they can be busy of free currently)
mapping(uint biome => EnumerableSet.Bytes32Set) heroesByBiomeV2;
mapping(uint biome => LastWindowsV2) stat24hV2;
}
/// @notice Deprecated. Reinforcement v1
struct HeroInfo {
uint8 biome;
uint score; // stored in 128 but easy to use 256
/// @notice To helper ratio
uint8 fee;
uint64 stakeTs;
}
struct HeroInfoV2 {
uint8 biome;
uint64 stakeTs;
/// @notice Amount of game token that is paid to the helper at the moment of the call {askHeroV2}
uint128 rewardAmount;
}
/// @notice Statistic of askHeroV2 calls per last 24 hours at the moment of the last call
struct LastWindowsV2 {
/// @notice 24 hours are divided on 8 intervals, each interval is 3 hour
/// Current basket has index {basketIndex}
/// {baskets[current basket]} contains "old" value.
/// New value for the current basket is collected in {basketValue}.
/// The value for the current basket is calculated as weighted average of old and new values.
/// New value replaces the old value at the moment of changing current basket index.
uint24[8] baskets;
/// @notice New value (hits counter) for current basket
uint24 basketValue;
/// @notice Abs. index of the current basket (abs. hour / 3)
uint48 basketIndex;
}
/// @dev 1 slot
struct ConfigReinforcementV2 {
/// @notice if Number-of-askHeroV2-calls is below given value then burn fee has min value
uint32 minNumberHits;
/// @notice if Number-of-askHeroV2-calls is above given value then burn fee has max value
uint32 maxNumberHits;
/// @notice Lowest fee = amountForDungeon / given value, i.e. 100 => amountForDungeon/100 as lower fee
uint32 lowDivider;
/// @notice Highest fee = amountForDungeon / given value, i.e. 2 => amountForDungeon/2 as highest fee
uint32 highDivider;
/// @notice Limit for min level of the staked hero
/// In practice we need following limitation: (stats.level < 5 || (stats.level - 5) / 5 < biome)
/// so, levelLimit should be equal 5
/// In tests we need to be able to disable such limitation, so levelLimit = 0 allow to disable that constraint
uint8 levelLimit;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function toHelperRatio(address heroToken, uint heroId) external view returns (uint);
function isStaked(address heroToken, uint heroId) external view returns (bool);
function registerTokenReward(address heroToken, uint heroId, address token, uint amount) external;
function registerNftReward(address heroToken, uint heroId, address token, uint tokenId) external;
function askHeroV2(address hero, uint heroId, address helper, uint helperId) external returns (int32[] memory attributes);
function askGuildHero(address hero, uint heroId, address helper, uint helperId) external returns (int32[] memory attributes);
/// @notice Return the guild in which the hero is currently asked for guild reinforcement
function busyGuildHelperOf(address heroToken, uint heroId) external view returns (uint guildId);
function releaseGuildHero(address helperHeroToken, uint helperHeroTokenId) external;
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
interface IRewardsPool {
/// @custom:storage-location erc7201:rewards.pool.main
struct MainState {
mapping(address token => uint baseAmountValue) baseAmounts;
}
function balanceOfToken(address token) external view returns (uint);
function rewardAmount(address token, uint maxBiome, uint maxNgLevel, uint biome, uint heroNgLevel) external view returns (uint);
function sendReward(address token, uint rewardAmount_, address receiver) external;
function lostProfitPercent(uint maxBiome, uint maxNgLevel, uint heroNgLevel) external view returns (uint percent);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../openzeppelin/EnumerableSet.sol";
import "../openzeppelin/EnumerableMap.sol";
interface IShelterAuction {
enum ShelterAuctionParams {
NONE_0,
POSITION_COUNTER_1,
BID_COUNTER_2,
FEE_3
}
//region ------------------------ Data types
/// @custom:storage-location erc7201:shelter.auction.main
struct MainState {
/// @notice Mapping to store auction params (i.e. counters)
mapping(ShelterAuctionParams param => uint value) params;
/// @notice Hold all positions. Any record should not be removed
mapping(uint positionId => Position) positions;
/// @dev BidId => Bid. Hold all bids. Any record should not be removed
mapping(uint bidId => AuctionBid) auctionBids;
/// @notice List of currently opened positions
EnumerableSet.UintSet openPositions;
/// @notice Seller to position map
/// At any moment each guild can have only one opened position to sell
mapping(uint sellerGuildId => uint openedPositionId) sellerPosition;
/// @notice Position that the buyer is going to purchase.
/// At any moment each guild can have only one opened position to purchase
mapping(uint buyerGuildId => BuyerPositionData) buyerPosition;
/// @notice All open and close bids for the given position
mapping(uint positionId => uint[] bidIds) positionToBidIds;
/// @notice Timestamp of the last bid for the auction
mapping(uint positionId => uint timestamp) lastAuctionBidTs;
}
struct Position {
bool open;
/// @notice User that opens the position. The user belongs to the guild with id = {sellerGuildId}
address seller;
/// @notice Assume that shelter can be stored as uint64
uint64 shelterId;
uint128 positionId;
/// @notice Min allowed (initial) auction price. Only first bid is able to use it.
uint128 minAuctionPrice;
uint128 sellerGuildId;
}
struct AuctionBid {
bool open;
/// @notice User that opens the bid. The user belongs to the guild with id = {buyerGuildId}
address buyer;
uint128 bidId;
uint128 positionId;
/// @notice Bid amount in terms of game token. This amount is transferred from guild Bank to ShelterAuction balance
uint128 amount;
uint128 buyerGuildId;
}
struct BuyerPositionData {
/// @notice ID of the position that the buyer is going to purchase
uint128 positionId;
/// @notice 0-based index of the opened bid in {positionToBidIds}
uint128 bidIndex;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function positionBySeller(uint sellerGuildId_) external view returns (uint positionId);
function positionByBuyer(uint buyerGuildId) external view returns (uint positionId, uint bidIndex);
function posByShelter(uint shelterId_) external view returns (uint positionId);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../openzeppelin/EnumerableSet.sol";
interface IShelterController {
/// @custom:storage-location erc7201:shelter.controller.main
struct MainState {
/// @notice List of items allowed to be purchased in the shelter
mapping(uint shelterId => EnumerableSet.AddressSet) shelterItems;
/// @notice Data of items available for purchasing in the given shelter
mapping(uint shelterId => mapping(address item => ShelterItemData)) shelterItemData;
// @notice Statistics how much items were purchased per day
mapping(uint shelterId => mapping(uint32 epochDay => mapping(address item => uint))) countPurchasedItems;
/// @notice List of registered shelters in {biome}
mapping(uint biome => EnumerableSet.UintSet shelterUids) shelters;
/// @notice Initial price of the shelters in game tokens
mapping(uint shelterId => uint) shelterPrices;
/// @notice Shelters belong to a specific guild (not the player)
/// Shelters can be free (don't belong to any guild)
mapping(uint shelterId => uint guildId) shelterToGuild;
/// @notice Each guild can own 0 or 1 shelter
mapping(uint guildId => uint shelterId) guildToShelter;
}
struct ShelterItemData {
/// @notice Price of the item in pvp-points
uint64 priceInPvpPoints;
/// @notice Price of the item game token
uint128 priceInGameToken;
/// @notice Max number of items that can be purchases per day in the shelter. 0 - no limitations
uint16 maxItemsPerDayLimit;
}
/// ----------------------------------------------------------------------------------------------
function clearShelter(uint guildId) external;
function guildToShelter(uint guildId) external view returns (uint shelterId);
function changeShelterOwner(uint shelterId, uint newOwnerGuildId) external;
function shelterToGuild(uint shelterId) external view returns (uint guildId);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../openzeppelin/EnumerableSet.sol";
import "../openzeppelin/EnumerableMap.sol";
interface IStatController {
/// @custom:storage-location erc7201:stat.controller.main
struct MainState {
mapping(bytes32 => bytes32[]) heroTotalAttributes;
/// @dev heroAdr+heroId => int32 packed strength, dexterity, vitality, energy
mapping(bytes32 => bytes32) _heroCore;
mapping(bytes32 => bytes32[]) heroBonusAttributes;
mapping(bytes32 => bytes32[]) heroTemporallyAttributes;
/// @dev heroAdr+heroId => uint32 packed level, experience, life, mana, lifeChances
mapping(bytes32 => bytes32) heroStats;
/// @dev heroAdr+heroId+itemSlot => itemAdr + itemId
mapping(bytes32 => bytes32) heroSlots;
/// @dev heroAdr+heroId => busy slots uint8[] packed
mapping(bytes32 => bytes32) heroBusySlots;
mapping(bytes32 => EnumerableSet.AddressSet) usedConsumables;
/// @dev heroCustomDataV2 is used instead
mapping(bytes32 => mapping(bytes32 => uint)) _deprecated_heroCustomData;
mapping(bytes32 => uint) globalCustomData;
/// @notice packNftIdWithValue(hero, heroId, ngLevel) => hero custom data map
/// @dev initially it was packedHero => hero custom data map
mapping(bytes32 => EnumerableMap.Bytes32ToUintMap) heroCustomDataV2;
}
enum ATTRIBUTES {
// core
STRENGTH, // 0
DEXTERITY, // 1
VITALITY, // 2
ENERGY, // 3
// attributes
DAMAGE_MIN, // 4
DAMAGE_MAX, // 5
ATTACK_RATING, // 6
DEFENSE, // 7
BLOCK_RATING, // 8
LIFE, // 9
MANA, // 10
// resistance
FIRE_RESISTANCE, // 11
COLD_RESISTANCE, // 12
LIGHTNING_RESISTANCE, // 13
// dmg against
DMG_AGAINST_HUMAN, // 14
DMG_AGAINST_UNDEAD, // 15
DMG_AGAINST_DAEMON, // 16
DMG_AGAINST_BEAST, // 17
// defence against
DEF_AGAINST_HUMAN, // 18
DEF_AGAINST_UNDEAD, // 19
DEF_AGAINST_DAEMON, // 20
DEF_AGAINST_BEAST, // 21
// --- unique, not augmentable
// hero will not die until have positive chances
LIFE_CHANCES, // 22
// increase chance to get an item
MAGIC_FIND, // 23
// decrease chance to get an item
DESTROY_ITEMS, // 24
// percent of chance x2 dmg
CRITICAL_HIT, // 25
// dmg factors
MELEE_DMG_FACTOR, // 26
FIRE_DMG_FACTOR, // 27
COLD_DMG_FACTOR, // 28
LIGHTNING_DMG_FACTOR, // 29
// increase attack rating on given percent
AR_FACTOR, // 30
// percent of damage will be converted to HP
LIFE_STOLEN_PER_HIT, // 31
// amount of mana restored after each battle
MANA_AFTER_KILL, // 32
// reduce all damage on percent after all other reductions
DAMAGE_REDUCTION, // 33
// -- statuses
// chance to stun an enemy, stunned enemy skip next hit
STUN, // 34
// chance burn an enemy, burned enemy will loss 50% of defence
BURN, // 35
// chance freeze an enemy, frozen enemy will loss 50% of MELEE damage
FREEZE, // 36
// chance to reduce enemy's attack rating on 50%
CONFUSE, // 37
// chance curse an enemy, cursed enemy will loss 50% of resistance
CURSE, // 38
// percent of dmg return to attacker
REFLECT_DAMAGE_MELEE, // 39
REFLECT_DAMAGE_MAGIC, // 40
// chance to poison enemy, poisoned enemy will loss 10% of the current health
POISON, // 41
// reduce chance get any of uniq statuses
RESIST_TO_STATUSES, // 42
END_SLOT // 43
}
// possible
// HEAL_FACTOR
struct CoreAttributes {
int32 strength;
int32 dexterity;
int32 vitality;
int32 energy;
}
struct ChangeableStats {
uint32 level;
uint32 experience;
uint32 life;
uint32 mana;
uint32 lifeChances;
}
enum ItemSlots {
UNKNOWN, // 0
HEAD, // 1
BODY, // 2
GLOVES, // 3
BELT, // 4
AMULET, // 5
BOOTS, // 6
RIGHT_RING, // 7
LEFT_RING, // 8
RIGHT_HAND, // 9
LEFT_HAND, // 10
TWO_HAND, // 11
SKILL_1, // 12
SKILL_2, // 13
SKILL_3, // 14
END_SLOT // 15
}
struct NftItem {
address token;
uint tokenId;
}
enum Race {
UNKNOWN, // 0
HUMAN, // 1
UNDEAD, // 2
DAEMON, // 3
BEAST, // 4
END_SLOT // 5
}
struct ChangeAttributesInfo {
address heroToken;
uint heroTokenId;
int32[] changeAttributes;
bool add;
bool temporally;
}
struct BuffInfo {
address heroToken;
uint heroTokenId;
uint32 heroLevel;
address[] buffTokens;
uint[] buffTokenIds;
}
/// @dev This struct is used inside event, so it's moved here from lib
struct ActionInternalInfo {
int32[] posAttributes;
int32[] negAttributes;
uint32 experience;
int32 heal;
int32 manaRegen;
int32 lifeChancesRecovered;
int32 damage;
int32 manaConsumed;
address[] mintedItems;
}
function initNewHero(address token, uint tokenId, uint heroClass) external;
function heroAttributes(address token, uint tokenId) external view returns (int32[] memory);
function heroAttribute(address token, uint tokenId, uint index) external view returns (int32);
function heroAttributesLength(address token, uint tokenId) external view returns (uint);
function heroBaseAttributes(address token, uint tokenId) external view returns (CoreAttributes memory);
function heroCustomData(address token, uint tokenId, bytes32 index) external view returns (uint);
function globalCustomData(bytes32 index) external view returns (uint);
function heroStats(address token, uint tokenId) external view returns (ChangeableStats memory);
function heroItemSlot(address token, uint64 tokenId, uint8 itemSlot) external view returns (bytes32 nftPacked);
function heroItemSlots(address heroToken, uint heroTokenId) external view returns (uint8[] memory);
function isHeroAlive(address heroToken, uint heroTokenId) external view returns (bool);
function levelUp(address token, uint tokenId, uint heroClass, CoreAttributes memory change) external returns (uint newLvl);
function changeHeroItemSlot(
address heroToken,
uint64 heroTokenId,
uint itemType,
uint8 itemSlot,
address itemToken,
uint itemTokenId,
bool equip
) external;
function changeCurrentStats(
address token,
uint tokenId,
ChangeableStats memory change,
bool increase
) external;
function changeBonusAttributes(ChangeAttributesInfo memory info) external;
function registerConsumableUsage(address heroToken, uint heroTokenId, address item) external;
function clearUsedConsumables(address heroToken, uint heroTokenId) external;
function clearTemporallyAttributes(address heroToken, uint heroTokenId) external;
function buffHero(BuffInfo memory info) external view returns (int32[] memory attributes, int32 manaConsumed);
function setHeroCustomData(address token, uint tokenId, bytes32 index, uint value) external;
function setGlobalCustomData(bytes32 index, uint value) external;
/// @notice Restore life and mana during reinforcement
/// @dev Life and mana will be increased on ((current life/mana attr value) - (prev life/mana attr value))
/// @param prevAttributes Hero attributes before reinforcement
function restoreLifeAndMana(address heroToken, uint heroTokenId, int32[] memory prevAttributes) external;
function reborn(address heroToken, uint heroTokenId, uint heroClass) external;
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../interfaces/IGOC.sol";
import "../interfaces/IStatController.sol";
import "../interfaces/IItemController.sol";
import "./IController.sol";
import "./IOracle.sol";
import "./IHeroController.sol";
import "../openzeppelin/EnumerableSet.sol";
interface IStoryController {
enum AnswerResultId {
UNKNOWN, // 0
SUCCESS, // 1
ATTRIBUTE_FAIL, // 2
RANDOM_FAIL, // 3
DELAY_FAIL, // 4
HERO_CUSTOM_DATA_FAIL, // 5
GLOBAL_CUSTOM_DATA_FAIL, // 6
END_SLOT
}
enum CustomDataResult {
UNKNOWN, // 0
HERO_SUCCESS, // 1
HERO_FAIL, // 2
GLOBAL_SUCCESS, // 3
GLOBAL_FAIL, // 4
END_SLOT
}
/// @custom:storage-location erc7201:story.controller.main
struct MainState {
// --- STORY REG INFO ---
/// @dev Uniq story identification.
mapping(uint32 => uint16) storyIds;
/// @dev Revers mapping for stories for using in the next object rewrite logic.
mapping(uint16 => uint32) idToStory;
/// @dev Store used ids for stories.
mapping(uint16 => bool) _usedStoryIds;
/// @dev Prevent register the story twice
mapping(uint32 => bool) registeredStories;
// --- ANSWER MAPPING ---
/// @dev storyId => all story pages. We need to have this mapping for properly remove meta info
mapping(uint16 => EnumerableSet.UintSet) allStoryPages;
/// @dev storyId => all possible answers. We need to have this mapping for properly remove meta info
mapping(uint16 => EnumerableSet.Bytes32Set) allStoryAnswers;
/// @dev storyId + pageId + heroClass (zero is default answers) => storyId + pageId + heroClass (zero is default answers) + answerId
mapping(bytes32 => bytes32[]) answers;
/// @dev answerUnPackedId + answerResultId => nextPageIds (will be chosen randomly from this array)
/// where answerResultId is:
/// 0 - unknown,
/// 1 - success,
/// 2 - attr fail
/// 3 - random fail
/// 4 - delay fail
/// 5 - hero custom data fail
/// 6 - global custom data fail
/// see COUNT_ANSWER_RESULT_IDS
mapping(bytes32 => uint16[]) nextPageIds;
/// @dev story + pageId + heroClass (zero is default answers) => random nextObjs (adr + id, like packed nft id)
mapping(bytes32 => uint32[]) nextObjectsRewrite;
/// @dev answerPackedId => packed array of uint32[]
/// 0 - random requirement(uint32, 1 - 99% success of this action, zero means no check)
/// 1 - delay requirement(uint32, if time since the last call more than this value the check is fail, zero means no check)
/// 2 - isFinalAnswer(uint8)
mapping(bytes32 => bytes32) answerAttributes;
// --- ANSWER REQUIREMENTS ---
/// @dev answerPackedId => array of AttributeRequirementsPacked
mapping(bytes32 => bytes32[]) attributeRequirements;
/// @dev answerPackedId=> array of ItemRequirementsPacked
mapping(bytes32 => bytes32[]) itemRequirements;
/// @dev answerPackedId => array of TokenRequirementsPacked
mapping(bytes32 => bytes32[]) tokenRequirements;
/// @dev answerPackedId => custom data for hero
mapping(bytes32 => CustomDataRequirementPacked[]) heroCustomDataRequirement;
/// @dev answerPackedId => global custom data
mapping(bytes32 => CustomDataRequirementPacked[]) globalCustomDataRequirement;
// --- ANSWER RESULTS ---
/// @dev answerPackedId => change attributes
mapping(bytes32 => bytes32[]) successInfoAttributes;
/// @dev answerPackedId => change stats
mapping(bytes32 => bytes32) successInfoStats;
/// @dev answerPackedId => mint items
mapping(bytes32 => bytes32[]) successInfoMintItems;
/// @dev answerPackedId => change attributes
mapping(bytes32 => bytes32[]) failInfoAttributes;
/// @dev answerPackedId => change stats
mapping(bytes32 => bytes32) failInfoStats;
/// @dev answerPackedId => mint items
mapping(bytes32 => bytes32[]) failInfoMintItems;
/// @dev answerUnPackedId + CustomDataResult => custom data array change
/// where CustomDataResult is
/// 1 - hero success
/// 2 - hero fail
/// 3 - global success
/// 4 - global fail
/// see COUNT_CUSTOM_DATA_RESULT_IDS
mapping(bytes32 => bytes32[]) customDataResult;
/// @notice answerPackedId => slot+chance+stopIfBurnt
/// @dev Since SIP-003 the items are not burn but broke
mapping(bytes32 => bytes32[]) burnItem;
// --- GENERAL STORY REQUIREMENTS ---
/// @dev story => Custom hero data requirements for a story. If exist and hero is not eligible should be not chose in a dungeon.
mapping(uint => CustomDataRequirementRangePacked[]) storyRequiredHeroData;
/// @dev story => Minimal level for the history. 0 means no requirements.
mapping(uint => uint) storyRequiredLevel;
// --- HERO STATES ---
/// @dev hero + heroId + storyId => pageId + heroLastActionTS
mapping(bytes32 => bytes32) heroState;
// --- OTHER ---
/// @dev storyId => build hash for the last update
mapping(uint16 => uint) storyBuildHash;
/// @notice Number of already minted items by the user within the given iteration of the story.
/// Only minting of the given number of items is allowed per iteration (see MAX_MINTED_ITEMS_PER_ITERATION).
/// @dev hero, heroId, story => mintedInIteration
/// This map is not cleared: storyId:objectId is 1:1, each object has own sequence of iterations without duplicates
mapping(bytes32 => mapping(uint iteration => uint countMintedItems)) mintedInIteration;
}
/// @dev We need to have flat structure coz Solidity can not handle arrays of structs properly
struct StoryMetaInfo {
uint16 storyId;
// --- story reqs
bytes32[] requiredCustomDataIndex;
uint64[] requiredCustomDataMinValue;
uint64[] requiredCustomDataMaxValue;
bool[] requiredCustomDataIsHero;
uint minLevel;
// --- answer reqs
AnswersMeta answersMeta;
AnswerNextPageMeta answerNextPage;
AnswerAttributeRequirementsMeta answerAttributeRequirements;
AnswerItemRequirementsMeta answerItemRequirements;
AnswerTokenRequirementsMeta answerTokenRequirements;
AnswerAttributesMeta answerAttributes;
AnswerCustomDataMeta answerHeroCustomDataRequirement;
AnswerCustomDataMeta answerGlobalCustomDataRequirement;
// --- answer results
AnswerBurnRandomItemMeta answerBurnRandomItemMeta;
NextObjRewriteMeta nextObjRewriteMeta;
// --- story results
AnswerResultMeta successInfo;
AnswerResultMeta failInfo;
AnswerCustomDataResultMeta successHeroCustomData;
AnswerCustomDataResultMeta failHeroCustomData;
AnswerCustomDataResultMeta successGlobalCustomData;
AnswerCustomDataResultMeta failGlobalCustomData;
}
struct NextObjRewriteMeta {
uint16[] nextObjPageIds;
uint8[] nextObjHeroClasses;
uint32[][] nextObjIds;
}
struct AnswersMeta {
uint16[] answerPageIds;
uint8[] answerHeroClasses;
uint16[] answerIds;
}
struct AnswerNextPageMeta {
uint16[] pageId;
uint8[] heroClass;
uint16[] answerId;
uint8[] answerResultIds;
uint16[][] answerNextPageIds;
}
struct AnswerAttributeRequirementsMeta {
uint16[] pageId;
uint8[] heroClass;
uint16[] answerId;
bool[][] cores;
uint8[][] ids;
int32[][] values;
}
struct AnswerItemRequirementsMeta {
uint16[] pageId;
uint8[] heroClass;
uint16[] answerId;
address[][] requireItems;
bool[][] requireItemBurn;
bool[][] requireItemEquipped;
}
struct AnswerTokenRequirementsMeta {
uint16[] pageId;
uint8[] heroClass;
uint16[] answerId;
address[][] requireToken;
uint88[][] requireAmount;
bool[][] requireTransfer;
}
struct AnswerAttributesMeta {
uint16[] pageId;
uint8[] heroClass;
uint16[] answerId;
uint32[] randomRequirements;
uint32[] delayRequirements;
bool[] isFinalAnswer;
}
struct AnswerCustomDataMeta {
uint16[] pageId;
uint8[] heroClass;
uint16[] answerId;
bytes32[][] dataIndexes;
bool[][] mandatory;
uint64[][] dataValuesMin;
uint64[][] dataValuesMax;
}
struct AnswerResultMeta {
uint16[] pageId;
uint8[] heroClass;
uint16[] answerId;
uint8[][] attributeIds;
/// @dev Max value is limitied by int24, see toBytes32ArrayWithIds impl
int32[][] attributeValues;
uint32[] experience;
int32[] heal;
int32[] manaRegen;
int32[] lifeChancesRecovered;
int32[] damage;
int32[] manaConsumed;
address[][] mintItems;
uint32[][] mintItemsChances;
}
struct AnswerCustomDataResultMeta {
uint16[] pageId;
uint8[] heroClass;
uint16[] answerId;
bytes32[][] dataIndexes;
int16[][] dataValues;
}
struct AnswerBurnRandomItemMeta {
uint16[] pageId;
uint8[] heroClass;
uint16[] answerId;
/// @notice 0 - random slot
uint8[][] slots;
/// @notice typical chances are [0..100] (no decimals here)
uint64[][] chances;
/// @notice Since SIP-003 the burning is replaced by breaking bu the name is kept as is
bool[][] isStopIfBurnt;
}
struct CustomDataRequirementPacked {
bytes32 index;
/// @dev min(uint64) + max(uint64) + mandatory(uint8)
bytes32 data;
}
struct CustomDataRequirementRangePacked {
bytes32 index;
/// @dev min(uint64) + max(uint64) + isHeroData(uint8)
bytes32 data;
}
struct StatsChange {
uint32 experience;
int32 heal;
int32 manaRegen;
int32 lifeChancesRecovered;
int32 damage;
int32 manaConsumed;
}
struct StoryActionContext {
uint stageId;
uint iteration;
bytes32 answerIdHash;
bytes32 answerAttributes;
address sender;
address heroToken;
IController controller;
IStatController statController;
IHeroController heroController;
IOracle oracle;
IItemController itemController;
uint8 heroClass;
uint8 heroClassFromAnswerHash;
uint8 biome;
uint16 storyId;
uint16 storyIdFromAnswerHash;
uint16 pageIdFromAnswerHash;
uint16 answerNumber;
uint16 pageId;
uint32 objectId;
uint64 dungeonId;
uint40 heroLastActionTS;
uint80 heroTokenId;
IStatController.ChangeableStats heroStats;
}
// --- WRITE ---
function storyAction(
address sender,
uint64 dungeonId,
uint32 objectId,
uint stageId,
address heroToken,
uint heroTokenId,
uint8 biome,
uint iteration,
bytes memory data
) external returns (IGOC.ActionResult memory);
// --- READ ---
function isStoryAvailableForHero(uint32 objectId, address heroToken, uint heroTokenId) external view returns (bool);
function idToStory(uint16 id) external view returns (uint32 objectId);
function heroPage(address hero, uint80 heroId, uint16 storyId) external view returns (uint16 pageId);
function storyIds(uint32 objectId) external view returns (uint16);
function registeredStories(uint32 objectId) external view returns (bool);
}// 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;
interface IUserController {
//region ------------------------ Data types
enum LootBoxKind {
/// @notice small loot box - reward for the daily activity
DAILY_0,
/// @notice large loot box - reward for the weekly activity (daily activity is passed each ot of the 7 days)
WEEKLY_1,
END_SLOT
}
/// @dev registerPassedDungeon assumes that the whole struct takes single slot only, not more
struct UserActivity {
/// @notice A day for which the daily activity is calculated (see counterXXX below)
/// The number of days since 1970-01-01
uint32 epochDay;
/// @notice A week for which total count of daily activities were calculated
/// The number of weeks since (1970-01-01 Thursday) - 3 days = (1969-12-29 Monday)
uint32 epochWeek;
/// @notice Count of dungeons passed during the day
uint32 counterPassedDungeons;
/// @notice Count of PvP during the day
uint32 counterPvp;
/// @notice Count of daily activities completed per the week
uint16 dailyActivities;
/// @notice Daily activity is completed and small loot box is added to the earned loot boxes
bool dailyLootBoxReceived;
/// @notice Weekly activity is completed and large loot box is added to the earned loot boxes
bool weeklyLootBoxReceived;
}
struct EarnedLootBoxes {
/// @notice Count of loot boxes earned by daily activity
uint32 dailyCounter;
/// @notice Count of loot boxes earned by weekly activity
uint32 weeklyCounter;
}
struct LootBoxConfig {
address[] mintItems;
uint32[] mintItemsChances;
uint maxDropItems;
}
/// @custom:storage-location erc7201:user.controller.main
struct MainState {
/// @notice Amount of sacra required to rename user account
uint feeRenaming;
/// @dev user EOA => account name
mapping(address => string) userAccountName;
/// @dev name => user EOA, needs for checking uniq names
mapping(string => address) nameToUserAccount;
/// @notice user => daily activity info
mapping(address => UserActivity) userActivity;
/// @notice user => earned loot boxes
mapping(address => EarnedLootBoxes) counterLootBoxes;
/// @notice Configs of loot boxes of various kinds
mapping(LootBoxKind => LootBoxConfig) lootBoxConfig;
/// @dev Deprecated, controller is used instead.
address userTokensVault;
/// @dev user EOA => account avatar
mapping(address => string) userAvatar;
// @notice Hall of Fame: ngLevel [1...99] => who opened the NG_LEVEL first
mapping(uint8 ngLevel => FameHallData) fameHall;
}
struct FameHallData {
// ------------ slot 1
/// @notice The hero who opened given the NG_LEVEL first
address hero;
uint64 heroId;
// ------------ slot 2
/// @notice The owner of the hero
address heroOwner;
/// @notice Timestamp of the moment of the opening given NG_LEVEL
uint64 tsOpen;
}
//endregion ------------------------ Data types
/// @notice Register daily activity - a dungeon was passed
/// @param user Owner of the hero who has passed the dungeon
function registerPassedDungeon(address user) external;
/// @notice Register daily activity - PvP was made
/// @param user Owner of the hero who has taken participation in the PvP
function registerPvP(address user, bool isWinner) external;
function registerFameHallHero(address hero, uint heroId, uint8 openedNgLevel) external;
}// 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";
/// @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.getStatController(cc);
library ControllerContextLib {
struct ControllerContext {
IController controller;
IStatController statController;
IStoryController storyController;
IOracle oracle;
ITreasury treasury;
IDungeonFactory dungeonFactory;
IGOC gameObjectController;
IReinforcementController reinforcementController;
IItemController itemController;
IHeroController heroController;
IGameToken gameToken;
IUserController userController;
IGuildController guildController;
IRewardsPool rewardsPool;
}
function init(IController controller) internal pure returns (ControllerContext memory cc) {
cc.controller = controller;
return cc;
}
function getStatController(ControllerContext memory cc) internal view returns (IStatController statController) {
if (address(cc.statController) == address(0)) {
cc.statController = IStatController(cc.controller.statController());
}
return cc.statController;
}
function getStoryController(ControllerContext memory cc) internal view returns (IStoryController storyController) {
if (address(cc.storyController) == address(0)) {
cc.storyController = IStoryController(cc.controller.storyController());
}
return cc.storyController;
}
function getOracle(ControllerContext memory cc) internal view returns (IOracle oracle) {
if (address(cc.oracle) == address(0)) {
cc.oracle = IOracle(cc.controller.oracle());
}
return cc.oracle;
}
function getTreasury(ControllerContext memory cc) internal view returns (ITreasury treasury) {
if (address(cc.treasury) == address(0)) {
cc.treasury = ITreasury(cc.controller.treasury());
}
return cc.treasury;
}
function getDungeonFactory(ControllerContext memory cc) internal view returns (IDungeonFactory dungeonFactory) {
if (address(cc.dungeonFactory) == address(0)) {
cc.dungeonFactory = IDungeonFactory(cc.controller.dungeonFactory());
}
return cc.dungeonFactory;
}
function getGameObjectController(ControllerContext memory cc) internal view returns (IGOC gameObjectController) {
if (address(cc.gameObjectController) == address(0)) {
cc.gameObjectController = IGOC(cc.controller.gameObjectController());
}
return cc.gameObjectController;
}
function getReinforcementController(ControllerContext memory cc) internal view returns (IReinforcementController reinforcementController) {
if (address(cc.reinforcementController) == address(0)) {
cc.reinforcementController = IReinforcementController(cc.controller.reinforcementController());
}
return cc.reinforcementController;
}
function getItemController(ControllerContext memory cc) internal view returns (IItemController itemController) {
if (address(cc.itemController) == address(0)) {
cc.itemController = IItemController(cc.controller.itemController());
}
return cc.itemController;
}
function getHeroController(ControllerContext memory cc) internal view returns (IHeroController heroController) {
if (address(cc.heroController) == address(0)) {
cc.heroController = IHeroController(cc.controller.heroController());
}
return cc.heroController;
}
function getGameToken(ControllerContext memory cc) internal view returns (IGameToken gameToken) {
if (address(cc.gameToken) == address(0)) {
cc.gameToken = IGameToken(cc.controller.gameToken());
}
return cc.gameToken;
}
function getUserController(ControllerContext memory cc) internal view returns (IUserController userController) {
if (address(cc.userController) == address(0)) {
cc.userController = IUserController(cc.controller.userController());
}
return cc.userController;
}
function getGuildController(ControllerContext memory cc) internal view returns (IGuildController guildController) {
if (address(cc.guildController) == address(0)) {
cc.guildController = IGuildController(cc.controller.guildController());
}
return cc.guildController;
}
function getRewardsPool(ControllerContext memory cc) internal view returns (IRewardsPool rewardsPool) {
if (address(cc.rewardsPool) == address(0)) {
cc.rewardsPool = IRewardsPool(cc.controller.rewardsPool());
}
return cc.rewardsPool;
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "./CalcLib.sol";
import "./PackingLib.sol";
import "./ItemLib.sol";
import "./StoryLib.sol";
import "../interfaces/IStatController.sol";
import "../interfaces/IOracle.sol";
import "../interfaces/IGOC.sol";
import "../interfaces/IAppErrors.sol";
import "../interfaces/IApplicationEvents.sol";
library EventLib {
using CalcLib for int32;
using PackingLib for bytes32;
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 ------------------------ Main logic
function action(IGOC.ActionContext calldata ctx, IGOC.EventInfo storage info) external returns (
IGOC.ActionResult memory
) {
(bool accept) = abi.decode(ctx.data, (bool));
return accept
? _eventAcceptResult(ctx, info)
: _noActionResult();
}
/// @notice Save data from {regInfo} to {info}
function eventRegInfoToInfo(IGOC.EventRegInfo calldata regInfo, IGOC.EventInfo storage info) external {
info.goodChance = regInfo.goodChance;
info.goodAttributes = regInfo.goodAttributes.values.toBytes32ArrayWithIds(regInfo.goodAttributes.ids);
info.badAttributes = regInfo.badAttributes.values.toBytes32ArrayWithIds(regInfo.badAttributes.ids);
info.statsChange = regInfo.experience.packStatsChange(
regInfo.heal,
regInfo.manaRegen,
regInfo.lifeChancesRecovered,
regInfo.damage,
regInfo.manaConsumed
);
bytes32[] memory mintItems = new bytes32[](regInfo.mintItems.length);
for (uint i; i < mintItems.length; ++i) {
mintItems[i] = regInfo.mintItems[i].packItemMintInfo(regInfo.mintItemsChances[i]);
}
info.mintItems = mintItems;
}
//endregion ------------------------ Main logic
//region ------------------------ Internal logic
function _eventAcceptResult(IGOC.ActionContext calldata ctx, IGOC.EventInfo storage info) internal returns (
IGOC.ActionResult memory result
) {
IStatController sc = IStatController(ctx.controller.statController());
IStatController.ActionInternalInfo memory gen = _generate(ctx, info, sc);
if (gen.posAttributes.length != 0) {
sc.changeBonusAttributes(IStatController.ChangeAttributesInfo({
heroToken: ctx.heroToken,
heroTokenId: ctx.heroTokenId,
changeAttributes: gen.posAttributes,
add: true,
temporally: true
}));
}
if (gen.negAttributes.length != 0) {
sc.changeBonusAttributes(IStatController.ChangeAttributesInfo({
heroToken: ctx.heroToken,
heroTokenId: ctx.heroTokenId,
changeAttributes: gen.negAttributes,
add: true,
temporally: true
}));
}
// refreshed stats
IStatController.ChangeableStats memory stats = sc.heroStats(ctx.heroToken, ctx.heroTokenId);
result.completed = true;
result.experience = gen.experience;
result.heal = gen.heal;
result.manaRegen = gen.manaRegen;
result.lifeChancesRecovered = gen.lifeChancesRecovered;
result.damage = gen.damage;
result.manaConsumed = CalcLib.minI32(gen.manaConsumed, int32(stats.mana));
result.mintItems = gen.mintedItems;
if (stats.life <= gen.damage.toUint()) {
result.kill = true;
}
emit IApplicationEvents.EventResult(ctx.dungeonId, ctx.heroToken, ctx.heroTokenId, ctx.stageId, gen, ctx.iteration);
return result;
}
/// @notice Generate empty result structure, only "completed" is true
function _noActionResult() internal pure returns (IGOC.ActionResult memory result) {
result.completed = true;
return result;
}
/// @notice Generate either positive or negative attributes, mint single item in any case
function _generate(IGOC.ActionContext calldata ctx, IGOC.EventInfo storage info, IStatController sc) internal returns (
IStatController.ActionInternalInfo memory result
) {
uint32 goodChance = info.goodChance;
if (goodChance > CalcLib.MAX_CHANCE) revert IAppErrors.TooHighChance(goodChance);
IOracle oracle = IOracle(ctx.controller.oracle());
uint random = goodChance == CalcLib.MAX_CHANCE ? CalcLib.MAX_CHANCE : oracle.getRandomNumber(CalcLib.MAX_CHANCE, 0);
if (random <= goodChance) {
result.posAttributes = StoryLib._generateAttributes(info.goodAttributes);
(result.experience,
result.heal,
result.manaRegen,
result.lifeChancesRecovered,,) = info.statsChange.unpackStatsChange();
} else {
result.negAttributes = StoryLib._generateAttributes(info.badAttributes);
(,,,, result.damage, result.manaConsumed) = info.statsChange.unpackStatsChange();
}
// always mint possible items even if bad result
result.mintedItems = _mintRandomItem(ctx, info, oracle, sc, CalcLib.nextPrng);
return result;
}
/// @notice Mint single random item
/// @param nextPrng_ CalcLib.nextPrng, param is required by unit tests
function _mintRandomItem(
IGOC.ActionContext calldata ctx,
IGOC.EventInfo storage info,
IOracle oracle,
IStatController sc,
function (LibPRNG.PRNG memory, uint) internal view returns (uint) nextPrng_
) internal returns (address[] memory minted) {
bytes32[] memory mintItemsPacked = info.mintItems;
if (mintItemsPacked.length == 0) {
return minted;
}
IStatController.ChangeableStats memory stats = sc.heroStats(ctx.heroToken, ctx.heroTokenId);
address[] memory mintItems = new address[](mintItemsPacked.length);
uint32[] memory mintItemsChances = new uint32[](mintItemsPacked.length);
for (uint i = 0; i < mintItemsPacked.length; i++) {
(mintItems[i], mintItemsChances[i]) = mintItemsPacked[i].unpackItemMintInfo();
}
return ItemLib._mintRandomItems(
ItemLib.MintItemInfo({
mintItems: mintItems,
mintItemsChances: mintItemsChances,
amplifier: 0,
seed: 0,
oracle: oracle,
magicFind: 0,
destroyItems: 0,
maxItems: 1, // MINT ONLY 1 ITEM!
mintDropChanceDelta: StatLib.mintDropChanceDelta(stats.experience, uint8(stats.level), ctx.biome),
mintDropChanceNgLevelMultiplier: 1e18
}),
nextPrng_
);
}
//endregion ------------------------ Internal logic
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../interfaces/IStatController.sol";
import "../interfaces/IItemController.sol";
import "../interfaces/IFightCalculator.sol";
import "../interfaces/IAppErrors.sol";
import "../interfaces/IApplicationEvents.sol";
import "../lib/StatLib.sol";
import "../lib/CalcLib.sol";
import "../lib/PackingLib.sol";
import "../solady/FixedPointMathLib.sol";
library FightLib {
using PackingLib for bytes32;
using CalcLib for int32;
//region ------------------------ Data types
struct AttackResult {
int32 defenderHealth;
int32 damage;
int32 lifeStolen;
int32 reflectDamage;
uint8 critical;
uint8 missed;
uint8 blocked;
}
//endregion ------------------------ Data types
//region ------------------------ Constants
uint internal constant MAX_FIGHT_CYCLES = 100;
int32 internal constant RESISTANCE_DENOMINATOR = 100;
int32 internal constant _MAX_RESIST = 90;
/// @notice SIP-002 constant: desired capacity
uint internal constant CAPACITY_RESISTS_DEFS = 90;
/// @notice SIP-002 constant: desired capacity
uint internal constant CAPACITY_CRITICAL_HIT_STATUSES = 100;
/// @notice SIP-002 constant: the factor of how fast the value will reach the capacity
uint internal constant K_FACTOR = 100;
/// @notice ln(2), decimals 18
int internal constant LN2 = 693147180559945309;
//endregion ------------------------ Constants
//region ------------------------ Main logic
/// @dev Items ownership must be checked before
/// it is no write actions but we need to emit an event for properly handle the battle on UI
/// return huge structs more expensive that call an event here
/// @param random_ Pass _pseudoRandom here, param is required for unit tests
function fight(
IItemController ic,
IFightCalculator.FightCall memory callData,
address msgSender,
function (uint) internal view returns (uint) random_
) internal returns (
IFightCalculator.FightResult memory
) {
IFightCalculator.FightInfoInternal memory fResult = prepareFightInternalInfo(ic, callData.fighterA, callData.fighterB);
fightProcessing(fResult, random_);
emit IApplicationEvents.FightResultProcessed(msgSender, fResult, callData, callData.iteration);
return IFightCalculator.FightResult({
healthA: fResult.fighterA.health,
healthB: fResult.fighterB.health,
manaConsumedA: fResult.fighterA.manaConsumed,
manaConsumedB: fResult.fighterB.manaConsumed
});
}
//endregion ------------------------ Main logic
//region ------------------------ High level of internal logic
function fightProcessing(
IFightCalculator.FightInfoInternal memory fResult,
function (uint) internal view returns (uint) random_
) internal view {
bool firstA = calcFirstHit(fResult);
setStatuses(fResult, firstA, random_);
setStatuses(fResult, !firstA, random_);
reduceAttributesByStatuses(fResult.fighterA.info.fighterAttributes, fResult.fighterA.statuses, fResult.fighterB.info.fighterAttributes);
reduceAttributesByStatuses(fResult.fighterB.info.fighterAttributes, fResult.fighterB.statuses, fResult.fighterA.info.fighterAttributes);
AttackResult memory resultA = processAttack(fResult, true, random_);
AttackResult memory resultB = processAttack(fResult, false, random_);
fResult.fighterA.statuses.gotCriticalHit = resultA.critical != 0;
fResult.fighterA.statuses.missed = resultA.missed != 0;
fResult.fighterA.statuses.hitBlocked = resultA.blocked != 0;
fResult.fighterB.statuses.gotCriticalHit = resultB.critical != 0;
fResult.fighterB.statuses.missed = resultB.missed != 0;
fResult.fighterB.statuses.hitBlocked = resultB.blocked != 0;
reduceHp(
firstA ? resultA : resultB,
firstA ? resultB : resultA,
firstA ? fResult.fighterA : fResult.fighterB,
firstA ? fResult.fighterB : fResult.fighterA
);
// restore health from stolen life
stealLife(fResult.fighterA, resultA);
stealLife(fResult.fighterB, resultB);
}
function processAttack(
IFightCalculator.FightInfoInternal memory fResult,
bool isA,
function (uint) internal view returns (uint) random_
) internal view returns (AttackResult memory attackResult) {
int32 defenderHealth = isA ? fResult.fighterB.health : fResult.fighterA.health;
if (skipTurn(fResult, isA)) {
return AttackResult({
defenderHealth: defenderHealth,
damage: 0,
lifeStolen: 0,
reflectDamage: 0,
critical: 0,
missed: 0,
blocked: 0
});
}
IFightCalculator.FighterInfo memory attackerInfo = isA ? fResult.fighterA.info : fResult.fighterB.info;
IFightCalculator.FighterInfo memory defenderInfo = isA ? fResult.fighterB.info : fResult.fighterA.info;
if (attackerInfo.attackType == IFightCalculator.AttackType.MELEE) {
attackResult = meleeDamageCalculation(attackerInfo, defenderInfo, defenderHealth, random_);
} else if (attackerInfo.attackType == IFightCalculator.AttackType.MAGIC) {
attackResult = magicDamageCalculation(
attackerInfo,
defenderInfo,
isA ? fResult.fighterA.magicAttack : fResult.fighterB.magicAttack,
defenderHealth,
random_
);
} else {
revert IAppErrors.NotAType(uint(attackerInfo.attackType));
}
}
//endregion ------------------------ High level of internal logic
//region ------------------------ Internal logic
function prepareFightInternalInfo(
IItemController ic,
IFightCalculator.FighterInfo memory fighterA,
IFightCalculator.FighterInfo memory fighterB
) internal view returns (IFightCalculator.FightInfoInternal memory) {
IFightCalculator.FightInfoInternal memory fInfo;
_setFightData(ic, fighterA, fInfo.fighterA);
_setFightData(ic, fighterB, fInfo.fighterB);
return fInfo;
}
/// @dev A part of prepareFightInternalInfo
function _setFightData(
IItemController ic,
IFightCalculator.FighterInfo memory fighter,
IFightCalculator.Fighter memory dest
) internal view {
dest.info = fighter;
dest.health = int32(fighter.fighterStats.life);
if (fighter.attackToken != address(0)) {
if (fighter.attackType != IFightCalculator.AttackType.MAGIC) revert IAppErrors.NotMagic();
dest.magicAttack = ic.itemAttackInfo(fighter.attackToken, fighter.attackTokenId);
}
// dest.manaConsumed is 0 by default, in current implementation we don't need to change it
}
/// @param random_ Either _pseudoRandom or pseudo-random for ut
function statusChance(
IFightCalculator.FighterInfo memory attackerInfo,
IItemController.AttackInfo memory attackerMA,
IStatController.ATTRIBUTES index,
int32 resist,
function (uint) internal view returns (uint) random_
) internal view returns (bool) {
int32 chance = _getChance(attackerInfo, attackerMA.aType, index, resist);
if (chance == 0) {
return false;
}
if (chance >= RESISTANCE_DENOMINATOR) {
return true;
}
return random_(RESISTANCE_DENOMINATOR.toUint()) < chance.toUint();
}
/// @notice set fResult.fighterB.statuses (for isA = true) or fResult.fighterA.statuses (for isA = false)
/// @param random_ Either _pseudoRandom or pseudo-random for ut
function setStatuses(
IFightCalculator.FightInfoInternal memory fResult,
bool isA,
function (uint) internal view returns (uint) random_
) internal view {
// setStatuses is called twice one by one: first time for A, second time for B
// if stun is set for A, setStatuses is skipped for B completely
if (!skipTurn(fResult, isA)) {
IFightCalculator.FighterInfo memory attackerInfo = isA ? fResult.fighterA.info : fResult.fighterB.info;
IFightCalculator.FighterInfo memory defenderInfo = isA ? fResult.fighterB.info : fResult.fighterA.info;
IItemController.AttackInfo memory attackerMA = isA ? fResult.fighterA.magicAttack : fResult.fighterB.magicAttack;
IFightCalculator.Statuses memory statuses = isA ? fResult.fighterB.statuses : fResult.fighterA.statuses;
int32 resist = _getAttrValue(defenderInfo.fighterAttributes, IStatController.ATTRIBUTES.RESIST_TO_STATUSES);
statuses.stun = statusChance(attackerInfo, attackerMA, IStatController.ATTRIBUTES.STUN, resist, random_);
statuses.burn = statusChance(
attackerInfo,
attackerMA,
IStatController.ATTRIBUTES.BURN,
_getAttrValue(defenderInfo.fighterAttributes, IStatController.ATTRIBUTES.FIRE_RESISTANCE),
random_
);
statuses.freeze = statusChance(
attackerInfo,
attackerMA,
IStatController.ATTRIBUTES.FREEZE,
_getAttrValue(defenderInfo.fighterAttributes, IStatController.ATTRIBUTES.COLD_RESISTANCE),
random_
);
statuses.confuse = statusChance(attackerInfo, attackerMA, IStatController.ATTRIBUTES.CONFUSE, resist, random_);
statuses.curse = statusChance(attackerInfo, attackerMA, IStatController.ATTRIBUTES.CURSE, resist, random_);
statuses.poison = statusChance(attackerInfo, attackerMA, IStatController.ATTRIBUTES.POISON, resist, random_);
}
}
function magicDamageCalculation(
IFightCalculator.FighterInfo memory attackerInfo,
IFightCalculator.FighterInfo memory defenderInfo,
IItemController.AttackInfo memory magicAttack,
int32 defenderHealth,
function (uint) internal view returns (uint) random_
) internal view returns (AttackResult memory attackResult) {
// generate damage
int32 damage = getMagicDamage(
attackerInfo,
magicAttack,
CalcLib.pseudoRandomInRangeFlex(magicAttack.min.toUint(), magicAttack.max.toUint(), random_)
);
damage = increaseMagicDmgByFactor(damage, attackerInfo, magicAttack.aType);
damage = increaseRaceDmg(damage, attackerInfo, defenderInfo.race);
bool critical = isCriticalHit(attackerInfo, random_(RESISTANCE_DENOMINATOR.toUint()));
damage = critical ? damage * 2 : damage;
// decrease damage
damage = decreaseRaceDmg(damage, defenderInfo, attackerInfo.race);
damage = decreaseDmgByDmgReduction(damage, defenderInfo);
if (magicAttack.aType == IItemController.AttackType.FIRE) {
damage -= _calcDmgInline(damage, defenderInfo, IStatController.ATTRIBUTES.FIRE_RESISTANCE);
} else if (magicAttack.aType == IItemController.AttackType.COLD) {
damage -= _calcDmgInline(damage, defenderInfo, IStatController.ATTRIBUTES.COLD_RESISTANCE);
} else if (magicAttack.aType == IItemController.AttackType.LIGHTNING) {
damage -= _calcDmgInline(damage, defenderInfo, IStatController.ATTRIBUTES.LIGHTNING_RESISTANCE);
}
int32 defenderHealthResult = defenderHealth < damage ? int32(0) : defenderHealth - damage;
damage = defenderHealth - defenderHealthResult;
return AttackResult({
defenderHealth: defenderHealthResult,
damage: damage,
lifeStolen: lifeStolenPerHit(damage, attackerInfo),
reflectDamage: reflectMagicDmg(damage, defenderInfo) + reflectChaos(magicAttack, attackerInfo, random_(1e18)),
critical: critical ? uint8(1) : uint8(0),
missed: 0,
blocked: 0
});
}
function meleeDamageCalculation(
IFightCalculator.FighterInfo memory attackerInfo,
IFightCalculator.FighterInfo memory defenderInfo,
int32 defenderHealth,
function (uint) internal view returns (uint) random_
) internal view returns (AttackResult memory attackResult) {
attackResult = (new AttackResult[](1))[0];
// generate damage
int32 damage = getDamage(attackerInfo.fighterAttributes, random_);
damage = increaseMeleeDmgByFactor(damage, attackerInfo);
damage = increaseRaceDmg(damage, attackerInfo, defenderInfo.race);
attackResult.critical = isCriticalHit(attackerInfo, random_(RESISTANCE_DENOMINATOR.toUint())) ? uint8(1) : uint8(0);
damage = attackResult.critical == 0 ? damage : damage * 2;
// decrease damage
damage = decreaseRaceDmg(damage, defenderInfo, attackerInfo.race);
damage = decreaseDmgByDmgReduction(damage, defenderInfo);
attackResult.missed = random_(1e18) > StatLib.chanceToHit(
_getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.ATTACK_RATING).toUint(),
_getAttrValue(defenderInfo.fighterAttributes, IStatController.ATTRIBUTES.DEFENSE).toUint(),
attackerInfo.fighterStats.level,
defenderInfo.fighterStats.level,
_getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.AR_FACTOR).toUint()
) ? 1 : 0;
attackResult.blocked = (random_(100) < _getAttrValue(defenderInfo.fighterAttributes, IStatController.ATTRIBUTES.BLOCK_RATING).toUint()) ? 1 : 0;
if (attackResult.missed != 0 || attackResult.blocked != 0) {
damage = 0;
}
int32 defenderHealthResult = defenderHealth <= damage ? int32(0) : defenderHealth - damage;
damage = defenderHealth - defenderHealthResult;
attackResult.defenderHealth = defenderHealthResult;
attackResult.damage = damage;
attackResult.lifeStolen = lifeStolenPerHit(damage, attackerInfo);
attackResult.reflectDamage = reflectMeleeDmg(damage, defenderInfo);
}
function getDamage(
int32[] memory attributes,
function (uint) internal view returns (uint) random_
) internal view returns (int32) {
return int32(int(CalcLib.pseudoRandomInRangeFlex(
_getAttrValue(attributes, IStatController.ATTRIBUTES.DAMAGE_MIN).toUint(),
_getAttrValue(attributes, IStatController.ATTRIBUTES.DAMAGE_MAX).toUint(),
random_
)));
}
//endregion ------------------------ Internal logic
//region ------------------------ Pure utils
/// @notice Modify values in {targetAttributes} and {casterAttributes} according to {statuses}
function reduceAttributesByStatuses(
int32[] memory targetAttributes,
IFightCalculator.Statuses memory statuses,
int32[] memory casterAttributes
) internal pure {
if (statuses.burn) {
targetAttributes[uint(IStatController.ATTRIBUTES.DEFENSE)] -= (targetAttributes[uint(IStatController.ATTRIBUTES.DEFENSE)] / 3);
targetAttributes[uint(IStatController.ATTRIBUTES.COLD_RESISTANCE)] += 50;
casterAttributes[uint(IStatController.ATTRIBUTES.CRITICAL_HIT)] += 10;
casterAttributes[uint(IStatController.ATTRIBUTES.DESTROY_ITEMS)] += 20;
}
if (statuses.freeze) {
targetAttributes[uint(IStatController.ATTRIBUTES.DAMAGE_MIN)] /= 2;
targetAttributes[uint(IStatController.ATTRIBUTES.DAMAGE_MAX)] /= 2;
targetAttributes[uint(IStatController.ATTRIBUTES.ATTACK_RATING)] -= targetAttributes[uint(IStatController.ATTRIBUTES.ATTACK_RATING)] / 3;
targetAttributes[uint(IStatController.ATTRIBUTES.BLOCK_RATING)] /= 2;
targetAttributes[uint(IStatController.ATTRIBUTES.FIRE_RESISTANCE)] += 50;
}
if (statuses.confuse) {
targetAttributes[uint(IStatController.ATTRIBUTES.ATTACK_RATING)] /= 2;
}
if (statuses.curse) {
targetAttributes[uint(IStatController.ATTRIBUTES.FIRE_RESISTANCE)] /= 2;
targetAttributes[uint(IStatController.ATTRIBUTES.COLD_RESISTANCE)] /= 2;
targetAttributes[uint(IStatController.ATTRIBUTES.LIGHTNING_RESISTANCE)] /= 2;
}
if (statuses.stun) {
casterAttributes[uint(IStatController.ATTRIBUTES.CRITICAL_HIT)] += 10;
}
if (statuses.poison) {
targetAttributes[uint(IStatController.ATTRIBUTES.ATTACK_RATING)] /= 2;
}
}
/// @notice Calculate new damage value depending on {defenderRace} and value of corresponded DMG_AGAINST_XXX attribute
/// @param defenderRace See IStatController.Race
/// @return Updated damage value
function increaseRaceDmg(int32 dmg, IFightCalculator.FighterInfo memory attackerInfo, uint defenderRace)
internal pure returns (int32) {
if (defenderRace == uint(IStatController.Race.HUMAN)) {
return dmg + _getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.DMG_AGAINST_HUMAN) * dmg / RESISTANCE_DENOMINATOR;
} else if (defenderRace == uint(IStatController.Race.UNDEAD)) {
return dmg + _getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.DMG_AGAINST_UNDEAD) * dmg / RESISTANCE_DENOMINATOR;
} else if (defenderRace == uint(IStatController.Race.DAEMON)) {
return dmg + _getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.DMG_AGAINST_DAEMON) * dmg / RESISTANCE_DENOMINATOR;
} else if (defenderRace == uint(IStatController.Race.BEAST)) {
return dmg + _getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.DMG_AGAINST_BEAST) * dmg / RESISTANCE_DENOMINATOR;
} else {
return dmg;
}
}
/// @notice Decrease damage depending on {attackerRace}
function decreaseRaceDmg(int32 dmg, IFightCalculator.FighterInfo memory defenderInfo, uint attackerRace) internal pure returns (int32) {
if (attackerRace == uint(IStatController.Race.HUMAN)) {
return dmg - _calcDmgInline(dmg, defenderInfo, IStatController.ATTRIBUTES.DEF_AGAINST_HUMAN);
} else if (attackerRace == uint(IStatController.Race.UNDEAD)) {
return dmg - _calcDmgInline(dmg, defenderInfo, IStatController.ATTRIBUTES.DEF_AGAINST_UNDEAD);
} else if (attackerRace == uint(IStatController.Race.DAEMON)) {
return dmg - _calcDmgInline(dmg, defenderInfo, IStatController.ATTRIBUTES.DEF_AGAINST_DAEMON);
} else if (attackerRace == uint(IStatController.Race.BEAST)) {
return dmg - _calcDmgInline(dmg, defenderInfo, IStatController.ATTRIBUTES.DEF_AGAINST_BEAST);
} else {
return dmg;
}
}
/// @notice Calculate damage after Melee-attack
function increaseMeleeDmgByFactor(int32 dmg, IFightCalculator.FighterInfo memory attackerInfo) internal pure returns (int32){
return dmg + _getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.MELEE_DMG_FACTOR) * dmg / RESISTANCE_DENOMINATOR;
}
/// @notice Calculate damage after Magic-attack
function increaseMagicDmgByFactor(int32 dmg, IFightCalculator.FighterInfo memory attackerInfo, IItemController.AttackType aType) internal pure returns (int32) {
if (aType == IItemController.AttackType.FIRE) {
return dmg + dmg * _getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.FIRE_DMG_FACTOR) / RESISTANCE_DENOMINATOR;
} else if (aType == IItemController.AttackType.COLD) {
return dmg + dmg * _getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.COLD_DMG_FACTOR) / RESISTANCE_DENOMINATOR;
} else if (aType == IItemController.AttackType.LIGHTNING) {
return dmg + dmg * _getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.LIGHTNING_DMG_FACTOR) / RESISTANCE_DENOMINATOR;
} else {
return dmg;
}
}
/// @notice Reduce damage depending on value of Damage Reduction attribute
function decreaseDmgByDmgReduction(int32 dmg, IFightCalculator.FighterInfo memory defenderInfo) internal pure returns (int32) {
return dmg - _calcDmgInline(dmg, defenderInfo, IStatController.ATTRIBUTES.DAMAGE_REDUCTION);
}
/// @notice Calculate poison damage < {health}
function poisonDmg(int32 health, IFightCalculator.Statuses memory statuses) internal pure returns (int32) {
// poison should not kill
if (statuses.poison && health.toUint() > 1) {
// at least 1 dmg
return int32(int(Math.max(health.toUint() / 10, 1)));
}
return 0;
}
/// @notice Reduce health of the fighters according to attacks results, calc damagePoison, damage and damageReflect.
function reduceHp(
AttackResult memory firstAttack,
AttackResult memory secondAttack,
IFightCalculator.Fighter memory firstFighter,
IFightCalculator.Fighter memory secondFighter
) internal pure {
secondFighter.health = firstAttack.defenderHealth;
firstFighter.damage = firstAttack.damage;
// hit only if second fighter survived
if (secondFighter.health != 0) {
firstFighter.health = secondAttack.defenderHealth;
secondFighter.damage = secondAttack.damage;
// reflect damage from second to first
secondFighter.damageReflect = (CalcLib.minI32(firstAttack.reflectDamage, firstFighter.health));
firstFighter.health -= secondFighter.damageReflect;
// reflect damage from first to second
firstFighter.damageReflect = (CalcLib.minI32(secondAttack.reflectDamage, secondFighter.health));
secondFighter.health -= firstFighter.damageReflect;
}
// poison second firstly (he got damage and statuses early)
firstFighter.damagePoison = poisonDmg(secondFighter.health, secondFighter.statuses);
secondFighter.health -= firstFighter.damagePoison;
// poison first fighter
secondFighter.damagePoison = poisonDmg(firstFighter.health, firstFighter.statuses);
firstFighter.health -= secondFighter.damagePoison;
}
/// @notice Calculate life-stolen-per-hit value for the given {damage} value
function lifeStolenPerHit(int32 dmg, IFightCalculator.FighterInfo memory attackerInfo) internal pure returns (int32) {
return dmg * _getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.LIFE_STOLEN_PER_HIT) / RESISTANCE_DENOMINATOR;
}
/// @notice Increase {fighter.health} on the value of life-stolen-per-hit (only if the health > 0)
function stealLife(IFightCalculator.Fighter memory fighter, AttackResult memory attackResult) internal pure {
if (fighter.health != 0) {
int32 newHealth = fighter.health + attackResult.lifeStolen;
int32 maxHealth = _getAttrValue(fighter.info.fighterAttributes, IStatController.ATTRIBUTES.LIFE);
fighter.health = (CalcLib.minI32(newHealth, maxHealth));
}
}
function skipTurn(IFightCalculator.FightInfoInternal memory fResult, bool isA) internal pure returns (bool) {
return isA ? fResult.fighterA.statuses.stun : fResult.fighterB.statuses.stun;
}
/// @notice Detect which hero is faster and makes the hit first. Magic is faster melee.
/// Otherwise first hit is made by the fighter with higher attack rating (A is selected if the ratings are equal)
function calcFirstHit(IFightCalculator.FightInfoInternal memory fInfo) internal pure returns (bool aFirst){
if (fInfo.fighterA.info.attackType == IFightCalculator.AttackType.MAGIC) {
if (fInfo.fighterB.info.attackType == IFightCalculator.AttackType.MAGIC) {
// if both fighters use magic we check attack rating
aFirst = isAttackerFaster(fInfo.fighterA.info, fInfo.fighterB.info);
} else {
// otherwise, magic always faster than melee
aFirst = true;
}
} else {
if (fInfo.fighterB.info.attackType == IFightCalculator.AttackType.MAGIC) {
// if fighter use magic he will be faster
aFirst = false;
} else {
// otherwise, check attack rating
aFirst = isAttackerFaster(fInfo.fighterA.info, fInfo.fighterB.info);
}
}
}
function isAttackerFaster(
IFightCalculator.FighterInfo memory fighterAInfo,
IFightCalculator.FighterInfo memory fighterBInfo
) internal pure returns (bool) {
return _getAttrValue(fighterAInfo.fighterAttributes, IStatController.ATTRIBUTES.ATTACK_RATING)
>= _getAttrValue(fighterBInfo.fighterAttributes, IStatController.ATTRIBUTES.ATTACK_RATING);
}
function reflectMeleeDmg(int32 dmg, IFightCalculator.FighterInfo memory defenderInfo) internal pure returns (int32) {
return dmg * _getAttrValue(defenderInfo.fighterAttributes, IStatController.ATTRIBUTES.REFLECT_DAMAGE_MELEE) / RESISTANCE_DENOMINATOR;
}
function reflectMagicDmg(int32 dmg, IFightCalculator.FighterInfo memory defenderInfo) internal pure returns (int32) {
return dmg * _getAttrValue(defenderInfo.fighterAttributes, IStatController.ATTRIBUTES.REFLECT_DAMAGE_MAGIC) / RESISTANCE_DENOMINATOR;
}
function _getChance(
IFightCalculator.FighterInfo memory attackerInfo,
IItemController.AttackType aType,
IStatController.ATTRIBUTES index,
int32 resist
) internal pure returns (int32 chance) {
int32 chanceBase = attackerInfo.fighterAttributes[uint(index)];
if (attackerInfo.attackType == IFightCalculator.AttackType.MAGIC) {
if (index == IStatController.ATTRIBUTES.BURN && aType == IItemController.AttackType.FIRE) {
chanceBase += int32(20);
}
if (index == IStatController.ATTRIBUTES.FREEZE && aType == IItemController.AttackType.COLD) {
chanceBase += int32(20);
}
if (index == IStatController.ATTRIBUTES.CONFUSE && aType == IItemController.AttackType.LIGHTNING) {
chanceBase += int32(20);
}
}
chance = _getAdjustedAttributeValue(chanceBase, index);
return chance - chance * (CalcLib.minI32(resist, _MAX_RESIST)) / RESISTANCE_DENOMINATOR;
}
/// @param randomValue Result of call _pseudoRandom, value in the range [0...RESISTANCE_DENOMINATOR)
function isCriticalHit(
IFightCalculator.FighterInfo memory attackerInfo,
uint randomValue
) internal pure returns (bool) {
return randomValue < _getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.CRITICAL_HIT).toUint();
}
/// @param randomValue Result of call CalcLib.pseudoRandom(1e18)
function reflectChaos(
IItemController.AttackInfo memory magicAttack,
IFightCalculator.FighterInfo memory attackerInfo,
uint randomValue
) internal pure returns (int32) {
return (magicAttack.aType == IItemController.AttackType.CHAOS && randomValue > 5e17)
? int32(attackerInfo.fighterStats.life) / int32(2)
: int32(0);
}
function _calcDmgInline(int32 dmg, IFightCalculator.FighterInfo memory info, IStatController.ATTRIBUTES index) internal pure returns (int32) {
return dmg * (CalcLib.minI32(_getAttrValue(info.fighterAttributes, index), _MAX_RESIST)) / RESISTANCE_DENOMINATOR;
}
function getMagicDamage(
IFightCalculator.FighterInfo memory attackerInfo,
IItemController.AttackInfo memory mAttack,
uint randomValue_
) internal pure returns (int32) {
int32 attributeFactorResult = (_getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.STRENGTH) * mAttack.attributeFactors.strength / 100);
attributeFactorResult += (_getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.DEXTERITY) * mAttack.attributeFactors.dexterity / 100);
attributeFactorResult += (_getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.VITALITY) * mAttack.attributeFactors.vitality / 100);
attributeFactorResult += (_getAttrValue(attackerInfo.fighterAttributes, IStatController.ATTRIBUTES.ENERGY) * mAttack.attributeFactors.energy / 100);
return int32(int(randomValue_)) + attributeFactorResult;
}
//endregion ------------------------ Pure utils
//region ------------------------ SIP-002
/// @notice SIP-002: Implement smooth increase that approaches to y0 but never reaches that value
/// @dev https://discord.com/channels/1134537718039318608/1265261881652674631
/// @param y0 is desired capacity, 90 for resists/defs, 100 for critical hit and statuses
/// @param x current value, base attribute. Assume x >= 0
/// @param k is the factor of how fast the value will reach 90 capacity, k=100 by default
/// @return new attribute value that is used in calculations, decimals 18
function getReducedValue(uint y0, uint x, uint k) internal pure returns (uint) {
// 2^n = exp(ln(2^n)) = exp(n * ln2)
int t = FixedPointMathLib.expWad(-int(x) * LN2 / int(k));
return t < 0
? 0 // some mistake happens (???)
: y0 * (1e18 - uint(t));
}
/// @notice Apply {getReducedValue} to the given attribute, change value in place
function _getAdjustedValue(int32 attributeValue, uint y0, uint k) internal pure returns (int32) {
return attributeValue <= 0
? int32(0) // negative values => 0
: int32(int(getReducedValue(y0, uint(int(attributeValue)), k) / 1e18));
}
/// @notice Return adjusted attribute value. Adjust selected attributes using y=z(1−2^(−x/k)) formula
/// Value in array {attributes} is NOT changed.
function _getAttrValue(int32[] memory attributes, IStatController.ATTRIBUTES attrId) internal pure returns (int32) {
return _getAdjustedAttributeValue(attributes[uint(attrId)], attrId);
}
function _getAdjustedAttributeValue(int32 value, IStatController.ATTRIBUTES attrId) internal pure returns (int32) {
if (
attrId == IStatController.ATTRIBUTES.BLOCK_RATING
|| attrId == IStatController.ATTRIBUTES.FIRE_RESISTANCE
|| attrId == IStatController.ATTRIBUTES.COLD_RESISTANCE
|| attrId == IStatController.ATTRIBUTES.LIGHTNING_RESISTANCE
|| attrId == IStatController.ATTRIBUTES.DEF_AGAINST_HUMAN
|| attrId == IStatController.ATTRIBUTES.DEF_AGAINST_UNDEAD
|| attrId == IStatController.ATTRIBUTES.DEF_AGAINST_DAEMON
|| attrId == IStatController.ATTRIBUTES.DEF_AGAINST_BEAST
|| attrId == IStatController.ATTRIBUTES.DAMAGE_REDUCTION
|| attrId == IStatController.ATTRIBUTES.RESIST_TO_STATUSES
) {
// use CAPACITY_RESISTS_DEFS, K_FACTOR
return _getAdjustedValue(value, CAPACITY_RESISTS_DEFS, K_FACTOR);
} else if (
attrId == IStatController.ATTRIBUTES.CRITICAL_HIT
|| attrId == IStatController.ATTRIBUTES.STUN
|| attrId == IStatController.ATTRIBUTES.BURN
|| attrId == IStatController.ATTRIBUTES.FREEZE
|| attrId == IStatController.ATTRIBUTES.CONFUSE
|| attrId == IStatController.ATTRIBUTES.CURSE
|| attrId == IStatController.ATTRIBUTES.POISON
) {
// use CAPACITY_CRITICAL_HIT_STATUSES, K_FACTOR
return _getAdjustedValue(value, CAPACITY_CRITICAL_HIT_STATUSES, K_FACTOR);
} else {
return value;
}
}
//endregion ------------------------ SIP-002
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../openzeppelin/EnumerableSet.sol";
import "../interfaces/IGOC.sol";
import "../interfaces/IAppErrors.sol";
import "../interfaces/IApplicationEvents.sol";
import "../proxy/Controllable.sol";
import "../lib/PackingLib.sol";
import "../lib/EventLib.sol";
import "../lib/StoryLib.sol";
import "../lib/MonsterLib.sol";
import "../lib/GOCLib.sol";
interface ArbSys {
function arbBlockNumber() external view returns (uint256);
}
library GameObjectControllerLib {
using EnumerableSet for EnumerableSet.UintSet;
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 ------------------------ CONSTANTS
/// @dev keccak256(abi.encode(uint256(keccak256("game.object.controller.main")) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant MAIN_STORAGE_LOCATION = 0xfa9e067a92ca4a9057b7b4465a8f29d633e1758238bd3a4a8ec5d0f904f6b900;
//endregion ------------------------ CONSTANTS
//region ------------------------ RESTRICTIONS
function onlyDeployer(IController controller) internal view {
if (!controller.isDeployer(msg.sender)) revert IAppErrors.ErrorNotDeployer(msg.sender);
}
function onlyDungeonFactory(IController c) internal view {
if (c.dungeonFactory() != msg.sender) revert IAppErrors.ErrorNotDungeonFactory(msg.sender);
}
//endregion ------------------------ RESTRICTIONS
//region ------------------------ VIEWS
function _S() internal pure returns (IGOC.MainState storage s) {
assembly {
s.slot := MAIN_STORAGE_LOCATION
}
return s;
}
function getObjectMeta(uint32 objectId) internal view returns (uint8 biome, uint8 objectSubType) {
return GOCLib.unpackObjectMeta(_S().objectMeta[objectId]);
}
function isAvailableForHero(IController controller, address heroToken, uint heroTokenId, uint32 objId) internal view returns (bool) {
(, uint8 objectSubType) = getObjectMeta(objId);
return GOCLib.isAvailableForHero(IStoryController(controller.storyController()), objId, objectSubType, heroToken, heroTokenId);
}
function isBattleObject(uint32 objectId) internal view returns (bool) {
(,uint8 objectSubType) = GOCLib.unpackObjectMeta(_S().objectMeta[objectId]);
return GOCLib.getObjectTypeBySubType(IGOC.ObjectSubType(objectSubType)) == IGOC.ObjectType.MONSTER;
}
function getObjectTypeBySubType(uint32 objectId) internal view returns (IGOC.ObjectType) {
(,uint8 objectSubType) = GOCLib.unpackObjectMeta(_S().objectMeta[objectId]);
return GOCLib.getObjectTypeBySubType(IGOC.ObjectSubType(objectSubType));
}
function getMonsterInfo(address hero, uint heroId, uint32 objectId) internal view returns (IGOC.MonsterGenInfo memory mGenInfo, IGOC.GeneratedMonster memory gen) {
uint iteration = _S().iterations[hero.packIterationKey(uint64(heroId), objectId)];
mGenInfo = MonsterLib.unpackMonsterInfo(_S().monsterInfos[objectId]);
gen = MonsterLib.unpackGeneratedMonster(_S().monsterInfos[objectId]._generatedMonsters[hero.packNftId(heroId)][iteration]);
}
function getIteration(address heroToken, uint heroTokenId, uint32 objId) internal view returns (uint) {
return _S().iterations[_iterationKey(heroToken, heroTokenId, objId)];
}
function getLastHeroFightTs(address heroToken, uint heroTokenId) internal view returns (uint) {
return _S().lastHeroFightTs[heroToken.packNftId(heroTokenId)];
}
function getFightDelay() internal view returns (uint) {
return _S().fightDelay;
}
function getStoryId(uint32 objectId) internal view returns (uint16) {
return _S().storyIds[objectId];
}
function getEventInfo(uint32 objectId) internal view returns (IGOC.EventInfo memory) {
return _S().eventInfos[objectId];
}
function getObjectIds(uint8 biome, IGOC.ObjectSubType subType) internal view returns (uint[] memory) {
return _S().objectIds[GOCLib.packObjectMeta(biome, uint8(subType))].values();
}
//endregion ------------------------ VIEWS
//region ------------------------ REGISTRATION
function registerEvent(IController controller, IGOC.EventRegInfo memory regInfo) internal {
onlyDeployer(controller);
_checkMintItems(regInfo.mintItems, regInfo.mintItemsChances);
uint32 objectId = _registerMetaId(regInfo.biome, regInfo.subType, regInfo.eventId);
EventLib.eventRegInfoToInfo(regInfo, _S().eventInfos[objectId]);
emit IApplicationEvents.EventRegistered(objectId, regInfo);
}
function registerStory(IController controller, uint16 storyId, uint8 biome, IGOC.ObjectSubType subType) internal {
onlyDeployer(controller);
uint32 objectId = _registerMetaId(biome, subType, storyId);
_S().storyIds[objectId] = storyId;
emit IApplicationEvents.StoryRegistered(objectId, storyId);
}
function registerMonster(IController controller, IGOC.MonsterGenInfo memory monsterGenInfo) internal {
onlyDeployer(controller);
_checkMintItems(monsterGenInfo.mintItems, monsterGenInfo.mintItemsChances);
uint32 objectId = _registerMetaId(monsterGenInfo.biome, monsterGenInfo.subType, monsterGenInfo.monsterId);
delete _S().monsterInfos[objectId];
MonsterLib.packMonsterInfo(monsterGenInfo, _S().monsterInfos[objectId]);
emit IApplicationEvents.MonsterRegistered(objectId, monsterGenInfo);
}
function removeObject(IController controller, uint32 objectId) internal {
onlyDeployer(controller);
bytes32 meta = _S().objectMeta[objectId];
delete _S().objectMeta[objectId];
_S().objectIds[meta].remove(objectId);
emit IApplicationEvents.ObjectRemoved(objectId);
}
//endregion ------------------------ REGISTRATION
//region ------------------------ OBJECT ACTIONS
/// @param cTypes Array of object subtypes, see IGOC.ObjectSubType.XXX
/// @param chances Chances in range 0-1e9, chances are corresponded to {cTypes} array
function getRandomObject(
IController c,
uint8[] memory cTypes,
uint32[] memory chances,
uint8 biome,
address heroToken,
uint heroTokenId
) internal returns (uint32 objectId) {
onlyDungeonFactory(c);
return GOCLib.getRandomObject(
_S(),
IStoryController(c.storyController()),
cTypes,
chances,
biome,
heroToken,
heroTokenId
);
}
/// @notice Open {object}: increase iteration, [generate monsters]
function open(IController c, address heroToken, uint heroTokenId, uint32 objectId) internal returns (uint iteration) {
onlyDungeonFactory(c);
iteration = _increaseIteration(heroToken, heroTokenId, objectId);
(, uint8 objectSubType) = getObjectMeta(objectId);
IHeroController.HeroInfo memory heroInfo = IHeroController(c.heroController()).getHeroInfo(heroToken, heroTokenId);
uint8 t = uint8(GOCLib.getObjectTypeBySubType(IGOC.ObjectSubType(objectSubType)));
if (t == uint8(IGOC.ObjectType.EVENT)) {
// noop
} else if (t == uint8(IGOC.ObjectType.MONSTER)) {
MonsterLib.initialGeneration(
_S().monsterInfos[objectId],
heroToken,
heroTokenId,
iteration,
heroInfo.ngLevel
);
} else if (t == uint8(IGOC.ObjectType.STORY)) {
// noop
} else {
revert IAppErrors.UnknownObjectTypeGocLib1(t);
}
}
/// @notice Execute event/story/monster action
/// @param data Object type-specified data packed using abi.encode.
/// For events: bool (accept / not accept results)
/// For monsters: AttackInfo
/// For story: bytes32 (answer id)
function action(
IController c,
address sender,
uint64 dungeonId,
uint32 objectId,
address heroToken,
uint heroTokenId,
uint8 stageId,
bytes memory data
) internal returns (IGOC.ActionResult memory) {
onlyDungeonFactory(c);
IGOC.ActionContext memory ctx;
ctx.objectId = objectId;
ctx.sender = sender;
ctx.heroToken = heroToken;
ctx.heroTokenId = heroTokenId;
ctx.stageId = stageId;
ctx.data = data;
(ctx.biome, ctx.objectSubType) = getObjectMeta(objectId);
ctx.heroNgLevel = IHeroController(c.heroController()).getHeroInfo(heroToken, heroTokenId).ngLevel;
ctx.dungeonId = dungeonId;
ctx.iteration = _S().iterations[_iterationKey(heroToken, heroTokenId, objectId)];
ctx.controller = c;
IGOC.ActionResult memory r;
uint8 t = uint8(GOCLib.getObjectTypeBySubType(IGOC.ObjectSubType(ctx.objectSubType)));
ctx.salt = block.number;
// for L2 chains need to get correct block number from precompiled contracts
if(block.chainid == uint(111188)) {
ctx.salt = ArbSys(address(100)).arbBlockNumber();
}
if (t == uint8(IGOC.ObjectType.EVENT)) {
r = EventLib.action(ctx, _S().eventInfos[objectId]);
} else if (t == uint8(IGOC.ObjectType.MONSTER)) {
_checkAndRefreshFightTs(heroToken, heroTokenId);
(r, ctx.salt) = MonsterLib.action(ctx, _S().monsterInfos[objectId]);
} else if (t == uint8(IGOC.ObjectType.STORY)) {
r = StoryLib.action(ctx, _S().storyIds[objectId]);
} else {
revert IAppErrors.UnknownObjectTypeGocLib2(t);
}
r.objectId = ctx.objectId;
r.heroToken = heroToken;
r.heroTokenId = heroTokenId;
r.iteration = ctx.iteration;
emit IApplicationEvents.ObjectResultEvent(
dungeonId,
objectId,
IGOC.ObjectType(t),
heroToken,
heroTokenId,
stageId,
ctx.iteration,
data,
r,
ctx.salt
);
return r;
}
//endregion ------------------------ OBJECT ACTIONS
//region ------------------------ Utils
/// @notice Generate object ID using (biome, subType, id)
/// @param biome Biome to which the object belongs. [1..99]
/// @param subType Subtype of the object, see IGOC.ObjectSubType.XXX. [1..99]
/// @param id Id of the event / story / monster. [1..10_000]
function _genObjectId(uint8 biome, uint8 subType, uint16 id) internal pure returns (uint32 objectId) {
if (biome == 0 || subType == 0 || id == 0) revert IAppErrors.ZeroValueNotAllowed();
if (biome >= 100) revert IAppErrors.GenObjectIdBiomeOverflow(biome);
if (uint(subType) >= 100) revert IAppErrors.GenObjectIdSubTypeOverflow(subType);
if (id > 10_000) revert IAppErrors.GenObjectIdIdOverflow(id);
objectId = uint32(biome) * 1_000_000 + uint32(subType) * 10_000 + uint32(id);
}
/// @notice Register the object in objectMeta and objectIds
/// @param biome Biome to which the object belongs. [1..99]
/// @param subType Subtype of the object, [1..99]
/// @param id Id of the event / story / monster. [1..10_000]
/// @return objectId Object id generated by {_genObjectId}
function _registerMetaId(uint8 biome, IGOC.ObjectSubType subType, uint16 id) internal returns (uint32 objectId) {
IGOC.MainState storage s = _S();
objectId = _genObjectId(biome, uint8(subType), id);
bytes32 meta = GOCLib.packObjectMeta(biome, uint8(subType));
s.objectMeta[objectId] = meta;
s.objectIds[meta].add(objectId);
}
/// @notice Update last-hero-fight-timestamp
function _checkAndRefreshFightTs(address heroToken, uint heroTokenId) internal {
IGOC.MainState storage s = _S();
bytes32 key = heroToken.packNftId(heroTokenId);
if (s.lastHeroFightTs[key] + s.fightDelay > block.timestamp) revert IAppErrors.FightDelay();
s.lastHeroFightTs[key] = block.timestamp;
}
function _increaseIteration(address heroToken, uint heroTokenId, uint32 objId) internal returns (uint iteration) {
IGOC.MainState storage s = _S();
bytes32 key = _iterationKey(heroToken, heroTokenId, objId);
iteration = s.iterations[key] + 1;
s.iterations[key] = iteration;
}
function _iterationKey(address heroToken, uint heroTokenId, uint32 objId) internal pure returns (bytes32) {
return heroToken.packIterationKey(uint64(heroTokenId), objId);
}
/// @notice Validate passed {mintItems_} and {mintItemsChances_}
function _checkMintItems(address[] memory mintItems_, uint32[] memory mintItemsChances_) internal pure {
uint length = mintItems_.length;
if (mintItemsChances_.length != length) revert IAppErrors.LengthsMismatch();
for (uint i; i < length; ++i) {
if (mintItems_[i] == address(0)) revert IAppErrors.ZeroAddress();
if (mintItemsChances_[i] == 0) revert IAppErrors.ZeroChance();
if (mintItemsChances_[i] > CalcLib.MAX_CHANCE) revert IAppErrors.TooHighChance(mintItemsChances_[i]);
}
}
//endregion ------------------------ Utils
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../interfaces/IAppErrors.sol";
import "../interfaces/IGOC.sol";
import "../interfaces/IStoryController.sol";
import "./CalcLib.sol";
import "./PackingLib.sol";
library GOCLib {
using EnumerableSet for EnumerableSet.UintSet;
using PackingLib for address;
/// @param cTypes Array of object subtypes, see IGOC.ObjectSubType.XXX
/// @param chances Chances in range 0-1e9, chances are corresponded to {cTypes} array
function getRandomObject(
IGOC.MainState storage s,
IStoryController sc,
uint8[] memory cTypes,
uint32[] memory chances,
uint8 biome,
address heroToken,
uint heroTokenId
) internal returns (uint32 objectId) {
uint8 cType = _getObjectType(cTypes, chances, CalcLib.pseudoRandom);
EnumerableSet.UintSet storage objects = s.objectIds[packObjectMeta(biome, cType)];
uint len = objects.length();
if (len == 0) revert IAppErrors.EmptyObjects();
uint objectArrayIdx = len == 1
? 0
: CalcLib.pseudoRandom(len - 1);
EnumerableSet.UintSet storage played = s.playedObjects[heroToken.packPlayedObjKey(uint64(heroTokenId), cType, biome)];
objectId = _searchObject(sc, len, objects, played, objectArrayIdx, false, heroToken, heroTokenId, cType);
if (objectId == 0) revert IAppErrors.ObjectNotFound();
played.add(objectId);
}
/// @notice Select cType using pseudo-random value according to the given {chances}
/// @param cTypes Zero values are ignored.
/// @param chances [0..100], decimals 9. At least once item should have value 100 to avoid {UnknownObjectType} error.
/// @param random_ CalcLib.pseudoRandom, required for unit tests
function _getObjectType(
uint8[] memory cTypes,
uint32[] memory chances,
function (uint) internal view returns (uint) random_
) internal view returns (uint8 cType) {
uint len = cTypes.length;
if (len == 0 || len != chances.length) revert IAppErrors.WrongGetObjectTypeInput();
if (len == 1) {
cType = cTypes[0];
} else {
uint random = random_(CalcLib.MAX_CHANCE);
uint minChance = CalcLib.MAX_CHANCE + 1;
for (uint i; i < len; ++i) {
// obj set can contain empty values, ignore them
if (cTypes[i] == 0) continue;
if (chances[i] > CalcLib.MAX_CHANCE) revert IAppErrors.WrongChances(chances[i], CalcLib.MAX_CHANCE);
if ((CalcLib.MAX_CHANCE - chances[i]) <= random) {
if (chances[i] < minChance) {
minChance = chances[i];
cType = cTypes[i];
}
}
}
}
if (cType == 0) revert IAppErrors.UnknownObjectTypeGoc1(0);
return cType;
}
/// @notice Find first object in {objects} available for the hero starting from {objArrayIdx}
/// If object not found clear {played} and try to search again.
/// @param lenObjects Length of {objects}
/// @param objArrayIdx Start index in objects
/// @param cType Object subtype
/// @param skipPlayed true - don't check if the found object was already played
/// @return objectId ID of the found object or 0 if the object is not found
function _searchObject(
IStoryController sc,
uint lenObjects,
EnumerableSet.UintSet storage objects,
EnumerableSet.UintSet storage played,
uint objArrayIdx,
bool skipPlayed,
address heroToken,
uint heroTokenId,
uint8 cType
) internal returns (uint32 objectId) {
// clear played objects if we played them all at the current biome
if (played.length() >= lenObjects) {
skipPlayed = true;
_clearPlayedObjects(played);
}
bool foundValid;
unchecked {
// search in a loop available objects
for (uint i; i < lenObjects; ++i) {
if (objArrayIdx >= lenObjects) {
objArrayIdx = 0;
}
uint32 objId = uint32(objects.at(objArrayIdx));
if (
isAvailableForHero(sc, objId, cType, heroToken, heroTokenId)
&& (skipPlayed || !played.contains(objId))
) {
foundValid = true;
objectId = objId;
break;
}
++objArrayIdx;
}
}
// in case when we do not have available objects it is possible they are not eligible and need to reset counter
if (!foundValid && !skipPlayed) {
_clearPlayedObjects(played);
objectId = _searchObject(sc, lenObjects, objects, played, objArrayIdx, true, heroToken, heroTokenId, cType);
}
return objectId;
}
function _clearPlayedObjects(EnumerableSet.UintSet storage played) internal {
uint[] memory values = played.values();
for (uint i; i < values.length; ++i) {
played.remove(values[i]);
}
}
/// @notice Check if the object subtype is available for the hero
function isAvailableForHero(IStoryController sc, uint32 objId, uint8 objectSubType, address hero, uint heroId) internal view returns (bool) {
IGOC.ObjectType objType = getObjectTypeBySubType(IGOC.ObjectSubType(objectSubType));
if (objType == IGOC.ObjectType.EVENT) {
// no checks
return true;
} else if (objType == IGOC.ObjectType.MONSTER) {
// no checks
return true;
} else if (objType == IGOC.ObjectType.STORY) {
return sc.isStoryAvailableForHero(objId, hero, heroId);
} else {
// actually, this case is impossible, getObjectTypeBySubType will revert above if objectSubType is incorrect
revert IAppErrors.UnknownObjectTypeForSubtype(objectSubType);
}
}
function packObjectMeta(uint8 biome, uint8 oType) internal pure returns (bytes32) {
return PackingLib.packUint8Array3(biome, oType, 0);
}
function unpackObjectMeta(bytes32 data) internal pure returns (uint8 biome, uint8 oType) {
(biome, oType,) = PackingLib.unpackUint8Array3(data);
}
/// @notice Get object type for the given {subType}
function getObjectTypeBySubType(IGOC.ObjectSubType subType) internal pure returns (IGOC.ObjectType) {
if (
subType == IGOC.ObjectSubType.SHRINE_4
|| subType == IGOC.ObjectSubType.CHEST_5
|| subType == IGOC.ObjectSubType.SHRINE_UNIQUE_8
) {
return IGOC.ObjectType.EVENT;
} else if (
subType == IGOC.ObjectSubType.ENEMY_NPC_1
|| subType == IGOC.ObjectSubType.ENEMY_NPC_SUPER_RARE_2
|| subType == IGOC.ObjectSubType.BOSS_3
|| subType == IGOC.ObjectSubType.ENEMY_NPC_UNIQUE_10
|| subType == IGOC.ObjectSubType.ENEMY_NPC_INSIDE_32
|| subType == IGOC.ObjectSubType.ENEMY_NPC_INSIDE_RARE_33
|| subType == IGOC.ObjectSubType.ENEMY_NPC_OUTSIDE_34
|| subType == IGOC.ObjectSubType.ENEMY_NPC_OUTSIDE_RARE_35
) {
return IGOC.ObjectType.MONSTER;
} else if (
subType == IGOC.ObjectSubType.STORY_6
|| subType == IGOC.ObjectSubType.STORY_UNIQUE_7
|| subType == IGOC.ObjectSubType.STORY_ON_ROAD_11
|| subType == IGOC.ObjectSubType.STORY_UNDERGROUND_12
|| subType == IGOC.ObjectSubType.STORY_NIGHT_CAMP_13
|| subType == IGOC.ObjectSubType.STORY_MOUNTAIN_14
|| subType == IGOC.ObjectSubType.STORY_WATER_15
|| subType == IGOC.ObjectSubType.STORY_CASTLE_16
|| subType == IGOC.ObjectSubType.STORY_HELL_17
|| subType == IGOC.ObjectSubType.STORY_SPACE_18
|| subType == IGOC.ObjectSubType.STORY_WOOD_19
|| subType == IGOC.ObjectSubType.STORY_CATACOMBS_20
|| subType == IGOC.ObjectSubType.STORY_BAD_HOUSE_21
|| subType == IGOC.ObjectSubType.STORY_GOOD_TOWN_22
|| subType == IGOC.ObjectSubType.STORY_BAD_TOWN_23
|| subType == IGOC.ObjectSubType.STORY_BANDIT_CAMP_24
|| subType == IGOC.ObjectSubType.STORY_BEAST_LAIR_25
|| subType == IGOC.ObjectSubType.STORY_PRISON_26
|| subType == IGOC.ObjectSubType.STORY_SWAMP_27
|| subType == IGOC.ObjectSubType.STORY_INSIDE_28
|| subType == IGOC.ObjectSubType.STORY_OUTSIDE_29
|| subType == IGOC.ObjectSubType.STORY_INSIDE_RARE_30
|| subType == IGOC.ObjectSubType.STORY_OUTSIDE_RARE_31
) {
return IGOC.ObjectType.STORY;
} else {
revert IAppErrors.UnknownObjectTypeGoc2(uint8(subType));
}
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../interfaces/IAppErrors.sol";
import "../interfaces/IApplicationEvents.sol";
import "../interfaces/IGuildController.sol";
import "../interfaces/IItem.sol";
import "../interfaces/IItemController.sol";
import "../interfaces/IOracle.sol";
import "../openzeppelin/Math.sol";
import "../solady/LibPRNG.sol";
import "./CalcLib.sol";
import "./ControllerContextLib.sol";
import "./ShelterLib.sol";
import "./StatLib.sol";
library ItemLib {
using CalcLib for int32;
using PackingLib for address;
using PackingLib for bytes32;
using PackingLib for bytes32[];
using PackingLib for uint32[];
using PackingLib for int32[];
//region ------------------------ Data types
struct GenerateAttributesContext {
/// @notice True if max allowed amount of random attributes were reached inside {_prepareAttributes}
bool stopGenerateRandom;
/// @notice Flag - attribute was generated. The array matches to info.ids
bool[] usedIndexes;
/// @notice Ids of the generated attributes
uint8[] ids;
/// @notice Randomly selected values of the generated attributes
int32[] values;
/// @notice Counter of the stored values into {ids} and {values}
uint counter;
/// @notice Total number of random attributes that were generated inside {_prepareAttributes}
uint randomAttrCounter;
/// @notice Total sum of all {random} values for random attributes generated in {_prepareAttributes}
uint randomSum;
/// @notice Total sum of all chances of the random attributes generated in {_prepareAttributes}
uint chancesSum;
/// @notice Total number of random attributes that can be generated
uint totalRandomAttrsPossible;
}
struct MintItemInfo {
uint8 maxItems;
int32 magicFind;
int32 destroyItems;
uint32[] mintItemsChances;
IOracle oracle;
address[] mintItems;
uint amplifier;
uint seed;
/// @notice Penalty to reduce chance as chance/delta if the hero not in his biome
/// @dev Use StatLib.mintDropChanceDelta
uint mintDropChanceDelta;
/// @notice SCR-1064: drop chance depends on NG_LEVEL, decimals 18, value is in the range [0...1e18]
/// it's always 100% for NG0 (no reduce, value is 1e18)
/// Use {dropChancePercent} to calculate actual value
uint mintDropChanceNgLevelMultiplier;
}
//endregion ------------------------ Data types
//region ------------------------ Restrictions
/// @notice ensure that the user belongs to a guild, the guild has a shelter, the shelter has highest level 3
function _onlyMemberOfGuildWithShelterMaxLevel(IController controller, address msgSender) internal view {
// ensure that signer belongs to a guild and the guild has a shelter of ANY level
IGuildController gc = IGuildController(controller.guildController());
uint guildId = gc.memberOf(msgSender);
if (guildId == 0) revert IAppErrors.NotGuildMember();
uint shelterId = gc.guildToShelter(guildId);
if (shelterId == 0) revert IAppErrors.GuildHasNoShelter();
// only highest level of shelters gives possibility to exit from dungeon
(, uint8 shelterLevel,) = PackingLib.unpackShelterId(shelterId);
if (shelterLevel != ShelterLib.MAX_SHELTER_LEVEL) revert IAppErrors.TooLowShelterLevel(shelterLevel, ShelterLib.MAX_SHELTER_LEVEL);
}
//endregion ------------------------ Restrictions
//region ------------------------ Main logic
/// @notice Mint new item, setup attributes, make extra setup if necessary (setup attack item, buff item)
/// @param sender Dungeon Factory / User Controller / Guild Controller are allowed
/// @param item Item to be minted
/// @param recipient The item is minted for the given recipient
/// @return itemId Id of the newly minted item
function mintNewItem(
IItemController.MainState storage s,
IController controller,
address sender,
address item,
address recipient
) external returns (uint itemId) {
ControllerContextLib.ControllerContext memory ctx = ControllerContextLib.init(controller);
address guildController = address(ControllerContextLib.getGuildController(ctx));
address shelterController = guildController == address(0)
? address(0)
: IGuildController(guildController).shelterController();
if (
address(ControllerContextLib.getDungeonFactory(ctx)) != sender
&& address(ControllerContextLib.getUserController(ctx)) != sender
&& guildController != sender
&& shelterController != sender
&& address(ControllerContextLib.getItemController(ctx)) != sender
&& address(ControllerContextLib.getHeroController(ctx)) != sender
) revert IAppErrors.MintNotAllowed();
itemId = IItem(item).mintFor(recipient);
IItemController.MintInfo memory info;
(
info.meta,
info.attributesIds,
info.attributesValues,
info.itemRarity
) = _setupNewAttributes(s, item, itemId, CalcLib.pseudoRandom);
// setup extra info
if (info.meta.itemMetaType == uint8(IItemController.ItemMetaType.ATTACK)) {
info.attackInfo = unpackItemAttackInfo(_setupNewAttackItem(s, item, itemId));
} else if (info.meta.itemMetaType == uint8(IItemController.ItemMetaType.BUFF)) {
(
info.casterIds,
info.casterValues,
info.targetIds,
info.targetValues
) = _setupNewBuffItem(s, item, itemId, CalcLib.pseudoRandom);
}
// consumable stats unchangeable, get them by address
emit IApplicationEvents.NewItemMinted(item, itemId, info);
}
/// @notice Mint random items, not more than {info.maxItems}
function mintRandomItems(MintItemInfo memory info) internal returns (address[] memory) {
return _mintRandomItems(info, CalcLib.nextPrng);
}
function applyActionMasks(
uint actionMask,
IStatController statController,
address heroToken,
uint heroTokenId
) external {
if ((actionMask & (2 ** uint(IItemController.ConsumableActionBits.CLEAR_TEMPORARY_ATTRIBUTES_0))) != 0) {
statController.clearTemporallyAttributes(heroToken, heroTokenId);
}
}
//endregion ------------------------ Main logic
//region ------------------------ Internal logic
/// @param nextPrng_ CalcLib.nextPrng, param is required by unit tests
function _mintRandomItems(
MintItemInfo memory info,
function (LibPRNG.PRNG memory, uint) internal view returns (uint) nextPrng_
) internal returns (address[] memory) {
// if hero is not in his biome do not mint at all
if (info.mintDropChanceDelta != 0) {
return new address[](0);
}
uint len = info.mintItems.length;
// Fisher–Yates shuffle
LibPRNG.PRNG memory prng = LibPRNG.PRNG(info.oracle.getRandomNumber(CalcLib.MAX_CHANCE, info.seed));
uint[] memory indices = new uint[](len);
for (uint i = 1; i < len; ++i) {
indices[i] = i;
}
LibPRNG.shuffle(prng, indices);
address[] memory minted = new address[](len);
uint mintedLength;
uint di = Math.min(CalcLib.toUint(info.destroyItems), 100);
for (uint i; i < len; ++i) {
if (info.mintItemsChances[indices[i]] > CalcLib.MAX_CHANCE) {
revert IAppErrors.TooHighChance(info.mintItemsChances[indices[i]]);
}
uint chance = _adjustChance(info.mintItemsChances[indices[i]], info, di);
// need to call random in each loop coz each minted item should have dedicated chance
uint rnd = nextPrng_(prng, CalcLib.MAX_CHANCE); // randomWithSeed_(CalcLib.MAX_CHANCE, rndSeed);
if (chance != 0 && (chance >= CalcLib.MAX_CHANCE || rnd < chance)) {
// There is no break here: the cycle is continued even if the number of the minted items reaches the max.
// The reason: gas consumption of success operation must be great of equal of the gas consumption of fail op.
if (mintedLength < info.maxItems) {
minted[i] = info.mintItems[indices[i]];
++mintedLength;
}
}
}
address[] memory mintedAdjusted = new address[](mintedLength);
uint j;
for (uint i; i < len; ++i) {
if (minted[i] != address(0)) {
mintedAdjusted[j] = minted[i];
++j;
}
}
return mintedAdjusted;
}
/// @notice Apply all corrections to the chance of item drop
/// There are two params to increase chances: amplifier and magicFind
/// There are two params to decrease chances: destroyItems and mintDropChanceNgLevelMultiplier
/// @param info Assume here, that info.mintDropChanceNgLevelMultiplier is in the range [0..1e18]
/// @param di Assume that di <= 100
function _adjustChance(uint32 itemChance, MintItemInfo memory info, uint di) internal pure returns (uint) {
uint chance = uint(itemChance) * Math.min(1e18, info.mintDropChanceNgLevelMultiplier) / 1e18;
chance += chance * info.amplifier / StatLib._MAX_AMPLIFIER;
chance += chance * CalcLib.toUint(info.magicFind) / 100;
chance -= chance * di / 100;
return chance;
}
function _setupNewAttributes(
IItemController.MainState storage s,
address item,
uint itemId,
function (uint) internal view returns (uint) random_
) internal returns (
IItemController.ItemMeta memory meta,
uint8[] memory ids,
int32[] memory values,
IItemController.ItemRarity itemRarity
){
meta = unpackedItemMeta(s.itemMeta[item]);
(ids, values, itemRarity) = _generateAttributes(unpackItemGenerateInfo(s.generateInfoAttributes[item]), meta, random_);
bytes32 packedItemId = item.packNftId(itemId);
if (ids.length != 0) {
s._itemAttributes[packedItemId] = values.toBytes32ArrayWithIds(ids);
}
s.itemInfo[packedItemId] = PackingLib.packItemInfo(uint8(itemRarity), 0, meta.baseDurability);
}
function _setupNewAttackItem(IItemController.MainState storage s, address item, uint itemId) internal returns (bytes32 attackInfo){
// we just write data for attack item, no need to generate, it will be augmented later so need individual data for itemId
attackInfo = s.generateInfoAttack[item];
s._itemAttackInfo[item.packNftId(itemId)] = attackInfo;
}
function _setupNewBuffItem(
IItemController.MainState storage s,
address item,
uint itemId,
function (uint) internal view returns (uint) random_
) internal returns (
uint8[] memory casterIds,
int32[] memory casterValues,
uint8[] memory targetIds,
int32[] memory targetValues
){
// CASTER
(casterIds, casterValues) = _generateSimpleAttributes(
unpackItemGenerateInfo(s.generateInfoCasterAttributes[item]),
true,
random_
);
if (casterIds.length != 0) {
s._itemCasterAttributes[item.packNftId(itemId)] = casterValues.toBytes32ArrayWithIds(casterIds);
}
// TARGET
(targetIds, targetValues) = _generateSimpleAttributes(
unpackItemGenerateInfo(s.generateInfoTargetAttributes[item]),
true,
random_
);
if (targetIds.length != 0) {
s._itemTargetAttributes[item.packNftId(itemId)] = targetValues.toBytes32ArrayWithIds(targetIds);
}
}
/// @notice Generate all mandatory attributes and try to generate required number of random attributes.
/// Generate at least {info.minRandomAttributes} of random attributes if it's possible
/// but not more than {info.maxRandomAttributes}. Value of each attribute is generated randomly according its chances.
/// @param meta Assume, that meta.min != 0, meta.max != 0 and both meta.min and meta.min should have same sign
/// because results value cannot be 0
/// @return ids Ids of the attributes, zero id is allowed
/// @return values Randomly generated attributes values, min <= value <= max
/// @return itemRarity Rarity of the item (Either meta.defaultRarity or calculated if there is no default rarity)
function _generateAttributes(
IItemController.ItemGenerateInfo memory info,
IItemController.ItemMeta memory meta,
function (uint) internal view returns (uint) random_
) internal view returns (
uint8[] memory ids,
int32[] memory values,
IItemController.ItemRarity itemRarity
) {
GenerateAttributesContext memory ctx;
uint len = info.ids.length;
if (len != 0) {
ctx.ids = new uint8[](len);
ctx.values = new int32[](len);
ctx.usedIndexes = new bool[](len);
// Fisher–Yates shuffle
_shuffleInfo(info, random_);
// initialize ctx by initial values
// generate all mandatory attributes, try to generate not more than {meta.maxRandomAttributes} random attributes
_prepareAttributes(info, meta.maxRandomAttributes, ctx, random_);
// generate missing random attributes if it's necessary, ctx.counter is incremented
_generateMissingRandomAttributes(info, meta.minRandomAttributes, ctx, random_);
itemRarity = meta.defaultRarity == 0
? _calculateRarity(ctx.randomSum, ctx.chancesSum, ctx.randomAttrCounter, meta.maxRandomAttributes)
: IItemController.ItemRarity(meta.defaultRarity);
} else {
itemRarity = IItemController.ItemRarity.UNKNOWN;
}
(ids, values) = _fixLengthsIdsValues(ctx.ids, ctx.values, ctx.counter);
}
/// @notice Generate missing random attributes if necessary
function _generateMissingRandomAttributes(
IItemController.ItemGenerateInfo memory info,
uint8 minRandomAttributes,
GenerateAttributesContext memory ctx,
function (uint) internal view returns (uint) random_
) internal view {
uint attrToGen = Math.min(ctx.totalRandomAttrsPossible, minRandomAttributes);
if (ctx.randomAttrCounter < attrToGen && ctx.totalRandomAttrsPossible > ctx.randomAttrCounter) {
// it's necessary AND possible to generate more random attributes
uint possibleRemainingAttrs = ctx.totalRandomAttrsPossible - ctx.randomAttrCounter;
uint remainingAttrsToGen = attrToGen - ctx.randomAttrCounter;
uint[] memory indicesToGen = new uint[](possibleRemainingAttrs);
uint indicesToGenCounter;
// enumerate all attributes, add all indices of not-generated attributes to {indexesToGen}
for (uint i; i < info.ids.length; ++i) {
// mandatory attrs should be already generated and no need to check
if (!ctx.usedIndexes[i]) {
indicesToGen[indicesToGenCounter] = i;
indicesToGenCounter++;
}
}
// Shuffle indices of not-generated attributes using Fisher–Yates shuffle
if (possibleRemainingAttrs > 1) {
for (uint i; i < possibleRemainingAttrs - 1; ++i) {
uint randomIndex = CalcLib.pseudoRandomInRangeFlex(i, possibleRemainingAttrs - 1, random_);
(indicesToGen[randomIndex], indicesToGen[i]) = (indicesToGen[i], indicesToGen[randomIndex]);
}
}
// Generate necessary amount of attributes. Fist (shuffled) attributes are selected (MAX_CHANCE is used for each)
for (uint i; i < remainingAttrsToGen; ++i) {
uint idx = indicesToGen[i];
(int32 attr,) = _generateAttribute(info.mins[idx], info.maxs[idx], CalcLib.MAX_CHANCE, random_);
ctx.ids[ctx.counter] = info.ids[idx];
ctx.values[ctx.counter] = attr;
ctx.counter++;
}
}
}
/// @notice Generate all mandatory attributes, generate not more than {meta.maxRandomAttributes} random attributes.
/// Updates context:
/// {ctx.totalRandomAttrsPossible} - total number of possible random attributes
/// {ctx.randomAttrCounter} - total number of generated random attributes <= {maxRandomAttributes}
/// {ctx.randomSum} = sum of random of all random attributes.
/// {ctx.chancesSum} = sum of chances of all random attributes.
/// {ctx.counter} = total number of generated attributes. Values of ctx.ids, ctx.values, ctx.usedIndexes are
/// initialized in the range [0...ctx.counter)
/// @param ctx Empty struct but arrays ids, values and usedIndexes should be allocated for info.ids.length items
function _prepareAttributes(
IItemController.ItemGenerateInfo memory info,
uint8 maxRandomAttributes,
GenerateAttributesContext memory ctx,
function (uint) internal view returns (uint) random_
) internal view {
uint len = info.ids.length;
for (uint i; i < len; ++i) {
if (info.chances[i] != CalcLib.MAX_CHANCE) {
ctx.totalRandomAttrsPossible++;
}
if (info.chances[i] >= CalcLib.MAX_CHANCE || !ctx.stopGenerateRandom) {
(int32 attr, uint random) = _generateAttribute(info.mins[i], info.maxs[i], info.chances[i], random_);
// count only random attributes for calc rarity
if (attr != 0) {
if (
info.chances[i] < CalcLib.MAX_CHANCE
// && random != 0 // commented: random = 0 can produce crash in _generateMissingRandomAttributes
) {
ctx.randomAttrCounter++;
ctx.randomSum += random;
ctx.chancesSum += info.chances[i];
}
ctx.ids[ctx.counter] = info.ids[i];
ctx.values[ctx.counter] = attr;
ctx.counter++;
ctx.usedIndexes[i] = true;
}
// it is a bit less fair random for attrs in the end of the list, however we assume it should be pretty rare case
if (ctx.randomAttrCounter == maxRandomAttributes) {
ctx.stopGenerateRandom = true;
}
}
}
}
/// @notice Shuffle info arrays using Fisher–Yates shuffle algo
function _shuffleInfo(
IItemController.ItemGenerateInfo memory info,
function (uint) internal view returns (uint) random_
) internal view {
uint len = info.ids.length;
if (len > 1) {
for (uint i; i < len - 1; i++) {
uint randomIndex = CalcLib.pseudoRandomInRangeFlex(i, len - 1, random_);
(info.ids[randomIndex], info.ids[i]) = (info.ids[i], info.ids[randomIndex]);
(info.mins[randomIndex], info.mins[i]) = (info.mins[i], info.mins[randomIndex]);
(info.maxs[randomIndex], info.maxs[i]) = (info.maxs[i], info.maxs[randomIndex]);
(info.chances[randomIndex], info.chances[i]) = (info.chances[i], info.chances[randomIndex]);
}
}
}
/// @notice Generate array [0,1,2.. N-1] and shuffle it using Fisher–Yates shuffle algo
function _shuffleIndices(
uint countItems,
function (uint) internal view returns (uint) random_
) internal view returns (uint[] memory indices){
indices = new uint[](countItems);
for (uint i = 1; i < countItems; ++i) {
indices[i] = i;
}
if (countItems > 1) {
for (uint i; i < countItems - 1; i++) {
uint randomIndex = CalcLib.pseudoRandomInRangeFlex(i, countItems - 1, random_);
(indices[randomIndex], indices[i]) = (indices[i], indices[randomIndex]);
}
}
}
/// @notice Reduce lengths of {ids} and {values} to {count}
function _fixLengthsIdsValues(uint8[] memory ids, int32[] memory values, uint count) internal pure returns (
uint8[] memory idsOut,
int32[] memory valuesOut
) {
if (count == ids.length) {
return (ids, values);
}
idsOut = new uint8[](count);
valuesOut = new int32[](count);
for (uint i; i < count; ++i) {
idsOut[i] = ids[i];
valuesOut[i] = values[i];
}
return (idsOut, valuesOut);
}
/// @param random_ Pass CalcLib.pseudoRandom here, param is required for unit tests. Max value is MAX_CHANCE
function _generateSimpleAttributes(
IItemController.ItemGenerateInfo memory info,
bool maxChance,
function (uint) internal view returns (uint) random_
) internal view returns (
uint8[] memory ids,
int32[] memory values
) {
uint len = info.ids.length;
ids = new uint8[](len);
values = new int32[](len);
uint n = 0;
for (uint i; i < len; ++i) {
(int32 attr,) = _generateAttribute(
info.mins[i],
info.maxs[i],
maxChance ? CalcLib.MAX_CHANCE : info.chances[i],
random_
);
if (attr != 0) {
ids[n] = info.ids[i];
values[n] = attr;
++n;
}
}
return _fixLengthsIdsValues(ids, values, n);
}
//endregion ------------------------ Internal logic
//region ------------------------ Internal utils
/// @param chance Chance in the range [0...MAX_CHANCE], MAX_CHANCE=1e9 means "mandatory" element.
/// @param random_ Pass CalcLib.pseudoRandom here, param is required for unit tests
/// @return attr Either 0 or min <= attr <= max
/// @return rnd Random value in the range [0...MAX_CHANCE]; It's always 0 for mandatory elements
function _generateAttribute(
int32 min,
int32 max,
uint32 chance,
function (uint) internal view returns (uint) random_
) internal view returns (
int32 attr,
uint rnd
) {
if (chance > CalcLib.MAX_CHANCE) revert IAppErrors.TooHighChance(chance);
uint diff = uint(CalcLib.absDiff(min, max));
if (chance < CalcLib.MAX_CHANCE) {
uint32 random = CalcLib.pseudoRandomUint32Flex(CalcLib.MAX_CHANCE, random_);
if (random < chance) {
uint r = uint(CalcLib.MAX_CHANCE - random * (CalcLib.MAX_CHANCE / chance));
int32 k = int32(int(r * diff / uint(CalcLib.MAX_CHANCE)));
return (min + k, random);
}
} else { // chance == CalcLib.MAX_CHANCE => mandatory element
if (diff == 0) {
return (min, 0);
} else {
uint r = uint(CalcLib.pseudoRandomUint32Flex(CalcLib.MAX_CHANCE, random_));
int32 k = int32(int(r % (diff + 1)));
// return zero random - no need to calc rarity for mandatory elements
return (min + k, 0);
}
}
return (0, 0);
}
/// @notice Calculate item rarity
/// @param randomSum Total sum random values of all random attributes in ItemGenerateInfo, [0...MAX_CHANCE/attrCounter]
/// @param chancesSum Total sum of all random chances in ItemGenerateInfo
/// @param attrCounter Count of random attributes in ItemGenerateInfo
/// @param maxAttr Index of max allowed random attribute (all attributes with higher indices are not random)
/// @return item rarity
function _calculateRarity(uint randomSum, uint chancesSum, uint attrCounter, uint maxAttr) internal pure returns (
IItemController.ItemRarity
) {
if (attrCounter == 0) {
return IItemController.ItemRarity.NORMAL;
}
uint random = randomSum / attrCounter;
uint averageChance = chancesSum / attrCounter;
if (random > CalcLib.MAX_CHANCE) revert IAppErrors.TooHighRandom(random);
if (random < averageChance / 4 && attrCounter == maxAttr) {
return IItemController.ItemRarity.RARE;
} else if (random < averageChance * 3 / 4) {
return attrCounter > 2
? IItemController.ItemRarity.RARE
: IItemController.ItemRarity.MAGIC;
} else {
return attrCounter > 1
? IItemController.ItemRarity.MAGIC
: IItemController.ItemRarity.NORMAL;
}
}
//endregion ------------------------ Internal utils
//region ------------------------ PACKING
function packItemGenerateInfo(IItemController.ItemGenerateInfo memory info) internal pure returns (bytes32[] memory result) {
uint len = info.ids.length;
if (len != info.mins.length || len != info.maxs.length || len != info.chances.length) {
revert IAppErrors.LengthsMismatch();
}
result = new bytes32[](len);
for (uint i; i < len; ++i) {
result[i] = PackingLib.packItemGenerateInfo(info.ids[i], info.mins[i], info.maxs[i], info.chances[i]);
}
}
function unpackItemGenerateInfo(bytes32[] memory gen) internal pure returns (
IItemController.ItemGenerateInfo memory
) {
uint length = gen.length;
uint8[] memory ids = new uint8[](length);
int32[] memory mins = new int32[](length);
int32[] memory maxs = new int32[](length);
uint32[] memory chances = new uint32[](length);
for (uint i; i < length; ++i) {
(ids[i], mins[i], maxs[i], chances[i]) = gen[i].unpackItemGenerateInfo();
}
return IItemController.ItemGenerateInfo(ids, mins, maxs, chances);
}
function packItemMeta(IItemController.ItemMeta memory meta) internal pure returns (bytes32) {
return PackingLib.packItemMeta(
meta.itemMetaType,
meta.itemLevel,
uint8(meta.itemType),
meta.baseDurability,
meta.defaultRarity,
meta.minRandomAttributes,
meta.maxRandomAttributes,
meta.manaCost,
meta.requirements
);
}
function unpackedItemMeta(bytes32 meta) internal pure returns (IItemController.ItemMeta memory result) {
return meta.unpackItemMeta();
}
function packItemInfo(IItemController.ItemInfo memory info) internal pure returns (bytes32) {
return PackingLib.packItemInfo(uint8(info.rarity), info.augmentationLevel, info.durability);
}
function unpackedItemInfo(bytes32 info) internal pure returns (IItemController.ItemInfo memory result) {
uint8 rarity;
(rarity, result.augmentationLevel, result.durability) = info.unpackItemInfo();
result.rarity = IItemController.ItemRarity(rarity);
return result;
}
function packItemAttackInfo(IItemController.AttackInfo memory info) internal pure returns (bytes32) {
return PackingLib.packItemAttackInfo(
uint8(info.aType),
info.min,
info.max,
info.attributeFactors.strength,
info.attributeFactors.dexterity,
info.attributeFactors.vitality,
info.attributeFactors.energy
);
}
function unpackItemAttackInfo(bytes32 info) internal pure returns (IItemController.AttackInfo memory result) {
IStatController.CoreAttributes memory fs;
uint8 aType;
(aType, result.min, result.max, fs.strength, fs.dexterity, fs.vitality, fs.energy) = info.unpackItemAttackInfo();
result.aType = IItemController.AttackType(aType);
result.attributeFactors = fs;
return result;
}
//endregion ------------------------ PACKING
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../interfaces/IGOC.sol";
import "./CalcLib.sol";
import "./PackingLib.sol";
import "./StatLib.sol";
import "./ItemLib.sol";
import "./StringLib.sol";
import "./FightLib.sol";
import "./RewardsPoolLib.sol";
import "../interfaces/IController.sol";
import "../interfaces/IStatController.sol";
import "../interfaces/IOracle.sol";
import "../interfaces/IFightCalculator.sol";
import "../interfaces/IDungeonFactory.sol";
import "../interfaces/IItemController.sol";
import "../interfaces/IERC20.sol";
library MonsterLib {
using CalcLib for int32;
using PackingLib for bytes32;
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;
/// @notice Max value for monster rarity and monster/dungeon multiplier
uint32 internal constant _MAX_AMPLIFIER = 1e9;
uint private constant _TOTAL_SUPPLY_BASE = 10_000_000e18;
/// @notice Base monster multiplier for NG+. Multiplier = base multiplier * hero ng_level
uint internal constant _MONSTER_MULTIPLIER_NGP_BASE = uint(_MAX_AMPLIFIER);
//region ------------------------ Data types
struct AdrContext {
address sender;
address heroToken;
IController controller;
IOracle oracle;
IStatController statController;
IItemController itemController;
uint heroTokenId;
}
struct FightInternalInfo {
int32 manaConsumed;
int32 damage;
int32 heroLifeRegen;
int32 heroHp;
int32 monsterHp;
uint32 monsterRarity;
IFightCalculator.FighterInfo heroFightInfo;
IFightCalculator.FighterInfo monsterFightInfo;
}
//endregion ------------------------ Data types
//region ------------------------ Main logic
/// @param heroNgLevel Pass type(uint8).max for !NG+
function initialGeneration(
IGOC.MonsterInfo storage mInfo,
address heroToken,
uint heroTokenId,
uint iteration,
uint8 heroNgLevel
) internal {
return _initialGeneration(mInfo, heroToken, heroTokenId, iteration, _pseudoRandom, heroNgLevel);
}
/// @notice Fight, post fight, generate fight results
/// @return result Fields objectId, heroToken, heroTokenId, iteration remain uninitialized here.
/// Caller is responsible to set that values.
/// @dev weird, but memory ctx is more efficient here than calldata ctx
function action(IGOC.ActionContext memory ctx, IGOC.MonsterInfo storage mInfo) external returns (
IGOC.ActionResult memory result,
uint8 turn
) {
return _action(ctx, mInfo, _pseudoRandom, FightLib.fight);
}
//endregion ------------------------ Main logic
//region ------------------------ Internal calculations
function _action(
IGOC.ActionContext memory ctx,
IGOC.MonsterInfo storage mInfo,
function (uint) internal view returns (uint) random_,
function(
IItemController,
IFightCalculator.FightCall memory,
address,
function (uint) internal view returns (uint)
) internal returns (IFightCalculator.FightResult memory) fight_
) internal returns (
IGOC.ActionResult memory result,
uint8 turn
) {
AdrContext memory adrCtx = _context(ctx);
IGOC.GeneratedMonster memory gen = unpackGeneratedMonster(mInfo._generatedMonsters[ctx.heroToken.packNftId(ctx.heroTokenId)][ctx.iteration]);
turn = gen.turnCounter;
(FightInternalInfo memory fInfo, IGOC.MonsterGenInfo memory genInfo) = _fight(ctx, mInfo, gen, adrCtx, random_, fight_);
result = _postFight(mInfo, ctx, adrCtx, fInfo, genInfo, gen);
}
/// @dev This function was extracted from {action()} to simplify unit testing
/// @param gen These values CAN BE modified in place in some cases.
/// @return result Fields objectId, heroToken, heroTokenId, iteration remain uninitialized here.
/// Caller is responsible to set that values.
function _postFight(
IGOC.MonsterInfo storage mInfo,
IGOC.ActionContext memory ctx,
AdrContext memory adrCtx,
FightInternalInfo memory fInfo,
IGOC.MonsterGenInfo memory genInfo,
IGOC.GeneratedMonster memory gen
) internal returns (
IGOC.ActionResult memory result
) {
bytes32 heroPackedId = ctx.heroToken.packNftId(ctx.heroTokenId);
if (gen.turnCounter > 100) {
// instant kill hero if too long battle
fInfo.heroHp = 0;
}
bool isMonsterDead = fInfo.monsterHp == 0;
bool isHeroDead = fInfo.heroHp == 0;
if (isMonsterDead) {
_bossDefeated(adrCtx, ctx);
}
if (isMonsterDead || isHeroDead) {
if (gen.generated) {
delete mInfo._generatedMonsters[heroPackedId][ctx.iteration];
}
// assume that if the hero is dead clearUsedConsumables will be called in _objectAction
if (isMonsterDead) {
adrCtx.statController.clearUsedConsumables(ctx.heroToken, ctx.heroTokenId);
}
} else {
if (gen.generated) {
gen.hp = fInfo.monsterHp;
gen.turnCounter = gen.turnCounter + 1;
} else {
// new instance of gen is created
gen = IGOC.GeneratedMonster({
generated: true,
amplifier: fInfo.monsterRarity,
hp: fInfo.monsterHp,
turnCounter: 1
});
}
mInfo._generatedMonsters[heroPackedId][ctx.iteration] = packGeneratedMonster(gen);
}
if (isMonsterDead) {
bytes32 index = _getMonsterCounterIndex(ctx.objectId);
uint curValue = adrCtx.statController.heroCustomData(ctx.heroToken, ctx.heroTokenId, index);
adrCtx.statController.setHeroCustomData(ctx.heroToken, ctx.heroTokenId, index, curValue + 1);
}
// --- generate result
result.kill = isHeroDead;
result.experience = isMonsterDead
? StatLib.expPerMonster(
fInfo.monsterFightInfo.fighterStats.experience,
fInfo.monsterRarity,
fInfo.heroFightInfo.fighterStats.experience,
fInfo.heroFightInfo.fighterStats.level,
ctx.biome
)
: 0;
result.heal = fInfo.heroLifeRegen;
result.manaRegen = isMonsterDead ? fInfo.heroFightInfo.fighterAttributes[uint(IStatController.ATTRIBUTES.MANA_AFTER_KILL)] : int32(0);
// result.lifeChancesRecovered = 0; // zero by default
result.damage = fInfo.damage;
result.manaConsumed = fInfo.manaConsumed;
result.mintItems = isMonsterDead
? _mintRandomItems(fInfo, ctx, genInfo, CalcLib.nextPrng)
: new address[](0);
result.completed = isMonsterDead || isHeroDead;
return result;
}
/// @notice Generate new {GeneratedMonster} and put it to {mInfo._generatedMonsters}
/// @param random_ Pass _pseudoRandom here, param is required for unit tests, range [0...MAX_AMPLIFIER]
/// @param heroNgLevel Assume type(uint8).max for !NG+
function _initialGeneration(
IGOC.MonsterInfo storage mInfo,
address heroToken,
uint heroTokenId,
uint iteration,
function (uint) internal view returns (uint) random_,
uint8 heroNgLevel
) internal {
IGOC.GeneratedMonster memory gen = IGOC.GeneratedMonster({
generated: true,
amplifier: uint32(random_(_MAX_AMPLIFIER)),
hp: 0,
turnCounter: 0
});
IGOC.MonsterGenInfo memory info = unpackMonsterInfo(mInfo);
(int32[] memory attributes,) = generateMonsterAttributes(
info.attributeIds,
info.attributeValues,
gen.amplifier,
monsterMultiplier(heroNgLevel),
info.experience
);
gen.hp = attributes[uint(IStatController.ATTRIBUTES.LIFE)];
mInfo._generatedMonsters[heroToken.packNftId(heroTokenId)][iteration] = packGeneratedMonster(gen);
}
function _bossDefeated(AdrContext memory adrCtx, IGOC.ActionContext memory ctx) internal {
if (ctx.objectSubType == uint8(IGOC.ObjectSubType.BOSS_3)) {
IDungeonFactory(adrCtx.controller.dungeonFactory()).setBossCompleted(ctx.objectId, ctx.heroToken, ctx.heroTokenId, ctx.biome);
}
}
function _collectHeroFighterInfo(
IFightCalculator.AttackInfo memory attackInfo,
AdrContext memory adrContext
) internal view returns (
IFightCalculator.FighterInfo memory fInfo,
int32 manaConsumed
) {
IStatController.ChangeableStats memory heroStats = adrContext.statController.heroStats(adrContext.heroToken, adrContext.heroTokenId);
(int32[] memory heroAttributes, int32 _manaConsumed) = _buffAndGetHeroAttributes(heroStats.level, attackInfo, adrContext);
manaConsumed = _manaConsumed;
if (attackInfo.attackType == IFightCalculator.AttackType.MAGIC) {
manaConsumed += int32(adrContext.itemController.itemMeta(attackInfo.attackToken).manaCost);
}
fInfo = IFightCalculator.FighterInfo({
fighterAttributes: heroAttributes,
fighterStats: heroStats,
attackType: attackInfo.attackType,
attackToken: attackInfo.attackToken,
attackTokenId: attackInfo.attackTokenId,
race: uint(IStatController.Race.HUMAN)
});
}
function _buffAndGetHeroAttributes(
uint level,
IFightCalculator.AttackInfo memory attackInfo,
AdrContext memory context
) internal view returns (
int32[] memory heroAttributes,
int32 manaConsumed
) {
return context.statController.buffHero(IStatController.BuffInfo({
heroToken: context.heroToken,
heroTokenId: context.heroTokenId,
heroLevel: uint32(level),
buffTokens: attackInfo.skillTokens,
buffTokenIds: attackInfo.skillTokenIds
}));
}
/// @notice Get skill tokens, ensure that they are equipped on, add skill-tokens target attributes to hero attributes
/// @param attributes Hero attributes. These values are incremented in place
// @param heroAttackInfo Checked attack info. Assume that all skill tokens belong either to the hero or to the helper.
function _debuff(
int32[] memory attributes,
IFightCalculator.AttackInfo memory heroAttackInfo,
AdrContext memory context
) internal view {
uint length = heroAttackInfo.skillTokens.length;
for (uint i; i < length; ++i) {
(int32[] memory values, uint8[] memory ids) = context.itemController.targetAttributes(
heroAttackInfo.skillTokens[i],
heroAttackInfo.skillTokenIds[i]
);
StatLib.attributesAdd(attributes, StatLib.valuesToFullAttributesArray(values, ids));
}
}
/// @param random_ Pass _pseudoRandom here, param is required for unit tests, range [0...MAX_AMPLIFIER]
function _collectMonsterFighterInfo(
IGOC.MultiplierInfo memory multiplierInfo,
IGOC.MonsterInfo storage mInfo,
IGOC.GeneratedMonster memory gen,
IFightCalculator.AttackInfo memory heroAttackInfo,
uint heroLevel,
AdrContext memory adrCtx,
function (uint) internal view returns (uint) random_
) internal view returns (
IFightCalculator.FighterInfo memory fighterInfo,
uint32 rarity,
IGOC.MonsterGenInfo memory genInfo
) {
IFightCalculator.AttackInfo memory attackInfo;
rarity = gen.generated ? gen.amplifier : uint32(random_(_MAX_AMPLIFIER));
(
fighterInfo.fighterAttributes,
fighterInfo.fighterStats.level,
fighterInfo.fighterStats.experience,
attackInfo,
genInfo
) = _generateMonsterInfo(
mInfo,
rarity,
monsterMultiplier(multiplierInfo.heroNgLevel),
heroLevel,
multiplierInfo.biome,
random_
);
_debuff(fighterInfo.fighterAttributes, heroAttackInfo, adrCtx);
fighterInfo.fighterStats.life = gen.generated
? uint32(gen.hp)
: fighterInfo.fighterStats.life = uint32(CalcLib.max32(fighterInfo.fighterAttributes[uint(IStatController.ATTRIBUTES.LIFE)], int32(1)));
fighterInfo.fighterStats.mana = uint32(fighterInfo.fighterAttributes[uint(IStatController.ATTRIBUTES.MANA)]);
fighterInfo.attackType = attackInfo.attackType;
fighterInfo.attackToken = attackInfo.attackToken;
fighterInfo.attackTokenId = attackInfo.attackTokenId;
fighterInfo.race = genInfo.race;
return (fighterInfo, rarity, genInfo);
}
/// @param random_ Pass _pseudoRandom here, param is required to simplify unit testing
/// @param fight_ Pass FightLib.fight here, param is required to simplify unit testing
function _fight(
IGOC.ActionContext memory ctx,
IGOC.MonsterInfo storage mInfo,
IGOC.GeneratedMonster memory gen,
AdrContext memory adrCtx,
function (uint) internal view returns (uint) random_,
function(
IItemController,
IFightCalculator.FightCall memory,
address,
function (uint) internal view returns (uint)
) internal returns (IFightCalculator.FightResult memory) fight_
) internal returns (
FightInternalInfo memory fInfo,
IGOC.MonsterGenInfo memory info
) {
IFightCalculator.FighterInfo memory heroFightInfo;
IFightCalculator.FighterInfo memory monsterFightInfo;
{
IFightCalculator.AttackInfo memory heroAttackInfo = decodeAndCheckAttackInfo(
adrCtx.itemController,
IHeroController(IController(adrCtx.controller).heroController()),
ctx.data,
adrCtx.heroToken,
adrCtx.heroTokenId
);
// use fInfo.manaConsumed and fInfo.monsterRarity to story values temporally to avoid creation of additional vars
(heroFightInfo, fInfo.manaConsumed) = _collectHeroFighterInfo(heroAttackInfo, adrCtx);
(monsterFightInfo, fInfo.monsterRarity, info) = _collectMonsterFighterInfo(
IGOC.MultiplierInfo({
biome: ctx.biome,
heroNgLevel: ctx.heroNgLevel
}),
mInfo,
gen,
heroAttackInfo,
heroFightInfo.fighterStats.level,
adrCtx,
random_
);
}
// >>> FIGHT!
IFightCalculator.FightResult memory fightResult = fight_(
adrCtx.itemController,
IFightCalculator.FightCall({
fighterA: heroFightInfo,
fighterB: monsterFightInfo,
dungeonId: ctx.dungeonId,
objectId: ctx.objectId,
heroAdr: adrCtx.heroToken,
heroId: adrCtx.heroTokenId,
stageId: ctx.stageId,
iteration: ctx.iteration,
turn: gen.turnCounter
}),
ctx.sender,
random_
);
fInfo = FightInternalInfo({
manaConsumed: fInfo.manaConsumed + fightResult.manaConsumedA,
monsterRarity: fInfo.monsterRarity,
damage: _calcDmg(int32(heroFightInfo.fighterStats.life), fightResult.healthA),
heroFightInfo: heroFightInfo,
monsterFightInfo: monsterFightInfo,
heroLifeRegen: fightResult.healthA > int32(heroFightInfo.fighterStats.life) ? fightResult.healthA - int32(heroFightInfo.fighterStats.life) : int32(0),
heroHp: fightResult.healthA,
monsterHp: fightResult.healthB
});
}
/// @param random_ Pass _pseudoRandom here, param is required for unit tests, range [0...1e18]
/// @return attributes Attributes amplified on amplifier and dungeonMultiplier
/// @return level Result level in the range: [mInfo.level .. heroLevel]
/// @return experience Experience amplified on amplifier and dungeonMultiplier
/// @return attackInfo Attack info. For magic hero attack type monster will have melee in half hits (randomly)
/// @return info Unpacked data from {mInfo}, some fields can be uninitialized, see comments to unpackMonsterInfo (!)
function _generateMonsterInfo(
IGOC.MonsterInfo storage mInfo,
uint32 amplifier,
uint dungeonMultiplier,
uint heroLevel,
uint biome,
function (uint) internal view returns (uint) random_
) internal view returns (
int32[] memory attributes,
uint32 level,
uint32 experience,
IFightCalculator.AttackInfo memory attackInfo,
IGOC.MonsterGenInfo memory info
) {
info = unpackMonsterInfo(mInfo);
level = uint32(info.level);
if (level < heroLevel + 1) {
level = uint32(Math.min(level + ((heroLevel - level) * 10 / 15), biome * 5));
}
if (info.attackType == uint8(IFightCalculator.AttackType.MAGIC)) {
// sometimes use melee (25% chance)
uint rnd = random_(1e18);
if (rnd > 0.75e18) {
attackInfo.attackType = IFightCalculator.AttackType.MELEE;
} else {
attackInfo.attackType = IFightCalculator.AttackType.MAGIC;
attackInfo.attackToken = info.attackToken;
attackInfo.attackTokenId = info.attackTokenId;
}
} else {
attackInfo.attackType = IFightCalculator.AttackType(info.attackType);
}
(attributes, experience) = generateMonsterAttributes(
info.attributeIds,
info.attributeValues,
amplifier,
dungeonMultiplier,
info.experience
);
return (attributes, level, experience, attackInfo, info);
}
function _mintRandomItems(
FightInternalInfo memory fInfo,
IGOC.ActionContext memory ctx,
IGOC.MonsterGenInfo memory genInfo,
function (LibPRNG.PRNG memory, uint) internal view returns (uint) nextPrng_
) internal returns (
address[] memory
) {
return ItemLib._mintRandomItems(
ItemLib.MintItemInfo({
mintItems: genInfo.mintItems,
mintItemsChances: genInfo.mintItemsChances,
amplifier: fInfo.monsterRarity,
seed: 0,
oracle: IOracle(ctx.controller.oracle()),
magicFind: fInfo.heroFightInfo.fighterAttributes[uint(IStatController.ATTRIBUTES.MAGIC_FIND)],
destroyItems: fInfo.heroFightInfo.fighterAttributes[uint(IStatController.ATTRIBUTES.DESTROY_ITEMS)],
maxItems: genInfo.maxDropItems,
mintDropChanceDelta: ctx.objectSubType == uint8(IGOC.ObjectSubType.BOSS_3) ? 0 : // do not reduce drop for bosses at all
StatLib.mintDropChanceDelta(
fInfo.heroFightInfo.fighterStats.experience,
uint8(fInfo.heroFightInfo.fighterStats.level),
ctx.biome
),
mintDropChanceNgLevelMultiplier: _getMintDropChanceNgLevelMultiplier(ctx)
}),
nextPrng_
);
}
/// @return drop chance multiplier, decimals 1e18; result value is guaranteed to be <= 1e18
function _getMintDropChanceNgLevelMultiplier(IGOC.ActionContext memory ctx) internal view returns (uint) {
return Math.min(1e18, RewardsPoolLib.dropChancePercent(
IDungeonFactory(ctx.controller.dungeonFactory()).maxAvailableBiome(),
IHeroController(ctx.controller.heroController()).maxOpenedNgLevel(),
ctx.heroNgLevel
));
}
//endregion ------------------------ Internal calculations
//region ------------------------ Utils
function _context(IGOC.ActionContext memory ctx) internal view returns (AdrContext memory context) {
context = AdrContext({
sender: ctx.sender,
heroToken: ctx.heroToken,
heroTokenId: ctx.heroTokenId,
controller: ctx.controller,
oracle: IOracle(ctx.controller.oracle()),
statController: IStatController(ctx.controller.statController()),
itemController: IItemController(ctx.controller.itemController())
});
}
function unpackGeneratedMonster(bytes32 gen) internal pure returns (IGOC.GeneratedMonster memory result) {
(bool generated, uint32 amplifier, int32 hp, uint8 turnCounter) = gen.unpackGeneratedMonster();
result = IGOC.GeneratedMonster({
generated: generated,
amplifier: amplifier,
hp: hp,
turnCounter: turnCounter
});
}
function packGeneratedMonster(IGOC.GeneratedMonster memory gen) internal pure returns (bytes32) {
return PackingLib.packGeneratedMonster(gen.generated, gen.amplifier, gen.hp, gen.turnCounter);
}
function packMonsterInfo(IGOC.MonsterGenInfo memory mInfo, IGOC.MonsterInfo storage info) internal {
info.attributes = mInfo.attributeValues.toBytes32ArrayWithIds(mInfo.attributeIds);
info.stats = PackingLib.packMonsterStats(mInfo.level, mInfo.race, mInfo.experience, mInfo.maxDropItems);
info.attackInfo = PackingLib.packAttackInfo(mInfo.attackToken, mInfo.attackTokenId, mInfo.attackType);
uint len = mInfo.mintItems.length;
bytes32[] memory mintItems = new bytes32[](len);
for (uint i; i < len; ++i) {
mintItems[i] = mInfo.mintItems[i].packItemMintInfo(mInfo.mintItemsChances[i]);
}
info.mintItems = mintItems;
}
/// @return Attention: Following fields are not initialized: biome, subType, monsterId
function unpackMonsterInfo(IGOC.MonsterInfo storage mInfo) internal view returns (IGOC.MonsterGenInfo memory) {
IGOC.MonsterGenInfo memory result;
(result.attributeValues, result.attributeIds) = mInfo.attributes.toInt32ArrayWithIds();
(result.level, result.race, result.experience, result.maxDropItems) = mInfo.stats.unpackMonsterStats();
(result.attackToken, result.attackTokenId, result.attackType) = mInfo.attackInfo.unpackAttackInfo();
uint len = mInfo.mintItems.length;
result.mintItems = new address[](len);
result.mintItemsChances = new uint32[](len);
for (uint i = 0; i < len; i++) {
(result.mintItems[i], result.mintItemsChances[i]) = mInfo.mintItems[i].unpackItemMintInfo();
}
// Attention: result.biome, result.subType, result.monsterId are not initialized
return result;
}
/// @notice Decode attack info. Ensure that attack token belongs to the hero.
/// Ensure that skill tokens belong to the hero OR to the current helper (SIP-001)
function decodeAndCheckAttackInfo(
IItemController ic,
IHeroController heroController,
bytes memory data,
address heroToken,
uint heroId
) internal view returns (IFightCalculator.AttackInfo memory) {
(IFightCalculator.AttackInfo memory attackInfo) = abi.decode(data, (IFightCalculator.AttackInfo));
if (uint(attackInfo.attackType) == 0) revert IAppErrors.UnknownAttackType(uint(attackInfo.attackType));
if (attackInfo.attackToken != address(0)) {
(address h, uint hId) = ic.equippedOn(attackInfo.attackToken, attackInfo.attackTokenId);
if (heroToken != h || hId != heroId) revert IAppErrors.NotYourAttackItem();
}
(address helperHeroToken, uint helperHeroId) = heroController.heroReinforcementHelp(heroToken, heroId);
for (uint i; i < attackInfo.skillTokens.length; ++i) {
(address h, uint hId) = ic.equippedOn(attackInfo.skillTokens[i], attackInfo.skillTokenIds[i]);
if (
(heroToken != h || hId != heroId)
&& ((helperHeroToken == address(0)) || (helperHeroToken != h || helperHeroId != hId))
) revert IAppErrors.NotYourBuffItem();
}
return attackInfo;
}
/// @dev Monsters power is increased on 100% with each increment of hero NG_LEVEL
function monsterMultiplier(uint8 heroNgLevel) internal pure returns (uint) {
return _MONSTER_MULTIPLIER_NGP_BASE * uint(heroNgLevel);
}
function amplifyMonsterAttribute(int32 value, uint32 amplifier, uint dungeonMultiplier) internal pure returns (int32) {
if (value == 0) {
return 0;
}
int destValue = int(value)
+ (int(value) * int(uint(amplifier)) / int(uint(_MAX_AMPLIFIER)))
+ (int(value) * int(dungeonMultiplier) / int(uint(_MAX_AMPLIFIER)));
if (destValue > type(int32).max || destValue < type(int32).min) revert IAppErrors.IntValueOutOfRange(destValue);
return int32(destValue);
}
/// @dev A wrapper around {CalcLib.pseudoRandom} to pass it as param (to be able to implement unit tests}
function _pseudoRandom(uint max) internal view returns (uint) {
return CalcLib.pseudoRandom(max);
}
/// @notice Amplify values of the attributes and of the experience
/// using randomly generated {amplifier} and {dungeonMultiplier}.
/// Attributes = amplify(ids, values), experience = amplify(baseExperience)
function generateMonsterAttributes(
uint8[] memory ids,
int32[] memory values,
uint32 amplifier,
uint dungeonMultiplier,
uint32 baseExperience
) internal pure returns (
int32[] memory attributes,
uint32 experience
) {
// reduce random
amplifier = amplifier / 4;
attributes = new int32[](uint(IStatController.ATTRIBUTES.END_SLOT));
for (uint i; i < ids.length; ++i) {
attributes[ids[i]] = amplifyMonsterAttribute(values[i], amplifier, dungeonMultiplier);
}
experience = uint32(amplifyMonsterAttribute(int32(baseExperience), amplifier, 0));
}
function _calcDmg(int32 heroLifeBefore, int32 heroLifeAfter) internal pure returns (int32 damage) {
return heroLifeAfter == 0
? heroLifeBefore
: heroLifeBefore - CalcLib.minI32(heroLifeAfter, heroLifeBefore);
}
function _getMonsterCounterIndex(uint32 objectId) internal pure returns (bytes32) {
return bytes32(abi.encodePacked("MONSTER_", StringLib._toString(uint(objectId))));
}
//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));
}
//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
}// 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;
import "../interfaces/IAppErrors.sol";
import "../interfaces/IApplicationEvents.sol";
import "../interfaces/IShelterController.sol";
import "../interfaces/IShelterController.sol";
import "../interfaces/IUserController.sol";
import "../lib/StringLib.sol";
import "../token/GuildBank.sol";
import "./StatLib.sol";
import "../interfaces/IShelterAuction.sol";
library ShelterLib {
using EnumerableSet for EnumerableSet.UintSet;
using EnumerableSet for EnumerableSet.AddressSet;
using EnumerableSet for EnumerableSet.UintSet;
//region ------------------------ Constants
/// @dev keccak256(abi.encode(uint256(keccak256("shelter.controller.main")) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant SHELTER_CONTROLLER_STORAGE_LOCATION = 0x5a293071b39954a4fcf98ae7184af7c6201e972e15842b884f1ad071e9bded00; // shelter.controller.main
uint8 internal constant MIN_SHELTER_LEVEL = 1;
uint8 internal constant MAX_SHELTER_LEVEL = 3;
//endregion ------------------------ Constants
//region ------------------------ Restrictions
function _onlyDeployer(IController controller) internal view {
if (!controller.isDeployer(msg.sender)) revert IAppErrors.ErrorNotDeployer(msg.sender);
}
function _onlyGuildController(address guildController) internal view {
if (msg.sender != guildController) revert IAppErrors.ErrorNotGuildController();
}
function _notPaused(IController controller) internal view {
if (controller.onPause()) revert IAppErrors.ErrorPaused();
}
//endregion ------------------------ Restrictions
//region ------------------------ Storage
function _S() internal pure returns (IShelterController.MainState storage s) {
assembly {
s.slot := SHELTER_CONTROLLER_STORAGE_LOCATION
}
return s;
}
//endregion ------------------------ Storage
//region ------------------------ Shelter view
/// @notice Get list of all registered shelters in the given {biome}
function getShelters(uint8 biome) internal view returns (uint[] memory shelterIds) {
return _S().shelters[biome].values();
}
/// @notice Get initial price of the given shelter. The price is used if the shelter doesn't belong to any guild
function getShelterPrice(uint shelterId) internal view returns (uint price) {
return _S().shelterPrices[shelterId];
}
/// @notice Get shelter which belongs to the given guild
function guildToShelter(uint guildId) internal view returns (uint shelterId) {
return _S().guildToShelter[guildId];
}
/// @notice Get guild to which the given shelter belongs
function shelterToGuild(uint shelterId) internal view returns (uint guildId) {
return _S().shelterToGuild[shelterId];
}
/// @notice Get shelter of the guild to which the user belongs
function getShelterId(IGuildController guildController, address user) internal view returns (uint shelterId) {
uint guildId = guildController.memberOf(user);
return guildId == 0
? 0
: _S().guildToShelter[guildId];
}
/// @notice List of items that can be bought in the shelter of the given level in the given biome
function getShelterItems(uint shelterId) internal view returns (address[] memory items) {
return _S().shelterItems[shelterId].values();
}
function getShelterItemData(uint shelterId, address item) internal view returns (
uint64 priceInPvpPoints,
uint128 priceInGameToken,
uint16 maxItemsPerDayLimit
) {
IShelterController.ShelterItemData memory data = _S().shelterItemData[shelterId][item];
return (
data.priceInPvpPoints,
data.priceInGameToken,
data.maxItemsPerDayLimit
);
}
/// @notice How many {item} instances were purchased per {epochDay} in the given {shelterId}
/// @param epochDay TimestampInSeconds / 24 * 60 * 60
function getCountPurchasedItems(address item, uint shelterId, uint32 epochDay) internal view returns (uint) {
return _S().countPurchasedItems[shelterId][epochDay][item];
}
//endregion ------------------------ Shelter view
//region ------------------------ Shelter config
/// @notice Register new shelter or overwrite exist. Only registered shelters can be purchased.
/// @param shelterId ID should be generated using {PackingLib.packShelterId}
/// @param price Initial shelter price in game tokens
function setShelter(IController controller, uint shelterId, uint price) internal {
ShelterLib._onlyDeployer(controller);
(uint8 biome, uint8 shelterLevel, ) = PackingLib.unpackShelterId(shelterId);
if (biome == 0 || biome > StatLib.MAX_POSSIBLE_BIOME) revert IAppErrors.ErrorIncorrectBiome(biome);
if (price == 0) revert IAppErrors.ZeroValueNotAllowed();
if (shelterLevel < MIN_SHELTER_LEVEL || shelterLevel > MAX_SHELTER_LEVEL) revert IAppErrors.IncorrectShelterLevel(shelterLevel);
_S().shelterPrices[shelterId] = price;
_S().shelters[biome].add(shelterId);
emit IApplicationEvents.RegisterShelter(shelterId, price);
}
/// @notice Set items that can be purchases in the given shelter: remove previously stored items, add new items.
/// @param shelterId ID should be generated using {PackingLib.packShelterId}
/// @param items List of item tokens
/// @param pricesInPvpPoints Prices in pvp-points. The points are taken from guild balance at the moment of purchasing
/// @param pricesInGameTokens Additional prices in game tokens. Can contain zeros.
/// @param maxItemsPerDayLimits Indicate how many item instances the users can purchase per day. 0 - no limitations
function setShelterItems(
IController controller,
uint shelterId,
address[] memory items,
uint64[] memory pricesInPvpPoints,
uint128[] memory pricesInGameTokens,
uint16[] memory maxItemsPerDayLimits
) internal {
ShelterLib._onlyDeployer(controller);
uint len = items.length;
if (len != pricesInPvpPoints.length || len != pricesInGameTokens.length || len != maxItemsPerDayLimits.length) {
revert IAppErrors.LengthsMismatch();
}
EnumerableSet.AddressSet storage set = _S().shelterItems[shelterId];
// remove previously stored items
address[] memory prevItems = set.values();
uint prevItemsLen = prevItems.length;
for (uint i; i < prevItemsLen; ++i) {
set.remove(prevItems[i]);
delete _S().shelterItemData[shelterId][prevItems[i]];
}
// add new items
for (uint i; i < len; ++i) {
set.add(items[i]);
if (pricesInPvpPoints[i] == 0 && pricesInGameTokens[i] == 0) revert IAppErrors.FreeShelterItemsAreNotAllowed(shelterId, items[i]);
_S().shelterItemData[shelterId][items[i]] = IShelterController.ShelterItemData({
priceInPvpPoints: pricesInPvpPoints[i],
priceInGameToken: pricesInGameTokens[i],
maxItemsPerDayLimit: maxItemsPerDayLimits[i]
});
}
emit IApplicationEvents.SetShelterItems(shelterId, items, pricesInPvpPoints, pricesInGameTokens, maxItemsPerDayLimits);
}
//endregion ------------------------ Shelter config
//region ------------------------ Shelter actions
/// @notice Guild buys a shelter that doesn't belong to any guild. It pays default prices and changes owner of the shelter.
function buyShelter(IController controller, address msgSender, uint shelterId) internal {
_notPaused(controller);
IGuildController guildController = IGuildController(controller.guildController());
(uint guildId,) = guildController.checkPermissions(msgSender, uint(IGuildController.GuildRightBits.CHANGE_SHELTER_3));
// only registered shelter can be purchased
(uint8 biome, , ) = PackingLib.unpackShelterId(shelterId);
if (!_S().shelters[biome].contains(shelterId)) revert IAppErrors.ShelterIsNotRegistered();
// Each guild is able to have only 1 shelter. Exist shelter should be sold or left
if (_S().guildToShelter[guildId] != 0) revert IAppErrors.GuildAlreadyHasShelter();
if (_S().shelterToGuild[shelterId] != 0) revert IAppErrors.ShelterIsBusy();
{ // Shelter can be bought only if there is no auction bid
address shelterAuction = guildController.shelterAuctionController();
if (shelterAuction != address(0)) {
(uint positionId,) = IShelterAuction(shelterAuction).positionByBuyer(guildId);
if (positionId != 0) revert IAppErrors.AuctionBidOpened(positionId);
}
}
// pay for the shelter from the guild bank
uint shelterPrice = getShelterPrice(shelterId);
guildController.payFromGuildBank(guildId, shelterPrice);
// register ownership
_S().guildToShelter[guildId] = shelterId;
_S().shelterToGuild[shelterId] = guildId;
emit IApplicationEvents.BuyShelter(guildId, shelterId);
}
/// @notice Guild leaves the shelter. The shelter becomes free, it can be bought by any guild by default price
function leaveShelter(IController controller, address msgSender, uint shelterId) internal {
_notPaused(controller);
IGuildController guildController = IGuildController(controller.guildController());
(uint guildId,) = guildController.checkPermissions(msgSender, uint(IGuildController.GuildRightBits.CHANGE_SHELTER_3));
if (_S().guildToShelter[guildId] != shelterId) revert IAppErrors.ShelterIsNotOwnedByTheGuild();
if (shelterId == 0) revert IAppErrors.GuildHasNoShelter();
{ // Shelter can be sold only if there is no opened auction position
address shelterAuction = guildController.shelterAuctionController();
if (shelterAuction != address(0)) {
uint positionId = IShelterAuction(shelterAuction).positionBySeller(guildId);
if (positionId != 0) revert IAppErrors.AuctionPositionOpened(positionId);
}
}
// unregister ownership
delete _S().guildToShelter[guildId];
delete _S().shelterToGuild[shelterId];
emit IApplicationEvents.LeaveShelter(guildId, shelterId);
}
/// @notice Purchase the {item} in the shelter that belongs to the guild to which {msgSender} belongs
function purchaseShelterItem(IController controller, address msgSender, address item, uint blockTimestamp) internal {
_notPaused(controller);
IGuildController guildController = IGuildController(controller.guildController());
// no permission are required - any guild member is able to purchase shelter item
// but the member should either be owner or should have enough pvp-points capacity, see restriction below
uint guildId = _getValidGuildId(guildController, msgSender);
uint shelterId = _S().guildToShelter[guildId];
if (shelterId == 0) revert IAppErrors.GuildHasNoShelter();
if (! _S().shelterItems[shelterId].contains(item)) revert IAppErrors.ShelterHasNotItem(shelterId, item);
// total number of the item instances that can be minted per day CAN BE limited
IShelterController.ShelterItemData memory itemData = _S().shelterItemData[shelterId][item];
uint numSoldItems;
{
uint32 epochDay = uint32(blockTimestamp / 86400);
mapping(address => uint) storage countPurchasedItems = _S().countPurchasedItems[shelterId][epochDay];
numSoldItems = countPurchasedItems[item];
if (itemData.maxItemsPerDayLimit != 0) {
if (numSoldItems >= itemData.maxItemsPerDayLimit) revert IAppErrors.MaxNumberItemsSoldToday(numSoldItems, itemData.maxItemsPerDayLimit);
}
countPurchasedItems[item] = numSoldItems + 1;
}
// user pays for the item by pvp-points and/or by game token (it depends on the item settings)
if (itemData.priceInPvpPoints != 0) {
guildController.usePvpPoints(guildId, msgSender, itemData.priceInPvpPoints);
}
if (itemData.priceInGameToken != 0) {
guildController.payFromBalance(itemData.priceInGameToken, msgSender);
//_process(controller, itemData.priceInGameToken, msgSender);
}
// mint the item
IItemController(controller.itemController()).mint(item, msgSender);
emit IApplicationEvents.PurchaseShelterItem(msgSender, item, numSoldItems + 1, itemData.priceInPvpPoints, itemData.priceInGameToken);
}
/// @notice clear necessary data to indicate that the guiles leaves the shelter
function clearShelter(address guildController, uint guildId) internal {
_onlyGuildController(guildController);
uint shelterId = _S().guildToShelter[guildId];
if (shelterId != 0) {
// assume, that msgSender shouldn't have permission CHANGE_SHELTER_3 here
// ensure that there is no open position for the shelter on auction
address shelterAuction = IGuildController(guildController).shelterAuctionController();
if (shelterAuction != address(0)) {
uint positionId = IShelterAuction(shelterAuction).positionBySeller(guildId);
if (positionId != 0) revert IAppErrors.AuctionPositionOpened(positionId);
}
delete _S().guildToShelter[guildId];
delete _S().shelterToGuild[shelterId];
emit IApplicationEvents.LeaveShelter(guildId, shelterId);
}
}
//endregion ------------------------ Shelter actions
//region ------------------------ Interaction with auctions
function changeShelterOwner(IController controller, uint shelterId, uint newOwnerGuildId) internal {
// we assume, that all checks are performed on ShelterAuction side, so we need min checks here
address shelterAuction = IGuildController(controller.guildController()).shelterAuctionController();
if (shelterAuction == address(0) || msg.sender != shelterAuction) revert IAppErrors.NotShelterAuction();
uint prevGuildId = _S().shelterToGuild[shelterId];
delete _S().guildToShelter[prevGuildId];
_S().shelterToGuild[shelterId] = newOwnerGuildId;
_S().guildToShelter[newOwnerGuildId] = shelterId;
emit IApplicationEvents.ChangeShelterOwner(shelterId, prevGuildId, newOwnerGuildId);
}
//endregion ------------------------ Interaction with auctions
//region ------------------------ Internal logic
function _getValidGuildId(IGuildController guildController, address user) internal view returns (uint guildId) {
guildId = guildController.memberOf(user);
if (guildId == 0) revert IAppErrors.NotGuildMember();
}
//endregion ------------------------ Internal 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 "../interfaces/IStatController.sol";
import "../interfaces/IHeroController.sol";
import "../interfaces/IAppErrors.sol";
import "../openzeppelin/Math.sol";
import "./CalcLib.sol";
import "./PackingLib.sol";
library StatLib {
using PackingLib for bytes32[];
using PackingLib for bytes32;
using PackingLib for uint32[];
using PackingLib for int32[];
using CalcLib for int32;
//region --------------------------- Constants
/// @notice Version of the contract
/// @dev Should be incremented when contract changed
string public constant STAT_LIB_VERSION = "1.0.0";
uint32 public constant MAX_LEVEL = 99;
uint public constant BASE_EXPERIENCE = 100_000;
uint public constant BIOME_LEVEL_STEP = 5;
uint internal constant _MAX_AMPLIFIER = 1e18;
uint private constant _PRECISION = 1e18;
uint private constant VIRTUAL_LEVEL_GAP = 2;
/// @dev Assume MAX_BIOME * BIOME_LEVEL_STEP < MAX_LEVEL + 1, see dungeonTreasuryReward
uint public constant MAX_POSSIBLE_BIOME = 19;
//endregion --------------------------- Constants
//region --------------------------- Data types
struct BaseMultiplier {
uint minDamage;
uint maxDamage;
uint attackRating;
uint defense;
uint blockRating;
uint life;
uint mana;
}
struct LevelUp {
uint life;
uint mana;
}
struct InitialHero {
IStatController.CoreAttributes core;
BaseMultiplier multiplier;
LevelUp levelUp;
int32 baseLifeChances;
}
enum HeroClasses {
UNKNOWN,
THRALL,
SAVAGE,
MAGE,
ASSASSIN,
GHOST,
HAMMERGINA,
END_SLOT
}
//endregion --------------------------- Data types
//region --------------------------- BASE
function isNetworkWithOldSavage() public view returns (bool) {
return block.chainid == uint(111188) || block.chainid == uint(250);
}
// --- HERO 1 (Slave) ---
function initialHero1() internal pure returns (InitialHero memory) {
return InitialHero({
core: IStatController.CoreAttributes({
strength: 15,
dexterity: 15,
vitality: 30,
energy: 10
}),
multiplier: BaseMultiplier({
minDamage: 0.1e18,
maxDamage: 0.2e18,
attackRating: 2e18,
defense: 2e18,
blockRating: 0.1e18,
life: 1.5e18,
mana: 0.5e18
}),
levelUp: LevelUp({
life: 2e18,
mana: 1e18
}),
baseLifeChances: 5
});
}
// --- HERO 2 (Spata) ---
function initialHero2() internal view returns (InitialHero memory) {
bool old = isNetworkWithOldSavage();
return InitialHero({
core: IStatController.CoreAttributes({
strength: 30,
dexterity: 5,
vitality: 25,
energy: 10
}),
multiplier: BaseMultiplier({
minDamage: 0.15e18,
maxDamage: old ? 0.25e18 : 0.5e18,
attackRating: old ? 2e18 : 3e18,
defense: 1e18,
blockRating: 0.08e18,
life: 1.3e18,
mana: 0.5e18
}),
levelUp: LevelUp({
life: 1.8e18,
mana: 1e18
}),
baseLifeChances: 5
});
}
// --- HERO 3 (Decidia) ---
function initialHero3() internal pure returns (InitialHero memory) {
return InitialHero({
core: IStatController.CoreAttributes({
strength: 10,
dexterity: 15,
vitality: 20,
energy: 25
}),
multiplier: BaseMultiplier({
minDamage: 0.1e18,
maxDamage: 0.2e18,
attackRating: 2e18,
defense: 1e18,
blockRating: 0.1e18,
life: 1e18,
mana: 2e18
}),
levelUp: LevelUp({
life: 1.3e18,
mana: 2e18
}),
baseLifeChances: 5
});
}
// --- HERO 4 (Innatus) ---
function initialHero4() internal pure returns (InitialHero memory) {
return InitialHero({
core: IStatController.CoreAttributes({
strength: 15,
dexterity: 25,
vitality: 15,
energy: 15
}),
multiplier: BaseMultiplier({
minDamage: 0.1e18,
maxDamage: 0.2e18,
attackRating: 4e18,
defense: 3e18,
blockRating: 0.2e18,
life: 1.2e18,
mana: 1e18
}),
levelUp: LevelUp({
life: 1.7e18,
mana: 1.5e18
}),
baseLifeChances: 5
});
}
// --- HERO 5 (F2P) ---
function initialHero5() internal pure returns (InitialHero memory) {
return InitialHero({
core: IStatController.CoreAttributes({
strength: 20,
dexterity: 20,
vitality: 20,
energy: 10
}),
multiplier: BaseMultiplier({
minDamage: 0.15e18,
maxDamage: 0.25e18,
attackRating: 3e18,
defense: 2.5e18,
blockRating: 0.15e18,
life: 1.5e18,
mana: 1.5e18
}),
levelUp: LevelUp({
life: 1.5e18,
mana: 1.5e18
}),
baseLifeChances: 1
});
}
// --- HERO 6 (F2P) HAMMERGINA ---
function initialHero6() internal pure returns (InitialHero memory) {
return InitialHero({
core: IStatController.CoreAttributes({
strength: 50,
dexterity: 30,
vitality: 50,
energy: 15
}),
multiplier: BaseMultiplier({
minDamage: 0.2e18,
maxDamage: 0.3e18,
attackRating: 5e18,
defense: 3e18,
blockRating: 0.15e18,
life: 2e18,
mana: 2e18
}),
levelUp: LevelUp({
life: 1.7e18,
mana: 1.5e18
}),
baseLifeChances: 1
});
}
// ------
function initialHero(uint heroClass) internal view returns (InitialHero memory) {
if (heroClass == 1) {
return initialHero1();
} else if (heroClass == 2) {
return initialHero2();
} else if (heroClass == 3) {
return initialHero3();
} else if (heroClass == 4) {
return initialHero4();
} else if (heroClass == 5) {
return initialHero5();
} else if (heroClass == 6) {
return initialHero6();
} else {
revert IAppErrors.UnknownHeroClass(heroClass);
}
}
//endregion --------------------------- BASE
//region --------------------------- CALCULATIONS
function minDamage(int32 strength, uint heroClass) internal view returns (int32) {
return int32(int(strength.toUint() * initialHero(heroClass).multiplier.minDamage / _PRECISION));
}
function maxDamage(int32 strength, uint heroClass) internal view returns (int32){
return int32(int(strength.toUint() * initialHero(heroClass).multiplier.maxDamage / _PRECISION));
}
function attackRating(int32 dexterity, uint heroClass) internal view returns (int32){
return int32(int(dexterity.toUint() * initialHero(heroClass).multiplier.attackRating / _PRECISION));
}
function defense(int32 dexterity, uint heroClass) internal view returns (int32){
return int32(int(dexterity.toUint() * initialHero(heroClass).multiplier.defense / _PRECISION));
}
function blockRating(int32 dexterity, uint heroClass) internal view returns (int32){
return int32(int(Math.min((dexterity.toUint() * initialHero(heroClass).multiplier.blockRating / _PRECISION), 75)));
}
function life(int32 vitality, uint heroClass, uint32 level) internal view returns (int32){
return int32(int(
(vitality.toUint() * initialHero(heroClass).multiplier.life / _PRECISION)
+ (uint(level) * initialHero(heroClass).levelUp.life / _PRECISION)
));
}
function mana(int32 energy, uint heroClass, uint32 level) internal view returns (int32){
return int32(int(
(energy.toUint() * initialHero(heroClass).multiplier.mana / _PRECISION)
+ (uint(level) * initialHero(heroClass).levelUp.mana / _PRECISION)
));
}
function lifeChances(uint heroClass, uint32 /*level*/) internal view returns (int32){
return initialHero(heroClass).baseLifeChances;
}
function levelExperience(uint32 level) internal pure returns (uint32) {
if (level == 0 || level >= MAX_LEVEL) {
return 0;
}
return uint32(uint(level) * BASE_EXPERIENCE * (67e17 - CalcLib.log2((uint(MAX_LEVEL - level + 2)) * 1e18)) / 1e18);
}
function chanceToHit(
uint attackersAttackRating,
uint defendersDefenceRating,
uint attackersLevel,
uint defendersLevel,
uint arFactor
) internal pure returns (uint) {
attackersAttackRating += attackersAttackRating * arFactor / 100;
uint x = Math.max(attackersAttackRating, 1);
uint y = Math.max(attackersAttackRating + defendersDefenceRating, 1);
uint z = attackersLevel;
uint k = defendersLevel / 2;
uint xy = x * 1e18 / y;
uint zk = z * 1e18 / (attackersLevel + k);
uint base = 2 * xy * zk / 1e18;
return Math.max(Math.min(base, 0.95e18), 0.2e18);
}
function experienceToVirtualLevel(uint experience, uint startFromLevel) internal pure returns (uint level) {
level = startFromLevel;
for (; level < MAX_LEVEL;) {
if (levelExperience(uint32(level)) >= (experience + 1)) {
break;
}
unchecked{++level;}
}
}
function expPerMonster(uint32 monsterExp, uint monsterRarity, uint32 /*heroExp*/, uint32 /*heroCurrentLvl*/, uint /*monsterBiome*/) internal pure returns (uint32) {
// do not reduce exp per level, it is no economical sense
return uint32(uint(monsterExp) + uint(monsterExp) * monsterRarity / _MAX_AMPLIFIER);
}
/// @notice Allow to calculate delta param for {mintDropChance}
function mintDropChanceDelta(uint heroCurrentExp, uint heroCurrentLevel, uint monsterBiome) internal pure returns (uint) {
uint heroBiome = getVirtualLevel(heroCurrentExp, heroCurrentLevel, true) / StatLib.BIOME_LEVEL_STEP + 1;
return heroBiome > monsterBiome ? 2 ** (heroBiome - monsterBiome + 10) : 0;
}
function getVirtualLevel(uint heroCurrentExp, uint heroCurrentLevel, bool withGap) internal pure returns (uint) {
uint virtualLevel = StatLib.experienceToVirtualLevel(heroCurrentExp, heroCurrentLevel);
if (withGap && (virtualLevel + 1) > VIRTUAL_LEVEL_GAP) {
virtualLevel -= VIRTUAL_LEVEL_GAP;
}
return virtualLevel;
}
function initAttributes(
bytes32[] storage attributes,
uint heroClass,
uint32 level,
IStatController.CoreAttributes memory base
) internal returns (uint32[] memory result) {
attributes.setInt32(uint(IStatController.ATTRIBUTES.STRENGTH), base.strength);
attributes.setInt32(uint(IStatController.ATTRIBUTES.DEXTERITY), base.dexterity);
attributes.setInt32(uint(IStatController.ATTRIBUTES.VITALITY), base.vitality);
attributes.setInt32(uint(IStatController.ATTRIBUTES.ENERGY), base.energy);
attributes.setInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MIN), minDamage(base.strength, heroClass));
attributes.setInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MAX), maxDamage(base.strength, heroClass));
attributes.setInt32(uint(IStatController.ATTRIBUTES.ATTACK_RATING), attackRating(base.dexterity, heroClass));
attributes.setInt32(uint(IStatController.ATTRIBUTES.DEFENSE), defense(base.dexterity, heroClass));
attributes.setInt32(uint(IStatController.ATTRIBUTES.BLOCK_RATING), blockRating(base.dexterity, heroClass));
attributes.setInt32(uint(IStatController.ATTRIBUTES.LIFE), life(base.vitality, heroClass, level));
attributes.setInt32(uint(IStatController.ATTRIBUTES.MANA), mana(base.energy, heroClass, level));
attributes.setInt32(uint(IStatController.ATTRIBUTES.LIFE_CHANCES), lifeChances(heroClass, level));
result = new uint32[](3);
result[0] = uint32(life(base.vitality, heroClass, level).toUint());
result[1] = uint32(mana(base.energy, heroClass, level).toUint());
result[2] = uint32(lifeChances(heroClass, uint32(level)).toUint());
}
function updateCoreDependAttributesInMemory(
int32[] memory attributes,
int32[] memory bonus,
uint heroClass,
uint32 level
) internal view returns (int32[] memory) {
int32 strength = attributes[uint(IStatController.ATTRIBUTES.STRENGTH)];
int32 dexterity = attributes[uint(IStatController.ATTRIBUTES.DEXTERITY)];
int32 vitality = attributes[uint(IStatController.ATTRIBUTES.VITALITY)];
int32 energy = attributes[uint(IStatController.ATTRIBUTES.ENERGY)];
attributes[uint(IStatController.ATTRIBUTES.DAMAGE_MIN)] = minDamage(strength, heroClass) + bonus[uint(IStatController.ATTRIBUTES.DAMAGE_MIN)];
attributes[uint(IStatController.ATTRIBUTES.DAMAGE_MAX)] = maxDamage(strength, heroClass) + bonus[uint(IStatController.ATTRIBUTES.DAMAGE_MAX)];
attributes[uint(IStatController.ATTRIBUTES.ATTACK_RATING)] = attackRating(dexterity, heroClass) + bonus[uint(IStatController.ATTRIBUTES.ATTACK_RATING)];
attributes[uint(IStatController.ATTRIBUTES.DEFENSE)] = defense(dexterity, heroClass) + bonus[uint(IStatController.ATTRIBUTES.DEFENSE)];
attributes[uint(IStatController.ATTRIBUTES.BLOCK_RATING)] = blockRating(dexterity, heroClass) + bonus[uint(IStatController.ATTRIBUTES.BLOCK_RATING)];
attributes[uint(IStatController.ATTRIBUTES.LIFE)] = life(vitality, heroClass, level) + bonus[uint(IStatController.ATTRIBUTES.LIFE)];
attributes[uint(IStatController.ATTRIBUTES.MANA)] = mana(energy, heroClass, level) + bonus[uint(IStatController.ATTRIBUTES.MANA)];
return attributes;
}
function updateCoreDependAttributes(
IController controller,
bytes32[] storage attributes,
bytes32[] storage bonusMain,
bytes32[] storage bonusExtra,
IStatController.ChangeableStats memory _heroStats,
uint index,
address heroToken,
int32 base
) internal {
uint heroClass = IHeroController(controller.heroController()).heroClass(heroToken);
if (index == uint(IStatController.ATTRIBUTES.STRENGTH)) {
attributes.setInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MIN),
StatLib.minDamage(base, heroClass)
+ bonusMain.getInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MIN))
+ bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MIN))
);
attributes.setInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MAX),
StatLib.maxDamage(base, heroClass)
+ bonusMain.getInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MAX))
+ bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.DAMAGE_MAX))
);
} else if (index == uint(IStatController.ATTRIBUTES.DEXTERITY)) {
attributes.setInt32(uint(IStatController.ATTRIBUTES.ATTACK_RATING),
StatLib.attackRating(base, heroClass)
+ bonusMain.getInt32(uint(IStatController.ATTRIBUTES.ATTACK_RATING))
+ bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.ATTACK_RATING))
);
attributes.setInt32(uint(IStatController.ATTRIBUTES.DEFENSE),
StatLib.defense(base, heroClass)
+ bonusMain.getInt32(uint(IStatController.ATTRIBUTES.DEFENSE))
+ bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.DEFENSE))
);
attributes.setInt32(uint(IStatController.ATTRIBUTES.BLOCK_RATING),
StatLib.blockRating(base, heroClass)
+ bonusMain.getInt32(uint(IStatController.ATTRIBUTES.BLOCK_RATING))
+ bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.BLOCK_RATING))
);
} else if (index == uint(IStatController.ATTRIBUTES.VITALITY)) {
attributes.setInt32(uint(IStatController.ATTRIBUTES.LIFE),
StatLib.life(base, heroClass, _heroStats.level)
+ bonusMain.getInt32(uint(IStatController.ATTRIBUTES.LIFE))
+ bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.LIFE))
);
} else if (index == uint(IStatController.ATTRIBUTES.ENERGY)) {
attributes.setInt32(uint(IStatController.ATTRIBUTES.MANA),
StatLib.mana(base, heroClass, _heroStats.level)
+ bonusMain.getInt32(uint(IStatController.ATTRIBUTES.MANA))
+ bonusExtra.getInt32(uint(IStatController.ATTRIBUTES.MANA))
);
}
}
function attributesAdd(int32[] memory base, int32[] memory add) internal pure returns (int32[] memory) {
unchecked{
for (uint i; i < base.length; ++i) {
base[i] += add[i];
}
}
return base;
}
// Currently this function is not used
// function attributesRemove(int32[] memory base, int32[] memory remove) internal pure returns (int32[] memory) {
// unchecked{
// for (uint i; i < base.length; ++i) {
// base[i] = CalcLib.minusWithMinFloorI32(base[i], remove[i]);
// }
// }
// return base;
// }
function packChangeableStats(IStatController.ChangeableStats memory stats) internal pure returns (bytes32) {
uint32[] memory cData = new uint32[](5);
cData[0] = stats.level;
cData[1] = stats.experience;
cData[2] = stats.life;
cData[3] = stats.mana;
cData[4] = stats.lifeChances;
return cData.packUint32Array();
}
function unpackChangeableStats(bytes32 data) internal pure returns (IStatController.ChangeableStats memory result) {
uint32[] memory cData = data.unpackUint32Array();
return IStatController.ChangeableStats({
level: cData[0],
experience: cData[1],
life: cData[2],
mana: cData[3],
lifeChances: cData[4]
});
}
function bytesToFullAttributesArray(bytes32[] memory attributes) internal pure returns (int32[] memory result) {
(int32[] memory values, uint8[] memory ids) = attributes.toInt32ArrayWithIds();
return valuesToFullAttributesArray(values, ids);
}
function valuesToFullAttributesArray(int32[] memory values, uint8[] memory ids) internal pure returns (int32[] memory result) {
result = new int32[](uint(IStatController.ATTRIBUTES.END_SLOT));
for (uint i; i < values.length; ++i) {
int32 value = values[i];
if (value != 0) {
result[ids[i]] = value;
}
}
}
//endregion --------------------------- CALCULATIONS
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;
import "../interfaces/IStoryController.sol";
import "../interfaces/IOracle.sol";
import "../interfaces/ITreasury.sol";
import "../interfaces/IGOC.sol";
import "../interfaces/IERC20.sol";
import "../interfaces/IERC721Enumerable.sol";
import "../interfaces/IAppErrors.sol";
import "../interfaces/IApplicationEvents.sol";
import "../lib/CalcLib.sol";
import "../lib/PackingLib.sol";
import "../lib/ItemLib.sol";
import "../lib/StringLib.sol";
library StoryLib {
using CalcLib for uint;
using PackingLib for address;
using PackingLib for uint16;
using PackingLib for bytes32;
using PackingLib for bytes32[];
//region ------------------------ Constants
/// @notice Max number of items that can be minted per iteration in the stories
uint internal constant MAX_MINTED_ITEMS_PER_ITERATION = 3;
//endregion ------------------------ Constants
//region ------------------------ Story logic
/// @notice Make action, increment STORY_XXX hero custom data if the dungeon is completed / hero is killed
function action(IGOC.ActionContext memory ctx, uint16 storyId) internal returns (IGOC.ActionResult memory result) {
if (storyId == 0) revert IAppErrors.ZeroStoryIdAction();
result = IStoryController(ctx.controller.storyController()).storyAction(
ctx.sender,
ctx.dungeonId,
ctx.objectId,
ctx.stageId,
ctx.heroToken,
ctx.heroTokenId,
ctx.biome,
ctx.iteration,
ctx.data
);
if (result.completed || result.kill) {
IStatController statController = IStatController(ctx.controller.statController());
bytes32 index = _getStoryIndex(storyId);
uint curValue = statController.heroCustomData(ctx.heroToken, ctx.heroTokenId, index);
statController.setHeroCustomData(ctx.heroToken, ctx.heroTokenId, index, curValue + 1);
}
}
/// @notice Check if the story is available for the hero
/// The story is available if hero level fits to requirements
/// and if the hero/global custom data requirements are met (current value is inside of [min, max])
function isStoryAvailableForHero(
IStoryController.MainState storage s,
uint16 storyId,
address heroToken,
uint heroTokenId,
address statController
) internal view returns (bool) {
uint reqLvl = s.storyRequiredLevel[storyId];
if (reqLvl != 0 && IStatController(statController).heroStats(heroToken, heroTokenId).level < reqLvl) {
return false;
}
IStoryController.CustomDataRequirementRangePacked[] storage allData = s.storyRequiredHeroData[storyId];
uint len = allData.length;
for (uint i; i < len; ++i) {
IStoryController.CustomDataRequirementRangePacked memory data = allData[i];
if (data.index == bytes32(0)) continue;
(uint64 min, uint64 max, bool isHeroData) = data.data.unpackCustomDataRequirements();
uint value = isHeroData
? IStatController(statController).heroCustomData(heroToken, heroTokenId, data.index)
: IStatController(statController).globalCustomData(data.index);
if (value < uint(min) || value > uint(max)) {
return false;
}
}
return true;
}
/// @notice Update bonus attributes, refresh hero states, initialize and return results
/// @param mintItemsData Source for _mintRandomItems, random item (max 1, probably 0) is selected and put to results
/// @param mintItems_ Function _mintRandomItems is passed here. Parameter is required to make unit tests.
function handleResult(
IStoryController.StoryActionContext memory context,
bytes32[] memory attributesChanges,
bytes32 statsChanges,
bytes32[] memory mintItemsData,
function (IStoryController.StoryActionContext memory, bytes32[] memory) internal returns (address[] memory) mintItems_
) internal returns (IGOC.ActionResult memory result) {
result.heroToken = context.heroToken;
result.heroTokenId = context.heroTokenId;
result.objectId = context.objectId;
int32[] memory attributes = _generateAttributes(attributesChanges);
if (attributes.length != 0) {
context.statController.changeBonusAttributes(IStatController.ChangeAttributesInfo({
heroToken: context.heroToken,
heroTokenId: context.heroTokenId,
changeAttributes: attributes,
add: true,
temporally: true
}));
// changeBonusAttributes can change life and mana, so we need to refresh hero stats. It's safer to do it always
context.heroStats = context.statController.heroStats(context.heroToken, context.heroTokenId);
emit IApplicationEvents.StoryChangeAttributes(
context.objectId,
context.heroToken,
context.heroTokenId,
context.dungeonId,
context.storyId,
context.stageId,
context.iteration,
attributes
);
}
IStoryController.StatsChange memory statsToChange = _generateStats(statsChanges);
if (statsToChange.heal != 0) {
int32 max = context.statController.heroAttribute(context.heroToken, context.heroTokenId, uint(IStatController.ATTRIBUTES.LIFE));
result.heal = max * statsToChange.heal / 100;
}
if (statsToChange.manaRegen != 0) {
int32 max = context.statController.heroAttribute(context.heroToken, context.heroTokenId, uint(IStatController.ATTRIBUTES.MANA));
result.manaRegen = max * statsToChange.manaRegen / 100;
}
if (statsToChange.damage != 0) {
int32 max = context.statController.heroAttribute(context.heroToken, context.heroTokenId, uint(IStatController.ATTRIBUTES.LIFE));
result.damage = max * statsToChange.damage / 100;
if (int32(context.heroStats.life) <= result.damage) {
result.kill = true;
}
}
if (statsToChange.manaConsumed != 0) {
int32 max = context.statController.heroAttribute(context.heroToken, context.heroTokenId, uint(IStatController.ATTRIBUTES.MANA));
result.manaConsumed = CalcLib.minI32(max * statsToChange.manaConsumed / 100, int32(context.heroStats.mana));
}
result.experience = statsToChange.experience;
result.lifeChancesRecovered = statsToChange.lifeChancesRecovered;
if (mintItemsData.length != 0) {
result.mintItems = mintItems_(context, mintItemsData);
}
return result;
}
/// @notice Put data from {heroCustomDatas} and {globalCustomDatas} to {statController}
function handleCustomDataResult(
IStoryController.StoryActionContext memory context,
bytes32[] memory heroCustomDatas,
bytes32[] memory globalCustomDatas
) internal {
uint len = heroCustomDatas.length;
for (uint i; i < len; ++i) {
(bytes32 customDataIndex, int16 value) = heroCustomDatas[i].unpackCustomDataChange();
if (customDataIndex != 0) {
uint curValue = context.statController.heroCustomData(context.heroToken, context.heroTokenId, customDataIndex);
context.statController.setHeroCustomData(
context.heroToken,
context.heroTokenId,
customDataIndex,
value == 0
? 0
: value > 0
? curValue + uint(int(value))
: curValue.minusWithZeroFloor(uint(int(- value)))
);
}
}
len = globalCustomDatas.length;
for (uint i; i < len; ++i) {
(bytes32 customDataIndex, int16 value) = globalCustomDatas[i].unpackCustomDataChange();
if (customDataIndex != 0) {
uint curValue = context.statController.globalCustomData(customDataIndex);
context.statController.setGlobalCustomData(
customDataIndex,
value == 0
? 0
: value > 0
? curValue + uint(int(value))
: curValue.minusWithZeroFloor(uint(int(- value)))
);
}
}
}
/// @notice SIP-003: Randomly select one or several items, break them and increase their fragility by 1%.
function breakItem(IStoryController.StoryActionContext memory context, IStoryController.MainState storage s) internal {
bytes32[] storage breakInfos = s.burnItem[context.answerIdHash];
uint length = breakInfos.length;
for (uint i; i < length; ++i) {
(uint8 slot, uint64 chance, bool stopIfBroken) = breakInfos[i].unpackBreakInfo();
uint8[2] memory slots = _adjustSlotToBreak(slot, context.oracle);
// Normally, {slots} contains two similar items and we need to check only first item.
// But "hands" is a special case: TWO_HAND and RIGHT_HAND should be checked both independently => + cycle by k
uint countSlots = slots[0] == slots[1] ? 1 : 2;
for (uint k = 0; k < countSlots; ++k) {
if (chance != 0 && context.oracle.getRandomNumberInRange(0, 100, 0) <= uint(chance)) {
uint8[] memory busySlots = context.statController.heroItemSlots(context.heroToken, context.heroTokenId);
uint lenBusySlots = busySlots.length;
if (lenBusySlots != 0) {
uint busySlotIndex;
bool itemExist;
if (slot == 0) {
busySlotIndex = context.oracle.getRandomNumberInRange(0, lenBusySlots - 1, 0);
itemExist = true;
} else {
for (uint j; j < lenBusySlots; ++j) {
if (busySlots[j] == slots[k]) {
busySlotIndex = j;
itemExist = true;
break;
}
}
}
if (itemExist) {
// SIP-003: don't burn item but break it
_breakItemInHeroSlot(context, busySlots[busySlotIndex]);
if (stopIfBroken) {
return; // go out of two cycles
}
}
}
}
}
}
}
/// @notice SCB-1016. There are some slots with equal meaning:
/// 1) weapon can be RIGHT-HAND, TWO-HAND (LEFT-HAND is not considered here)
/// 2) ring can be LEFT, RIGHT
/// 3) skill can be SKILL_1, SKILL_2, SKILL_3
/// Story-writer is able to specify only one slot to break.
/// 1) if ONE/TWO-HAND slot is specified then any available weapon (ONE or TWO hands) should be broken
/// 2) if LEFT right is specified then random(LEFT or RIGHT) slot should be broken
/// 3) skills - there is same rule as for the rings
/// @return slots Slots that should be checked. Normally {slots} contains same item twice.
/// The items are different in one case only: [RIGHT_HAND, TWO_HAND]
function _adjustSlotToBreak(uint8 slot, IOracle oracle) internal returns (uint8[2] memory slots) {
if (slot == uint8(IStatController.ItemSlots.RIGHT_HAND) || slot == uint8(IStatController.ItemSlots.TWO_HAND)) {
return [uint8(IStatController.ItemSlots.RIGHT_HAND), uint8(IStatController.ItemSlots.TWO_HAND)];
} else if (slot == uint8(IStatController.ItemSlots.RIGHT_RING) || slot == uint8(IStatController.ItemSlots.LEFT_RING)) {
uint8 selectedSlot = (oracle.getRandomNumber(1, 0) == 0)
? uint8(IStatController.ItemSlots.RIGHT_RING)
: uint8(IStatController.ItemSlots.LEFT_RING);
return [selectedSlot, selectedSlot];
} else if (
slot == uint8(IStatController.ItemSlots.SKILL_1)
|| slot == uint8(IStatController.ItemSlots.SKILL_2)
|| slot == uint8(IStatController.ItemSlots.SKILL_3)
) {
uint rnd = oracle.getRandomNumber(2, 0);
uint8 selectedSlot = (rnd == 0)
? uint8(IStatController.ItemSlots.SKILL_1)
: ((rnd == 1)
? uint8(IStatController.ItemSlots.SKILL_2)
: uint8(IStatController.ItemSlots.SKILL_3));
return [selectedSlot, selectedSlot];
} else {
return [slot, slot];
}
}
/// @notice Update internal hero state, generate {result}
/// @param context We update some fields in place, so memory, not calldata here
function handleAnswer(
IStoryController.AnswerResultId answerResultId,
IStoryController.MainState storage s,
IStoryController.StoryActionContext memory context
) external returns (
IGOC.ActionResult memory result,
uint16 nextPage,
uint16[] memory nextPages
) {
return _handleAnswer(answerResultId, s, context, _mintRandomItems);
}
/// @notice Update internal hero state, generate {result}
/// @param context We update some fields in place, so memory, not calldata here
/// @param mintItems_ Function _mintRandomItems is passed here. Parameter is required to make unit tests.
function _handleAnswer(
IStoryController.AnswerResultId answerResultId,
IStoryController.MainState storage s,
IStoryController.StoryActionContext memory context,
function (IStoryController.StoryActionContext memory, bytes32[] memory) internal returns (address[] memory) mintItems_
) internal returns (
IGOC.ActionResult memory result,
uint16 nextPage,
uint16[] memory nextPages
) {
result.objectId = context.objectId;
result.heroTokenId = context.heroTokenId;
result.heroToken = context.heroToken;
nextPages = s.nextPageIds[context.storyId.packStoryNextPagesId(
context.pageId,
context.heroClassFromAnswerHash,
context.answerNumber,
uint8(answerResultId)
)];
nextPage = _getNextPage(context.oracle, nextPages);
// number of items that can be minted inside single iteration in the story is limited
// if the max is reached the minting is silently skipped
// we assume here, that mintItems_ mints only 1 item so it's not necessary to limit number of minted items inside mintItems_
uint mintedInIteration = _getMintedInIteration(s, context);
if (answerResultId == IStoryController.AnswerResultId.SUCCESS) {
result = handleResult(
context,
s.successInfoAttributes[context.answerIdHash],
s.successInfoStats[context.answerIdHash],
mintedInIteration < MAX_MINTED_ITEMS_PER_ITERATION ? s.successInfoMintItems[context.answerIdHash] : new bytes32[](0),
mintItems_
);
handleCustomDataResult(
context,
s.customDataResult[context.storyId.packStoryCustomDataResult(
context.pageId,
context.heroClassFromAnswerHash,
context.answerNumber,
uint8(IStoryController.CustomDataResult.HERO_SUCCESS)
)],
s.customDataResult[context.storyId.packStoryCustomDataResult(
context.pageId,
context.heroClassFromAnswerHash,
context.answerNumber,
uint8(IStoryController.CustomDataResult.GLOBAL_SUCCESS)
)]
);
} else {
result = handleResult(
context,
s.failInfoAttributes[context.answerIdHash],
s.failInfoStats[context.answerIdHash],
mintedInIteration < MAX_MINTED_ITEMS_PER_ITERATION ? s.failInfoMintItems[context.answerIdHash] : new bytes32[](0),
mintItems_
);
handleCustomDataResult(
context,
s.customDataResult[context.storyId.packStoryCustomDataResult(
context.pageId,
context.heroClassFromAnswerHash,
context.answerNumber,
uint8(IStoryController.CustomDataResult.HERO_FAIL)
)],
s.customDataResult[context.storyId.packStoryCustomDataResult(
context.pageId,
context.heroClassFromAnswerHash,
context.answerNumber,
uint8(IStoryController.CustomDataResult.GLOBAL_FAIL)
)]
);
}
if (result.mintItems.length != 0) {
_setMintedInIteration(s, context, mintedInIteration + result.mintItems.length);
}
}
/// @notice Check if the user has already minted an item within the current iteration of the story.
/// if the item is already minted any additional minting should be skipped without revert
function _getMintedInIteration(IStoryController.MainState storage s, IStoryController.StoryActionContext memory context)
internal view returns (uint countMintedItems) {
return s.mintedInIteration[context.heroToken.packStoryHeroStateId(context.heroTokenId, context.storyId)][context.iteration];
}
/// @notice Mark that the user has already minted an item within the current iteration of the story
/// Only minting of the single item is allowed per iteration
function _setMintedInIteration(
IStoryController.MainState storage s,
IStoryController.StoryActionContext memory context,
uint newCountMintedItems
) internal {
s.mintedInIteration[context.heroToken.packStoryHeroStateId(context.heroTokenId, context.storyId)][context.iteration] = newCountMintedItems;
}
/// @notice Revert if {heroAnswers} doesn't contain {answerIdHash}
function checkAnswerIndexValid(bytes32[] memory heroAnswers, bytes32 answerIdHash) internal pure {
uint len = heroAnswers.length;
for (uint i; i < len; ++i) {
if (heroAnswers[i] == answerIdHash) return;
}
revert IAppErrors.NotAnswer();
}
/// @notice Clear heroState for the current story
/// @return nextObjs Default nextObjectsRewrite for the current page (values for 0 hero class)
function finishStory(IStoryController.StoryActionContext memory ctx, IStoryController.MainState storage s) internal returns (
uint32[] memory nextObjs
) {
delete s.heroState[ctx.heroToken.packStoryHeroStateId(ctx.heroTokenId, ctx.storyId)];
// It's not necessary to clear mintedInIteration because for each hero each object has a sequence of iterations
// that is not reset on changing dungeons
return s.nextObjectsRewrite[ctx.storyId.packStoryPageId(ctx.pageId, 0)];
}
//endregion ------------------------ Story logic
//region ------------------------ Internal utils for story logic
/// @dev This function is made separate to simplify unit testing
function _mintRandomItems(IStoryController.StoryActionContext memory context, bytes32[] memory mintItemsData) internal returns (
address[] memory
) {
uint len = mintItemsData.length;
address[] memory mintItems = new address[](len);
uint32[] memory mintItemsChances = new uint32[](len);
for (uint i; i < len; ++i) {
(mintItems[i], mintItemsChances[i]) = mintItemsData[i].unpackItemMintInfo();
}
return ItemLib.mintRandomItems(ItemLib.MintItemInfo({
mintItems: mintItems,
mintItemsChances: mintItemsChances,
amplifier: 0,
seed: 0,
oracle: context.oracle,
magicFind: 0,
destroyItems: 0,
maxItems: 1, // MINT ONLY 1 ITEM!
mintDropChanceDelta: StatLib.mintDropChanceDelta(context.heroStats.experience, uint8(context.heroStats.level), context.biome),
mintDropChanceNgLevelMultiplier: 1e18
}));
}
/// @param attributesChanges Values+ids packed using toBytes32ArrayWithIds
function _generateAttributes(bytes32[] memory attributesChanges) internal pure returns (int32[] memory attributes) {
if (attributesChanges.length != 0) {
(int32[] memory values, uint8[] memory ids) = attributesChanges.toInt32ArrayWithIds();
uint len = ids.length;
if (len != 0) {
attributes = new int32[](uint(IStatController.ATTRIBUTES.END_SLOT));
for (uint i; i < len; ++i) {
int32 value = values[i];
attributes[ids[i]] = value;
}
}
}
return attributes;
}
function _generateStats(bytes32 statsChanges) internal pure returns (IStoryController.StatsChange memory change) {
(
change.experience,
change.heal,
change.manaRegen,
change.lifeChancesRecovered,
change.damage,
change.manaConsumed
) = statsChanges.unpackStatsChange();
return change;
}
/// @notice Break the item from the given {slot} (i.e. reduce item's durability to 0) and take it off
/// Broken item is taken off also.
function _breakItemInHeroSlot(IStoryController.StoryActionContext memory ctx, uint8 slot) internal {
(address itemAdr, uint itemId) = ctx.statController.heroItemSlot(ctx.heroToken, uint64(ctx.heroTokenId), slot).unpackNftId();
// take off the broken item and mark it as broken
ctx.itemController.takeOffDirectly(itemAdr, itemId, ctx.heroToken, ctx.heroTokenId, slot, ctx.sender, true);
// add 1% of fragility, deprecated
// ctx.itemController.incBrokenItemFragility(itemAdr, itemId);
emit IApplicationEvents.ItemBroken(
ctx.heroToken,
ctx.heroTokenId,
ctx.dungeonId,
ctx.objectId,
itemAdr,
itemId,
ctx.stageId,
ctx.iteration
);
}
function _getNextPage(IOracle oracle, uint16[] memory pages) internal returns (uint16) {
if (pages.length == 0) {
return 0;
}
if (pages.length == 1) {
return pages[0];
}
return pages[oracle.getRandomNumberInRange(0, pages.length - 1, 0)];
}
function _getStoryIndex(uint16 storyId) internal pure returns (bytes32) {
return bytes32(abi.encodePacked("STORY_", StringLib._toString(storyId)));
}
//endregion ------------------------ Internal utils for story logic
//region ------------------------ Check answers
function checkAnswer(
IStoryController.StoryActionContext memory context,
IStoryController.MainState storage s
) external returns (IStoryController.AnswerResultId result) {
result = checkAnswerAttributes(context, context.answerIdHash, s);
if (result == IStoryController.AnswerResultId.SUCCESS) {
result = checkAnswerItems(context, context.answerIdHash, s);
}
if (result == IStoryController.AnswerResultId.SUCCESS) {
result = checkAnswerTokens(context, context.answerIdHash, s);
}
if (result == IStoryController.AnswerResultId.SUCCESS) {
result = checkAnswerDelay(context);
}
if (result == IStoryController.AnswerResultId.SUCCESS) {
result = checkAnswerHeroCustomData(context, context.answerIdHash, s);
}
if (result == IStoryController.AnswerResultId.SUCCESS) {
result = checkAnswerGlobalCustomData(context, context.answerIdHash, s);
}
if (result == IStoryController.AnswerResultId.SUCCESS) {
result = checkAnswerRandom(context);
}
}
/// @notice Check if hero attribute values meet attribute requirements for the given answer
function checkAnswerAttributes(
IStoryController.StoryActionContext memory context,
bytes32 answerIndex,
IStoryController.MainState storage s
) internal view returns (IStoryController.AnswerResultId) {
bytes32[] storage reqs = s.attributeRequirements[answerIndex];
uint length = reqs.length;
for (uint i; i < length; ++i) {
(uint8 attributeIndex, int32 value, bool isCore) = reqs[i].unpackStoryAttributeRequirement();
if (isCore) {
IStatController.CoreAttributes memory base = context.statController.heroBaseAttributes(context.heroToken, context.heroTokenId);
if (attributeIndex == uint8(IStatController.ATTRIBUTES.STRENGTH) && base.strength < value) {
return IStoryController.AnswerResultId.ATTRIBUTE_FAIL;
}
if (attributeIndex == uint8(IStatController.ATTRIBUTES.DEXTERITY) && base.dexterity < value) {
return IStoryController.AnswerResultId.ATTRIBUTE_FAIL;
}
if (attributeIndex == uint8(IStatController.ATTRIBUTES.VITALITY) && base.vitality < value) {
return IStoryController.AnswerResultId.ATTRIBUTE_FAIL;
}
if (attributeIndex == uint8(IStatController.ATTRIBUTES.ENERGY) && base.energy < value) {
return IStoryController.AnswerResultId.ATTRIBUTE_FAIL;
}
} else {
int32 attr = context.statController.heroAttribute(context.heroToken, context.heroTokenId, attributeIndex);
if (attr < value) {
return IStoryController.AnswerResultId.ATTRIBUTE_FAIL;
}
}
}
return IStoryController.AnswerResultId.SUCCESS;
}
/// @notice Check item requirements for the given answer, check following issues:
/// 1) For equipped item: check if it is on balance
/// 2) For not equipped item: burn first owned item if requireItemBurn OR check that not equipped item is on balance
function checkAnswerItems(
IStoryController.StoryActionContext memory context,
bytes32 answerIndex,
IStoryController.MainState storage s
) internal returns (IStoryController.AnswerResultId) {
bytes32[] storage reqs = s.itemRequirements[answerIndex];
uint length = reqs.length;
for (uint i; i < length; ++i) {
(address item, bool requireItemBurn, bool requireItemEquipped) = reqs[i].unpackStoryItemRequirement();
// equipped item is on balance of the heroToken, not on balance of the sender
if (requireItemEquipped && IERC721Enumerable(item).balanceOf(context.heroToken) == 0) {
revert IAppErrors.NotItem1();
}
if (requireItemBurn) {
_burnFirstOwnedItem(context, item);
}
if (!requireItemEquipped && !requireItemBurn) {
if (IERC721Enumerable(item).balanceOf(context.sender) == 0) revert IAppErrors.NotItem2();
}
}
return IStoryController.AnswerResultId.SUCCESS;
}
/// @notice burn first owned item and generate event
/// @dev Use separate function to workaround stack too deep
function _burnFirstOwnedItem(IStoryController.StoryActionContext memory context, address item) internal {
uint itemId = IERC721Enumerable(item).tokenOfOwnerByIndex(context.sender, 0);
context.itemController.destroy(item, itemId); // destroy reverts if the item is equipped
emit IApplicationEvents.NotEquippedItemBurned(
context.heroToken,
context.heroTokenId,
context.dungeonId,
context.storyId,
item,
itemId,
context.stageId,
context.iteration
);
}
/// @notice Ensure that the sender has enough amounts of the required tokens, send fees to the treasury
function checkAnswerTokens(
IStoryController.StoryActionContext memory context,
bytes32 answerIndex,
IStoryController.MainState storage s
) internal returns (IStoryController.AnswerResultId) {
bytes32[] memory reqs = s.tokenRequirements[answerIndex];
uint length = reqs.length;
for (uint i; i < length; ++i) {
(address token, uint88 amount, bool requireTransfer) = reqs[i].unpackStoryTokenRequirement();
amount = uint88(adjustTokenAmountToGameToken(uint(amount), context.controller));
if (amount != 0) {
uint balance = IERC20(token).balanceOf(context.sender);
if (balance < uint(amount)) revert IAppErrors.NotEnoughAmount(balance, uint(amount));
if (requireTransfer) {
context.controller.process(token, amount, context.sender);
}
}
}
return IStoryController.AnswerResultId.SUCCESS;
}
function adjustTokenAmountToGameToken(uint amount, IController controller) internal view returns(uint) {
return amount * controller.gameTokenPrice() / 1e18;
}
/// @notice Generate error randomly
function checkAnswerRandom(IStoryController.StoryActionContext memory context) internal returns (IStoryController.AnswerResultId) {
(uint32 random,,) = context.answerAttributes.unpackStorySimpleRequirement();
if (random != 0 && random < 100) {
if (context.oracle.getRandomNumber(100, 0) > uint(random)) {
return IStoryController.AnswerResultId.RANDOM_FAIL;
}
} else if (random > 100) {
revert IAppErrors.NotRandom(random);
}
return IStoryController.AnswerResultId.SUCCESS;
}
/// @notice Ensure that the answer was given fast enough
function checkAnswerDelay(IStoryController.StoryActionContext memory context) internal view returns (IStoryController.AnswerResultId) {
(,uint32 delay,) = context.answerAttributes.unpackStorySimpleRequirement();
if (delay != 0) {
uint lastCall = uint(context.heroLastActionTS);
if (lastCall != 0 && lastCall < block.timestamp && block.timestamp - lastCall > uint(delay)) {
return IStoryController.AnswerResultId.DELAY_FAIL;
}
}
return IStoryController.AnswerResultId.SUCCESS;
}
function checkAnswerHeroCustomData(
IStoryController.StoryActionContext memory context,
bytes32 answerIndex,
IStoryController.MainState storage s
) internal view returns (IStoryController.AnswerResultId) {
return _checkAnswerCustomData(context, s.heroCustomDataRequirement[answerIndex], true);
}
function checkAnswerGlobalCustomData(
IStoryController.StoryActionContext memory context,
bytes32 answerIndex,
IStoryController.MainState storage s
) internal view returns (IStoryController.AnswerResultId) {
return _checkAnswerCustomData(context, s.globalCustomDataRequirement[answerIndex], false);
}
function _checkAnswerCustomData(
IStoryController.StoryActionContext memory context,
IStoryController.CustomDataRequirementPacked[] memory datas,
bool heroCustomData
) internal view returns (IStoryController.AnswerResultId) {
uint len = datas.length;
for (uint i; i < len; ++i) {
IStoryController.CustomDataRequirementPacked memory data = datas[i];
if (data.index != 0) {
(uint valueMin, uint valueMax, bool mandatory) = data.data.unpackCustomDataRequirements();
uint heroValue = heroCustomData
? context.statController.heroCustomData(context.heroToken, context.heroTokenId, data.index)
: context.statController.globalCustomData(data.index);
if (heroValue < valueMin || heroValue > valueMax) {
if (mandatory) {
if (heroCustomData) {
revert IAppErrors.NotHeroData();
} else {
revert IAppErrors.NotGlobalData();
}
} else {
return heroCustomData
? IStoryController.AnswerResultId.HERO_CUSTOM_DATA_FAIL
: IStoryController.AnswerResultId.GLOBAL_CUSTOM_DATA_FAIL;
}
}
}
}
return IStoryController.AnswerResultId.SUCCESS;
}
//endregion ------------------------ Check answers
}// 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
// 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
pragma solidity ^0.8.4;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
library FixedPointMathLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error ExpOverflow();
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error FactorialOverflow();
/// @dev The operation failed, due to an overflow.
error RPowOverflow();
/// @dev The mantissa is too big to fit.
error MantissaOverflow();
/// @dev The operation failed, due to an multiplication overflow.
error MulWadFailed();
/// @dev The operation failed, due to an multiplication overflow.
error SMulWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error DivWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error SDivWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error MulDivFailed();
/// @dev The division failed, as the denominator is zero.
error DivFailed();
/// @dev The full precision multiply-divide operation failed, either due
/// to the result being larger than 256 bits, or a division by a zero.
error FullMulDivFailed();
/// @dev The output is undefined, as the input is less-than-or-equal to zero.
error LnWadUndefined();
/// @dev The input outside the acceptable domain.
error OutOfDomain();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The scalar of ETH and most ERC20s.
uint256 internal constant WAD = 1e18;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIMPLIFIED FIXED POINT OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `(x * y) / WAD` rounded down.
function mulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if mul(y, gt(x, div(not(0), y))) {
mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
revert(0x1c, 0x04)
}
z := div(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down.
function sMulWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require((x == 0 || z / x == y) && !(x == -1 && y == type(int256).min))`.
if iszero(gt(or(iszero(x), eq(sdiv(z, x), y)), lt(not(x), eq(y, shl(255, 1))))) {
mstore(0x00, 0xedcd4dd4) // `SMulWadFailed()`.
revert(0x1c, 0x04)
}
z := sdiv(z, WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
function rawMulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
function rawSMulWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded up.
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if mul(y, gt(x, div(not(0), y))) {
mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded up, but without overflow checks.
function rawMulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down.
function divWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && (WAD == 0 || x <= type(uint256).max / WAD))`.
if iszero(mul(y, iszero(mul(WAD, gt(x, div(not(0), WAD)))))) {
mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
revert(0x1c, 0x04)
}
z := div(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down.
function sDivWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, WAD)
// Equivalent to `require(y != 0 && ((x * WAD) / WAD == x))`.
if iszero(and(iszero(iszero(y)), eq(sdiv(z, WAD), x))) {
mstore(0x00, 0x5c43740d) // `SDivWadFailed()`.
revert(0x1c, 0x04)
}
z := sdiv(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
function rawDivWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
function rawSDivWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded up.
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && (WAD == 0 || x <= type(uint256).max / WAD))`.
if iszero(mul(y, iszero(mul(WAD, gt(x, div(not(0), WAD)))))) {
mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded up, but without overflow and divide by zero checks.
function rawDivWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
}
}
/// @dev Equivalent to `x` to the power of `y`.
/// because `x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)`.
function powWad(int256 x, int256 y) internal pure returns (int256) {
// Using `ln(x)` means `x` must be greater than 0.
return expWad((lnWad(x) * y) / int256(WAD));
}
/// @dev Returns `exp(x)`, denominated in `WAD`.
/// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln
function expWad(int256 x) internal pure returns (int256 r) {
unchecked {
// When the result is less than 0.5 we return zero.
// This happens when `x <= (log(1e-18) * 1e18) ~ -4.15e19`.
if (x <= -41446531673892822313) return r;
/// @solidity memory-safe-assembly
assembly {
// When the result is greater than `(2**255 - 1) / 1e18` we can not represent it as
// an int. This happens when `x >= floor(log((2**255 - 1) / 1e18) * 1e18) ≈ 135`.
if iszero(slt(x, 135305999368893231589)) {
mstore(0x00, 0xa37bfec9) // `ExpOverflow()`.
revert(0x1c, 0x04)
}
}
// `x` is now in the range `(-42, 136) * 1e18`. Convert to `(-42, 136) * 2**96`
// for more intermediate precision and a binary basis. This base conversion
// is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
x = (x << 78) / 5 ** 18;
// Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
// of two such that exp(x) = exp(x') * 2**k, where k is an integer.
// Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96;
x = x - k * 54916777467707473351141471128;
// `k` is in the range `[-61, 195]`.
// Evaluate using a (6, 7)-term rational approximation.
// `p` is made monic, we'll multiply by a scale factor later.
int256 y = x + 1346386616545796478920950773328;
y = ((y * x) >> 96) + 57155421227552351082224309758442;
int256 p = y + x - 94201549194550492254356042504812;
p = ((p * y) >> 96) + 28719021644029726153956944680412240;
p = p * x + (4385272521454847904659076985693276 << 96);
// We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
int256 q = x - 2855989394907223263936484059900;
q = ((q * x) >> 96) + 50020603652535783019961831881945;
q = ((q * x) >> 96) - 533845033583426703283633433725380;
q = ((q * x) >> 96) + 3604857256930695427073651918091429;
q = ((q * x) >> 96) - 14423608567350463180887372962807573;
q = ((q * x) >> 96) + 26449188498355588339934803723976023;
/// @solidity memory-safe-assembly
assembly {
// Div in assembly because solidity adds a zero check despite the unchecked.
// The q polynomial won't have zeros in the domain as all its roots are complex.
// No scaling is necessary because p is already `2**96` too large.
r := sdiv(p, q)
}
// r should be in the range `(0.09, 0.25) * 2**96`.
// We now need to multiply r by:
// - The scale factor `s ≈ 6.031367120`.
// - The `2**k` factor from the range reduction.
// - The `1e18 / 2**96` factor for base conversion.
// We do this all at once, with an intermediate result in `2**213`
// basis, so the final right shift is always by a positive amount.
r = int256(
(uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)
);
}
}
/// @dev Returns `ln(x)`, denominated in `WAD`.
/// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln
function lnWad(int256 x) internal pure returns (int256 r) {
/// @solidity memory-safe-assembly
assembly {
// We want to convert `x` from `10**18` fixed point to `2**96` fixed point.
// We do this by multiplying by `2**96 / 10**18`. But since
// `ln(x * C) = ln(x) + ln(C)`, we can simply do nothing here
// and add `ln(2**96 / 10**18)` at the end.
// Compute `k = log2(x) - 96`, `r = 159 - k = 255 - log2(x) = 255 ^ log2(x)`.
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// We place the check here for more optimal stack operations.
if iszero(sgt(x, 0)) {
mstore(0x00, 0x1615e638) // `LnWadUndefined()`.
revert(0x1c, 0x04)
}
// forgefmt: disable-next-item
r := xor(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff))
// Reduce range of x to (1, 2) * 2**96
// ln(2^k * x) = k * ln(2) + ln(x)
x := shr(159, shl(r, x))
// Evaluate using a (8, 8)-term rational approximation.
// `p` is made monic, we will multiply by a scale factor later.
// forgefmt: disable-next-item
let p := sub( // This heavily nested expression is to avoid stack-too-deep for via-ir.
sar(96, mul(add(43456485725739037958740375743393,
sar(96, mul(add(24828157081833163892658089445524,
sar(96, mul(add(3273285459638523848632254066296,
x), x))), x))), x)), 11111509109440967052023855526967)
p := sub(sar(96, mul(p, x)), 45023709667254063763336534515857)
p := sub(sar(96, mul(p, x)), 14706773417378608786704636184526)
p := sub(mul(p, x), shl(96, 795164235651350426258249787498))
// We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
// `q` is monic by convention.
let q := add(5573035233440673466300451813936, x)
q := add(71694874799317883764090561454958, sar(96, mul(x, q)))
q := add(283447036172924575727196451306956, sar(96, mul(x, q)))
q := add(401686690394027663651624208769553, sar(96, mul(x, q)))
q := add(204048457590392012362485061816622, sar(96, mul(x, q)))
q := add(31853899698501571402653359427138, sar(96, mul(x, q)))
q := add(909429971244387300277376558375, sar(96, mul(x, q)))
// `p / q` is in the range `(0, 0.125) * 2**96`.
// Finalization, we need to:
// - Multiply by the scale factor `s = 5.549…`.
// - Add `ln(2**96 / 10**18)`.
// - Add `k * ln(2)`.
// - Multiply by `10**18 / 2**96 = 5**18 >> 78`.
// The q polynomial is known not to have zeros in the domain.
// No scaling required because p is already `2**96` too large.
p := sdiv(p, q)
// Multiply by the scaling factor: `s * 5**18 * 2**96`, base is now `5**18 * 2**192`.
p := mul(1677202110996718588342820967067443963516166, p)
// Add `ln(2) * k * 5**18 * 2**192`.
// forgefmt: disable-next-item
p := add(mul(16597577552685614221487285958193947469193820559219878177908093499208371, sub(159, r)), p)
// Add `ln(2**96 / 10**18) * 5**18 * 2**192`.
p := add(600920179829731861736702779321621459595472258049074101567377883020018308, p)
// Base conversion: mul `2**18 / 2**192`.
r := sar(174, p)
}
}
/// @dev Returns `W_0(x)`, denominated in `WAD`.
/// See: https://en.wikipedia.org/wiki/Lambert_W_function
/// a.k.a. Product log function. This is an approximation of the principal branch.
function lambertW0Wad(int256 x) internal pure returns (int256 w) {
// forgefmt: disable-next-item
unchecked {
if ((w = x) <= -367879441171442322) revert OutOfDomain(); // `x` less than `-1/e`.
int256 wad = int256(WAD);
int256 p = x;
uint256 c; // Whether we need to avoid catastrophic cancellation.
uint256 i = 4; // Number of iterations.
if (w <= 0x1ffffffffffff) {
if (-0x4000000000000 <= w) {
i = 1; // Inputs near zero only take one step to converge.
} else if (w <= -0x3ffffffffffffff) {
i = 32; // Inputs near `-1/e` take very long to converge.
}
} else if (w >> 63 == 0) {
/// @solidity memory-safe-assembly
assembly {
// Inline log2 for more performance, since the range is small.
let v := shr(49, w)
let l := shl(3, lt(0xff, v))
l := add(or(l, byte(and(0x1f, shr(shr(l, v), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020504060203020504030106050205030304010505030400000000)), 49)
w := sdiv(shl(l, 7), byte(sub(l, 31), 0x0303030303030303040506080c13))
c := gt(l, 60)
i := add(2, add(gt(l, 53), c))
}
} else {
int256 ll = lnWad(w = lnWad(w));
/// @solidity memory-safe-assembly
assembly {
// `w = ln(x) - ln(ln(x)) + b * ln(ln(x)) / ln(x)`.
w := add(sdiv(mul(ll, 1023715080943847266), w), sub(w, ll))
i := add(3, iszero(shr(68, x)))
c := iszero(shr(143, x))
}
if (c == 0) {
do { // If `x` is big, use Newton's so that intermediate values won't overflow.
int256 e = expWad(w);
/// @solidity memory-safe-assembly
assembly {
let t := mul(w, div(e, wad))
w := sub(w, sdiv(sub(t, x), div(add(e, t), wad)))
}
if (p <= w) break;
p = w;
} while (--i != 0);
/// @solidity memory-safe-assembly
assembly {
w := sub(w, sgt(w, 2))
}
return w;
}
}
do { // Otherwise, use Halley's for faster convergence.
int256 e = expWad(w);
/// @solidity memory-safe-assembly
assembly {
let t := add(w, wad)
let s := sub(mul(w, e), mul(x, wad))
w := sub(w, sdiv(mul(s, wad), sub(mul(e, t), sdiv(mul(add(t, wad), s), add(t, t)))))
}
if (p <= w) break;
p = w;
} while (--i != c);
/// @solidity memory-safe-assembly
assembly {
w := sub(w, sgt(w, 2))
}
// For certain ranges of `x`, we'll use the quadratic-rate recursive formula of
// R. Iacono and J.P. Boyd for the last iteration, to avoid catastrophic cancellation.
if (c != 0) {
int256 t = w | 1;
/// @solidity memory-safe-assembly
assembly {
x := sdiv(mul(x, wad), t)
}
x = (t * (wad + lnWad(x)));
/// @solidity memory-safe-assembly
assembly {
w := sdiv(x, add(wad, t))
}
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* GENERAL NUMBER UTILITIES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Calculates `floor(x * y / d)` with full precision.
/// Throws if result overflows a uint256 or when `d` is zero.
/// Credit to Remco Bloemen under MIT license: https://2π.com/21/muldiv
function fullMulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
for {} 1 {} {
// 512-bit multiply `[p1 p0] = x * y`.
// Compute the product mod `2**256` and mod `2**256 - 1`
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that `product = p1 * 2**256 + p0`.
// Least significant 256 bits of the product.
result := mul(x, y) // Temporarily use `result` as `p0` to save gas.
let mm := mulmod(x, y, not(0))
// Most significant 256 bits of the product.
let p1 := sub(mm, add(result, lt(mm, result)))
// Handle non-overflow cases, 256 by 256 division.
if iszero(p1) {
if iszero(d) {
mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
revert(0x1c, 0x04)
}
result := div(result, d)
break
}
// Make sure the result is less than `2**256`. Also prevents `d == 0`.
if iszero(gt(d, p1)) {
mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
revert(0x1c, 0x04)
}
/*------------------- 512 by 256 division --------------------*/
// Make division exact by subtracting the remainder from `[p1 p0]`.
// Compute remainder using mulmod.
let r := mulmod(x, y, d)
// `t` is the least significant bit of `d`.
// Always greater or equal to 1.
let t := and(d, sub(0, d))
// Divide `d` by `t`, which is a power of two.
d := div(d, t)
// Invert `d mod 2**256`
// Now that `d` is an odd number, it has an inverse
// modulo `2**256` such that `d * inv = 1 mod 2**256`.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, `d * inv = 1 mod 2**4`.
let inv := xor(2, mul(3, d))
// Now use 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.
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**8
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**16
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**32
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**64
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**128
result :=
mul(
// Divide [p1 p0] by the factors of two.
// Shift in bits from `p1` into `p0`. For this we need
// to flip `t` such that it is `2**256 / t`.
or(
mul(sub(p1, gt(r, result)), add(div(sub(0, t), t), 1)),
div(sub(result, r), t)
),
// inverse mod 2**256
mul(inv, sub(2, mul(d, inv)))
)
break
}
}
}
/// @dev Calculates `floor(x * y / d)` with full precision, rounded up.
/// Throws if result overflows a uint256 or when `d` is zero.
/// Credit to Uniswap-v3-core under MIT license:
/// https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol
function fullMulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 result) {
result = fullMulDiv(x, y, d);
/// @solidity memory-safe-assembly
assembly {
if mulmod(x, y, d) {
result := add(result, 1)
if iszero(result) {
mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Returns `floor(x * y / d)`.
/// Reverts if `x * y` overflows, or `d` is zero.
function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(d != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {
mstore(0x00, 0xad251c27) // `MulDivFailed()`.
revert(0x1c, 0x04)
}
z := div(mul(x, y), d)
}
}
/// @dev Returns `ceil(x * y / d)`.
/// Reverts if `x * y` overflows, or `d` is zero.
function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(d != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {
mstore(0x00, 0xad251c27) // `MulDivFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(mul(x, y), d))), div(mul(x, y), d))
}
}
/// @dev Returns `ceil(x / d)`.
/// Reverts if `d` is zero.
function divUp(uint256 x, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
if iszero(d) {
mstore(0x00, 0x65244e4e) // `DivFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(x, d))), div(x, d))
}
}
/// @dev Returns `max(0, x - y)`.
function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
/// @dev Exponentiate `x` to `y` by squaring, denominated in base `b`.
/// Reverts if the computation overflows.
function rpow(uint256 x, uint256 y, uint256 b) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(b, iszero(y)) // `0 ** 0 = 1`. Otherwise, `0 ** n = 0`.
if x {
z := xor(b, mul(xor(b, x), and(y, 1))) // `z = isEven(y) ? scale : x`
let half := shr(1, b) // Divide `b` by 2.
// Divide `y` by 2 every iteration.
for { y := shr(1, y) } y { y := shr(1, y) } {
let xx := mul(x, x) // Store x squared.
let xxRound := add(xx, half) // Round to the nearest number.
// Revert if `xx + half` overflowed, or if `x ** 2` overflows.
if or(lt(xxRound, xx), shr(128, x)) {
mstore(0x00, 0x49f7642b) // `RPowOverflow()`.
revert(0x1c, 0x04)
}
x := div(xxRound, b) // Set `x` to scaled `xxRound`.
// If `y` is odd:
if and(y, 1) {
let zx := mul(z, x) // Compute `z * x`.
let zxRound := add(zx, half) // Round to the nearest number.
// If `z * x` overflowed or `zx + half` overflowed:
if or(xor(div(zx, x), z), lt(zxRound, zx)) {
// Revert if `x` is non-zero.
if iszero(iszero(x)) {
mstore(0x00, 0x49f7642b) // `RPowOverflow()`.
revert(0x1c, 0x04)
}
}
z := div(zxRound, b) // Return properly scaled `zxRound`.
}
}
}
}
}
/// @dev Returns the square root of `x`.
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// `floor(sqrt(2**15)) = 181`. `sqrt(2**15) - 181 = 2.84`.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// Let `y = x / 2**r`. We check `y >= 2**(k + 8)`
// but shift right by `k` bits to ensure that if `x >= 256`, then `y >= 256`.
let r := shl(7, lt(0xffffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffffff, shr(r, x))))
z := shl(shr(1, r), z)
// Goal was to get `z*z*y` within a small factor of `x`. More iterations could
// get y in a tighter range. Currently, we will have y in `[256, 256*(2**16))`.
// We ensured `y >= 256` so that the relative difference between `y` and `y+1` is small.
// That's not possible if `x < 256` but we can just verify those cases exhaustively.
// Now, `z*z*y <= x < z*z*(y+1)`, and `y <= 2**(16+8)`, and either `y >= 256`, or `x < 256`.
// Correctness can be checked exhaustively for `x < 256`, so we assume `y >= 256`.
// Then `z*sqrt(y)` is within `sqrt(257)/sqrt(256)` of `sqrt(x)`, or about 20bps.
// For `s` in the range `[1/256, 256]`, the estimate `f(s) = (181/1024) * (s+1)`
// is in the range `(1/2.84 * sqrt(s), 2.84 * sqrt(s))`,
// with largest error when `s = 1` and when `s = 256` or `1/256`.
// Since `y` is in `[256, 256*(2**16))`, let `a = y/65536`, so that `a` is in `[1/256, 256)`.
// Then we can estimate `sqrt(y)` using
// `sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2**18`.
// There is no overflow risk here since `y < 2**136` after the first branch above.
z := shr(18, mul(z, add(shr(r, x), 65536))) // A `mul()` is saved from starting `z` at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
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)))
// If `x+1` is a perfect square, the Babylonian method cycles between
// `floor(sqrt(x))` and `ceil(sqrt(x))`. This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
z := sub(z, lt(div(x, z), z))
}
}
/// @dev Returns the cube root of `x`.
/// Credit to bout3fiddy and pcaversaccio under AGPLv3 license:
/// https://github.com/pcaversaccio/snekmate/blob/main/src/utils/Math.vy
function cbrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
z := div(shl(div(r, 3), shl(lt(0xf, shr(r, x)), 0xf)), xor(7, mod(r, 3)))
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := sub(z, lt(div(x, mul(z, z)), z))
}
}
/// @dev Returns the square root of `x`, denominated in `WAD`.
function sqrtWad(uint256 x) internal pure returns (uint256 z) {
unchecked {
z = 10 ** 9;
if (x <= type(uint256).max / 10 ** 36 - 1) {
x *= 10 ** 18;
z = 1;
}
z *= sqrt(x);
}
}
/// @dev Returns the cube root of `x`, denominated in `WAD`.
function cbrtWad(uint256 x) internal pure returns (uint256 z) {
unchecked {
z = 10 ** 12;
if (x <= (type(uint256).max / 10 ** 36) * 10 ** 18 - 1) {
if (x >= type(uint256).max / 10 ** 36) {
x *= 10 ** 18;
z = 10 ** 6;
} else {
x *= 10 ** 36;
z = 1;
}
}
z *= cbrt(x);
}
}
/// @dev Returns the factorial of `x`.
function factorial(uint256 x) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
if iszero(lt(x, 58)) {
mstore(0x00, 0xaba0f2a2) // `FactorialOverflow()`.
revert(0x1c, 0x04)
}
for { result := 1 } x { x := sub(x, 1) } { result := mul(result, x) }
}
}
/// @dev Returns the log2 of `x`.
/// Equivalent to computing the index of the most significant bit (MSB) of `x`.
/// Returns 0 if `x` is zero.
function log2(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// forgefmt: disable-next-item
r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020504060203020504030106050205030304010505030400000000))
}
}
/// @dev Returns the log2 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log2Up(uint256 x) internal pure returns (uint256 r) {
r = log2(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(shl(r, 1), x))
}
}
/// @dev Returns the log10 of `x`.
/// Returns 0 if `x` is zero.
function log10(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
if iszero(lt(x, 100000000000000000000000000000000000000)) {
x := div(x, 100000000000000000000000000000000000000)
r := 38
}
if iszero(lt(x, 100000000000000000000)) {
x := div(x, 100000000000000000000)
r := add(r, 20)
}
if iszero(lt(x, 10000000000)) {
x := div(x, 10000000000)
r := add(r, 10)
}
if iszero(lt(x, 100000)) {
x := div(x, 100000)
r := add(r, 5)
}
r := add(r, add(gt(x, 9), add(gt(x, 99), add(gt(x, 999), gt(x, 9999)))))
}
}
/// @dev Returns the log10 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log10Up(uint256 x) internal pure returns (uint256 r) {
r = log10(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(exp(10, r), x))
}
}
/// @dev Returns the log256 of `x`.
/// Returns 0 if `x` is zero.
function log256(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(shr(3, r), lt(0xff, shr(r, x)))
}
}
/// @dev Returns the log256 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log256Up(uint256 x) internal pure returns (uint256 r) {
r = log256(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(shl(shl(3, r), 1), x))
}
}
/// @dev Returns the scientific notation format `mantissa * 10 ** exponent` of `x`.
/// Useful for compressing prices (e.g. using 25 bit mantissa and 7 bit exponent).
function sci(uint256 x) internal pure returns (uint256 mantissa, uint256 exponent) {
/// @solidity memory-safe-assembly
assembly {
mantissa := x
if mantissa {
if iszero(mod(mantissa, 1000000000000000000000000000000000)) {
mantissa := div(mantissa, 1000000000000000000000000000000000)
exponent := 33
}
if iszero(mod(mantissa, 10000000000000000000)) {
mantissa := div(mantissa, 10000000000000000000)
exponent := add(exponent, 19)
}
if iszero(mod(mantissa, 1000000000000)) {
mantissa := div(mantissa, 1000000000000)
exponent := add(exponent, 12)
}
if iszero(mod(mantissa, 1000000)) {
mantissa := div(mantissa, 1000000)
exponent := add(exponent, 6)
}
if iszero(mod(mantissa, 10000)) {
mantissa := div(mantissa, 10000)
exponent := add(exponent, 4)
}
if iszero(mod(mantissa, 100)) {
mantissa := div(mantissa, 100)
exponent := add(exponent, 2)
}
if iszero(mod(mantissa, 10)) {
mantissa := div(mantissa, 10)
exponent := add(exponent, 1)
}
}
}
}
/// @dev Convenience function for packing `x` into a smaller number using `sci`.
/// The `mantissa` will be in bits [7..255] (the upper 249 bits).
/// The `exponent` will be in bits [0..6] (the lower 7 bits).
/// Use `SafeCastLib` to safely ensure that the `packed` number is small
/// enough to fit in the desired unsigned integer type:
/// ```
/// uint32 packed = SafeCastLib.toUint32(FixedPointMathLib.packSci(777 ether));
/// ```
function packSci(uint256 x) internal pure returns (uint256 packed) {
(x, packed) = sci(x); // Reuse for `mantissa` and `exponent`.
/// @solidity memory-safe-assembly
assembly {
if shr(249, x) {
mstore(0x00, 0xce30380c) // `MantissaOverflow()`.
revert(0x1c, 0x04)
}
packed := or(shl(7, x), packed)
}
}
/// @dev Convenience function for unpacking a packed number from `packSci`.
function unpackSci(uint256 packed) internal pure returns (uint256 unpacked) {
unchecked {
unpacked = (packed >> 7) * 10 ** (packed & 0x7f);
}
}
/// @dev Returns the average of `x` and `y`.
function avg(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = (x & y) + ((x ^ y) >> 1);
}
}
/// @dev Returns the average of `x` and `y`.
function avg(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = (x >> 1) + (y >> 1) + (x & y & 1);
}
}
/// @dev Returns the absolute value of `x`.
function abs(int256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(sar(255, x), add(sar(255, x), x))
}
}
/// @dev Returns the absolute distance between `x` and `y`.
function dist(int256 x, int256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(mul(xor(sub(y, x), sub(x, y)), sgt(x, y)), sub(y, x))
}
}
/// @dev Returns the minimum of `x` and `y`.
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), lt(y, x)))
}
}
/// @dev Returns the minimum of `x` and `y`.
function min(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), slt(y, x)))
}
}
/// @dev Returns the maximum of `x` and `y`.
function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), gt(y, x)))
}
}
/// @dev Returns the maximum of `x` and `y`.
function max(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), sgt(y, x)))
}
}
/// @dev Returns `x`, bounded to `minValue` and `maxValue`.
function clamp(uint256 x, uint256 minValue, uint256 maxValue)
internal
pure
returns (uint256 z)
{
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, minValue), gt(minValue, x)))
z := xor(z, mul(xor(z, maxValue), lt(maxValue, z)))
}
}
/// @dev Returns `x`, bounded to `minValue` and `maxValue`.
function clamp(int256 x, int256 minValue, int256 maxValue) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, minValue), sgt(minValue, x)))
z := xor(z, mul(xor(z, maxValue), slt(maxValue, z)))
}
}
/// @dev Returns greatest common divisor of `x` and `y`.
function gcd(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
for { z := x } y {} {
let t := y
y := mod(z, y)
z := t
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* RAW NUMBER OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns `x + y`, without checking for overflow.
function rawAdd(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x + y;
}
}
/// @dev Returns `x + y`, without checking for overflow.
function rawAdd(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x + y;
}
}
/// @dev Returns `x - y`, without checking for underflow.
function rawSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x - y;
}
}
/// @dev Returns `x - y`, without checking for underflow.
function rawSub(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x - y;
}
}
/// @dev Returns `x * y`, without checking for overflow.
function rawMul(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x * y;
}
}
/// @dev Returns `x * y`, without checking for overflow.
function rawMul(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x * y;
}
}
/// @dev Returns `x / y`, returning 0 if `y` is zero.
function rawDiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(x, y)
}
}
/// @dev Returns `x / y`, returning 0 if `y` is zero.
function rawSDiv(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(x, y)
}
}
/// @dev Returns `x % y`, returning 0 if `y` is zero.
function rawMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mod(x, y)
}
}
/// @dev Returns `x % y`, returning 0 if `y` is zero.
function rawSMod(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := smod(x, y)
}
}
/// @dev Returns `(x + y) % d`, return 0 if `d` if zero.
function rawAddMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := addmod(x, y, d)
}
}
/// @dev Returns `(x * y) % d`, return 0 if `d` if zero.
function rawMulMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mulmod(x, y, d)
}
}
}// 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.
}
}
}// SPDX-License-Identifier: BUSL-1.1
/**
▒▓▒ ▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓███▓▓▒ ▒▒▒▒▓▓▓▒▓▓▓▓▓▓▓██▓
▒██▒▓▓▓▓█▓██████████████████▓ ▒▒▒▓███████████████▒
▒██▒▓█████████████████████▒ ▒▓██████████▓███████
▒███████████▓▒ ▒███▓▓██████▓
█████████▒ ▒▓▒▓███████▒
███████▓ ▒▒▒▒▒▓▓█▓▒ ▓█▓████████
▒▒▒▒▒ ▒▒▒▒▓▓▓█████▒ ▓█████████▓
▒▓▓▓▒▓██████▓ ▒▓▓████████▒
▒██▓▓▓███████▒ ▒▒▓███▓████
▒███▓█████▒ ▒▒█████▓██▓
██████▓ ▒▒▒▓██▓██▓█████▒
▒▒▓▓▒ ▒██▓▒▓▓████████
▓█████▓███████▓
██▓▓██████████▒
▒█████████████
███████████▓
▒▓▓▓▓▓▓▒▓ ▒█████████▒ ▒▓▓
▒▓█▒ ▒▒█▒▒ ▓██████ ▒▒▓▓▒
▒▒█▒ ▓▒ ▒████ ▒▓█▓█▓▒
▓▒██▓▒ ██ ▒▓█▓▓▓██▒
▓█▓▓▓▓▓█▓▓▓▒ ▒▒▒ ▒▒▒▓▓▓▓▒▓▒▒▓▒▓▓▓▓▓▓▓▓▒ ▒▓█▒ ▒▓▒▓█▓
▒▓█▓▓▓▓▓▓▓▓▓▓▒ ▒▒▒▓▒ ▒▒▒▓▓ ▓▓ ▓▓█▓ ▒▒▓▓ ▒▒█▒ ▒▓▒▓█▓
▒▒▓▓▓▒▓▒ ▒▓▓▓▒█▒ ▒▒▒█▒ ▒▒█▓▒▒▒▓▓▓▒ ▓██▓▓▓▓▓▓▓███▓
▒ ▒▓▓█▓ ▒▓▓▓▓█▓█▓ ▒█▓▓▒ ▓▓█▓▒▓█▓▒▒ ▓█▓ ▓███▓
▓▓▒ ▒▒▓▓█▓▒▒▓█▒ ▒▓██▓ ▓██▓▒ ▒█▓ ▓▓██ ▒▓▓▓▒▒▓█▓ ▒▓████▒
██▓▓▒▒▒▒▓▓███▓▒ ▒▓▓▓▓▒▒ ▒▓▓▓▓▓▓▓▒▒▒▓█▓▓▓▓█▓▓▒▒▓▓▓▓▓▒ ▒▓████▓▒ ▓▓███████▓▓▒
*/
pragma solidity 0.8.23;
import "../interfaces/IAppErrors.sol";
import "../interfaces/IERC20.sol";
import "../interfaces/IERC721.sol";
import "../interfaces/IGuildBank.sol";
import "../interfaces/IGuildController.sol";
import {IApplicationEvents} from "../interfaces/IApplicationEvents.sol";
contract GuildBank is IGuildBank {
//region ------------------------ CONSTANTS
/// @notice Version of the contract
/// @dev Should be incremented when contract changed
string public constant VERSION = "1.0.1";
//endregion ------------------------ CONSTANTS
//region ------------------------ Members
IGuildController immutable public guildController;
uint immutable public guildId;
//endregion ------------------------ Members
//region ------------------------ Restrictions and constructor
function _onlyGuildController(address msgSender) internal view {
if (msgSender != address(guildController)) revert IAppErrors.GuildControllerOnly();
}
constructor (address guildController_, uint guildId_) {
guildController = IGuildController(guildController_);
guildId = guildId_;
}
//endregion ------------------------ Restrictions and constructor
//region ------------------------ ERC20
function transfer(address token, address recipient, uint amount) external {
_onlyGuildController(msg.sender);
IERC20(token).transfer(recipient, amount);
emit IApplicationEvents.GuildBankTransfer(token, recipient, amount);
}
function approve(address token, address spender, uint256 amount) external returns (bool) {
_onlyGuildController(msg.sender);
return IERC20(token).approve(spender, amount);
}
//endregion ------------------------ ERC20
//region ------------------------ ERC721
function transferNft(address to, address nft, uint256 tokenId) external {
_onlyGuildController(msg.sender);
IERC721(nft).transferFrom(address(this), to, tokenId);
emit IApplicationEvents.GuildBankTransferNft(to, nft, tokenId);
}
function transferNftMulti(address to, address[] memory nfts, uint256[] memory tokenIds) external {
_onlyGuildController(msg.sender);
uint len = nfts.length;
if (len != tokenIds.length) revert IAppErrors.LengthsMismatch();
for (uint i; i < len; ++i) {
IERC721(nfts[i]).transferFrom(address(this), to, tokenIds[i]);
}
emit IApplicationEvents.GuildBankTransferNftMulti(to, nfts, tokenIds);
}
function approveNft(address to, address nft, uint256 tokenId) external {
_onlyGuildController(msg.sender);
IERC721(nft).approve(to, tokenId);
}
function approveNftMulti(address to, address[] memory nfts, uint256[] memory tokenIds) external {
_onlyGuildController(msg.sender);
uint len = nfts.length;
if (len != tokenIds.length) revert IAppErrors.LengthsMismatch();
for (uint i; i < len; ++i) {
IERC721(nfts[i]).approve(to, tokenIds[i]);
}
}
//endregion ------------------------ ERC721
}{
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 50
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"metadata": {
"useLiteralContent": true
},
"libraries": {
"contracts/lib/EventLib.sol": {
"EventLib": "0x22e2625f9d8c28cb4bce944e9d64efb4388ea991"
},
"contracts/lib/MonsterLib.sol": {
"MonsterLib": "0xdf837f0327bbf85b066c400f17b2b2727f94cb2f"
}
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[],"name":"EmptyObjects","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ErrorNotDeployer","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ErrorNotDungeonFactory","type":"error"},{"inputs":[],"name":"FightDelay","type":"error"},{"inputs":[{"internalType":"uint8","name":"biome","type":"uint8"}],"name":"GenObjectIdBiomeOverflow","type":"error"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"GenObjectIdIdOverflow","type":"error"},{"inputs":[{"internalType":"uint256","name":"subType","type":"uint256"}],"name":"GenObjectIdSubTypeOverflow","type":"error"},{"inputs":[{"internalType":"int256","name":"value","type":"int256"}],"name":"IntOutOfRange","type":"error"},{"inputs":[{"internalType":"int256","name":"value","type":"int256"}],"name":"IntValueOutOfRange","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"LengthsMismatch","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[],"name":"ObjectNotFound","type":"error"},{"inputs":[{"internalType":"uint32","name":"chance","type":"uint32"}],"name":"TooHighChance","type":"error"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"TooHighValue","type":"error"},{"inputs":[{"internalType":"uint8","name":"objectSubType","type":"uint8"}],"name":"UnknownObjectTypeForSubtype","type":"error"},{"inputs":[{"internalType":"uint8","name":"objectType","type":"uint8"}],"name":"UnknownObjectTypeGoc1","type":"error"},{"inputs":[{"internalType":"uint8","name":"objectType","type":"uint8"}],"name":"UnknownObjectTypeGoc2","type":"error"},{"inputs":[{"internalType":"uint8","name":"objectType","type":"uint8"}],"name":"UnknownObjectTypeGocLib1","type":"error"},{"inputs":[{"internalType":"uint8","name":"objectType","type":"uint8"}],"name":"UnknownObjectTypeGocLib2","type":"error"},{"inputs":[{"internalType":"uint32","name":"chances","type":"uint32"},{"internalType":"uint32","name":"maxChances","type":"uint32"}],"name":"WrongChances","type":"error"},{"inputs":[],"name":"WrongGetObjectTypeInput","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"ZeroChance","type":"error"},{"inputs":[],"name":"ZeroStoryIdAction","type":"error"},{"inputs":[],"name":"ZeroValue","type":"error"},{"inputs":[],"name":"ZeroValueNotAllowed","type":"error"},{"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":"uint32","name":"objectId","type":"uint32"},{"components":[{"internalType":"uint8","name":"biome","type":"uint8"},{"internalType":"uint16","name":"eventId","type":"uint16"},{"internalType":"enum IGOC.ObjectSubType","name":"subType","type":"uint8"},{"internalType":"uint32","name":"goodChance","type":"uint32"},{"components":[{"internalType":"uint8[]","name":"ids","type":"uint8[]"},{"internalType":"int32[]","name":"values","type":"int32[]"}],"internalType":"struct IGOC.AttributeGenerateInfo","name":"goodAttributes","type":"tuple"},{"components":[{"internalType":"uint8[]","name":"ids","type":"uint8[]"},{"internalType":"int32[]","name":"values","type":"int32[]"}],"internalType":"struct IGOC.AttributeGenerateInfo","name":"badAttributes","type":"tuple"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"int32","name":"heal","type":"int32"},{"internalType":"int32","name":"manaRegen","type":"int32"},{"internalType":"int32","name":"lifeChancesRecovered","type":"int32"},{"internalType":"int32","name":"damage","type":"int32"},{"internalType":"int32","name":"manaConsumed","type":"int32"},{"internalType":"address[]","name":"mintItems","type":"address[]"},{"internalType":"uint32[]","name":"mintItemsChances","type":"uint32[]"}],"indexed":false,"internalType":"struct IGOC.EventRegInfo","name":"eventRegInfo","type":"tuple"}],"name":"EventRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"objectId","type":"uint32"},{"components":[{"internalType":"uint16","name":"monsterId","type":"uint16"},{"internalType":"uint8","name":"biome","type":"uint8"},{"internalType":"enum IGOC.ObjectSubType","name":"subType","type":"uint8"},{"internalType":"uint8[]","name":"attributeIds","type":"uint8[]"},{"internalType":"int32[]","name":"attributeValues","type":"int32[]"},{"internalType":"uint8","name":"level","type":"uint8"},{"internalType":"uint8","name":"race","type":"uint8"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"uint8","name":"maxDropItems","type":"uint8"},{"internalType":"address","name":"attackToken","type":"address"},{"internalType":"uint64","name":"attackTokenId","type":"uint64"},{"internalType":"uint8","name":"attackType","type":"uint8"},{"internalType":"address[]","name":"mintItems","type":"address[]"},{"internalType":"uint32[]","name":"mintItemsChances","type":"uint32[]"}],"indexed":false,"internalType":"struct IGOC.MonsterGenInfo","name":"monsterGenInfo","type":"tuple"}],"name":"MonsterRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"objectId","type":"uint32"}],"name":"ObjectRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"dungeonId","type":"uint64"},{"indexed":false,"internalType":"uint32","name":"objectId","type":"uint32"},{"indexed":false,"internalType":"enum IGOC.ObjectType","name":"objectType","type":"uint8"},{"indexed":false,"internalType":"address","name":"hero","type":"address"},{"indexed":false,"internalType":"uint256","name":"heroId","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"stageId","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"iteration","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"},{"components":[{"internalType":"bool","name":"kill","type":"bool"},{"internalType":"bool","name":"completed","type":"bool"},{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"address[]","name":"mintItems","type":"address[]"},{"internalType":"int32","name":"heal","type":"int32"},{"internalType":"int32","name":"manaRegen","type":"int32"},{"internalType":"int32","name":"lifeChancesRecovered","type":"int32"},{"internalType":"int32","name":"damage","type":"int32"},{"internalType":"int32","name":"manaConsumed","type":"int32"},{"internalType":"uint32","name":"objectId","type":"uint32"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"uint256","name":"iteration","type":"uint256"},{"internalType":"uint32[]","name":"rewriteNextObject","type":"uint32[]"}],"indexed":false,"internalType":"struct IGOC.ActionResult","name":"result","type":"tuple"},{"indexed":false,"internalType":"uint256","name":"salt","type":"uint256"}],"name":"ObjectResultEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"address","name":"oldLogic","type":"address"}],"name":"RevisionIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"objectId","type":"uint32"},{"indexed":false,"internalType":"uint16","name":"storyId","type":"uint16"}],"name":"StoryRegistered","type":"event"},{"inputs":[],"name":"CONTROLLABLE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint64","name":"dungeonId","type":"uint64"},{"internalType":"uint32","name":"objectId","type":"uint32"},{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"uint8","name":"stageId","type":"uint8"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"action","outputs":[{"components":[{"internalType":"bool","name":"kill","type":"bool"},{"internalType":"bool","name":"completed","type":"bool"},{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"address[]","name":"mintItems","type":"address[]"},{"internalType":"int32","name":"heal","type":"int32"},{"internalType":"int32","name":"manaRegen","type":"int32"},{"internalType":"int32","name":"lifeChancesRecovered","type":"int32"},{"internalType":"int32","name":"damage","type":"int32"},{"internalType":"int32","name":"manaConsumed","type":"int32"},{"internalType":"uint32","name":"objectId","type":"uint32"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"uint256","name":"iteration","type":"uint256"},{"internalType":"uint32[]","name":"rewriteNextObject","type":"uint32[]"}],"internalType":"struct IGOC.ActionResult","name":"","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"controller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"created","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"createdBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"objectId","type":"uint32"}],"name":"getEventInfo","outputs":[{"components":[{"internalType":"uint32","name":"goodChance","type":"uint32"},{"internalType":"bytes32[]","name":"goodAttributes","type":"bytes32[]"},{"internalType":"bytes32[]","name":"badAttributes","type":"bytes32[]"},{"internalType":"bytes32","name":"statsChange","type":"bytes32"},{"internalType":"bytes32[]","name":"mintItems","type":"bytes32[]"}],"internalType":"struct IGOC.EventInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFightDelay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"uint32","name":"objId","type":"uint32"}],"name":"getIteration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"getLastHeroFightTs","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"hero","type":"address"},{"internalType":"uint256","name":"heroId","type":"uint256"},{"internalType":"uint32","name":"objectId","type":"uint32"}],"name":"getMonsterInfo","outputs":[{"components":[{"internalType":"uint16","name":"monsterId","type":"uint16"},{"internalType":"uint8","name":"biome","type":"uint8"},{"internalType":"enum IGOC.ObjectSubType","name":"subType","type":"uint8"},{"internalType":"uint8[]","name":"attributeIds","type":"uint8[]"},{"internalType":"int32[]","name":"attributeValues","type":"int32[]"},{"internalType":"uint8","name":"level","type":"uint8"},{"internalType":"uint8","name":"race","type":"uint8"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"uint8","name":"maxDropItems","type":"uint8"},{"internalType":"address","name":"attackToken","type":"address"},{"internalType":"uint64","name":"attackTokenId","type":"uint64"},{"internalType":"uint8","name":"attackType","type":"uint8"},{"internalType":"address[]","name":"mintItems","type":"address[]"},{"internalType":"uint32[]","name":"mintItemsChances","type":"uint32[]"}],"internalType":"struct IGOC.MonsterGenInfo","name":"mGenInfo","type":"tuple"},{"components":[{"internalType":"bool","name":"generated","type":"bool"},{"internalType":"uint8","name":"turnCounter","type":"uint8"},{"internalType":"int32","name":"hp","type":"int32"},{"internalType":"uint32","name":"amplifier","type":"uint32"}],"internalType":"struct IGOC.GeneratedMonster","name":"gen","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"heroNgLevel","type":"uint8"}],"name":"getMonsterMultiplier","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint8","name":"biome","type":"uint8"},{"internalType":"enum IGOC.ObjectSubType","name":"subType","type":"uint8"}],"name":"getObjectIds","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"objectId","type":"uint32"}],"name":"getObjectMeta","outputs":[{"internalType":"uint8","name":"biome","type":"uint8"},{"internalType":"uint8","name":"objectSubType","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"objectId","type":"uint32"}],"name":"getObjectTypeBySubType","outputs":[{"internalType":"enum IGOC.ObjectType","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8[]","name":"cTypes","type":"uint8[]"},{"internalType":"uint32[]","name":"chances","type":"uint32[]"},{"internalType":"uint8","name":"biome","type":"uint8"},{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"}],"name":"getRandomObject","outputs":[{"internalType":"uint32","name":"objectId","type":"uint32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"objectId","type":"uint32"}],"name":"getStoryId","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"oldLogic","type":"address"}],"name":"increaseRevision","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"controller_","type":"address"}],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"uint32","name":"objId","type":"uint32"}],"name":"isAvailableForHero","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"objectId","type":"uint32"}],"name":"isBattleObject","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"value_","type":"address"}],"name":"isController","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"value_","type":"address"}],"name":"isGovernance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"heroToken","type":"address"},{"internalType":"uint256","name":"heroTokenId","type":"uint256"},{"internalType":"uint32","name":"objectId","type":"uint32"}],"name":"open","outputs":[{"internalType":"uint256","name":"iteration","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"previousImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"biome","type":"uint8"},{"internalType":"uint16","name":"eventId","type":"uint16"},{"internalType":"enum IGOC.ObjectSubType","name":"subType","type":"uint8"},{"internalType":"uint32","name":"goodChance","type":"uint32"},{"components":[{"internalType":"uint8[]","name":"ids","type":"uint8[]"},{"internalType":"int32[]","name":"values","type":"int32[]"}],"internalType":"struct IGOC.AttributeGenerateInfo","name":"goodAttributes","type":"tuple"},{"components":[{"internalType":"uint8[]","name":"ids","type":"uint8[]"},{"internalType":"int32[]","name":"values","type":"int32[]"}],"internalType":"struct IGOC.AttributeGenerateInfo","name":"badAttributes","type":"tuple"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"int32","name":"heal","type":"int32"},{"internalType":"int32","name":"manaRegen","type":"int32"},{"internalType":"int32","name":"lifeChancesRecovered","type":"int32"},{"internalType":"int32","name":"damage","type":"int32"},{"internalType":"int32","name":"manaConsumed","type":"int32"},{"internalType":"address[]","name":"mintItems","type":"address[]"},{"internalType":"uint32[]","name":"mintItemsChances","type":"uint32[]"}],"internalType":"struct IGOC.EventRegInfo","name":"regInfo","type":"tuple"}],"name":"registerEvent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint16","name":"monsterId","type":"uint16"},{"internalType":"uint8","name":"biome","type":"uint8"},{"internalType":"enum IGOC.ObjectSubType","name":"subType","type":"uint8"},{"internalType":"uint8[]","name":"attributeIds","type":"uint8[]"},{"internalType":"int32[]","name":"attributeValues","type":"int32[]"},{"internalType":"uint8","name":"level","type":"uint8"},{"internalType":"uint8","name":"race","type":"uint8"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"uint8","name":"maxDropItems","type":"uint8"},{"internalType":"address","name":"attackToken","type":"address"},{"internalType":"uint64","name":"attackTokenId","type":"uint64"},{"internalType":"uint8","name":"attackType","type":"uint8"},{"internalType":"address[]","name":"mintItems","type":"address[]"},{"internalType":"uint32[]","name":"mintItemsChances","type":"uint32[]"}],"internalType":"struct IGOC.MonsterGenInfo","name":"monsterGenInfo","type":"tuple"}],"name":"registerMonster","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"storyId","type":"uint16"},{"internalType":"uint8","name":"biome","type":"uint8"},{"internalType":"enum IGOC.ObjectSubType","name":"subType","type":"uint8"}],"name":"registerStory","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"objectId","type":"uint32"}],"name":"removeObject","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revision","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]Contract Creation Code
60806040523480156200001157600080fd5b507ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff1615906001600160401b03166000811580156200005d5750825b90506000826001600160401b031660011480156200007a5750303b155b90508115801562000089575080155b15620000a85760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b03191660011785558315620000d757845460ff60401b1916680100000000000000001785555b83156200011e57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505061583580620001336000396000f3fe608060405234801561001057600080fd5b506004361061017a5760003560e01c80637cc96380116100d45780637cc96380146102f05780638154916e146102f8578063909aa53914610318578063936725ec146103385780639d0bcca014610369578063a8ca89ff1461037e578063b414c55314610391578063b429afeb146103a4578063d447d4df146103b7578063d5e46dcd146103ca578063dee1f0e4146103f7578063f2c6c6fb1461040a578063f3deb3911461041d578063f77c479114610430578063ffa1ad741461043857600080fd5b806306efb7851461017f5780630e2ecde2146101a55780630e91f15e146101cb578063172ae49a146101eb57806319ab453c146102005780631c10968c14610213578063325a19f11461021b5780634593144c146102235780634675bfff1461022b5780634fac6ccd1461023e5780635f1d20a1146102515780636742e2d61461026457806369c3d472146102875780636f383c0f146102a857806375a8e7da146102d0575b600080fd5b61019261018d366004613e0a565b61045c565b6040519081526020015b60405180910390f35b6101b86101b3366004613e53565b610471565b60405161ffff909116815260200161019c565b6101de6101d9366004613e99565b61047c565b60405161019c9190613ece565b6101fe6101f9366004613e53565b610488565b005b6101fe61020e366004613f12565b61049c565b610192610596565b6101926105a5565b6101926105d9565b6101fe610239366004614226565b610609565b6101fe61024c366004613f12565b61061a565b61019261025f3660046143b3565b61072b565b6102776102723660046143d0565b610736565b604051901515815260200161019c565b61029a6102953660046143d0565b610755565b60405161019c929190614651565b6102bb6102b63660046146a0565b61077c565b60405163ffffffff909116815260200161019c565b6102e36102de366004613e53565b61079f565b60405161019c9190614761565b6101926107b0565b61030b610306366004613e53565b6107ce565b60405161019c91906147e3565b61032b610326366004614808565b6107d9565b60405161019c9190614a0e565b61035c604051806040016040528060058152602001640312e302e360dc1b81525081565b60405161019c9190614a71565b610371610804565b60405161019c9190614a84565b6101fe61038c366004614a98565b610834565b6101fe61039f366004614add565b61084c565b6102776103b2366004613f12565b61085d565b6102776103c5366004613e53565b610882565b6103dd6103d8366004613e53565b61088d565b6040805160ff93841681529290911660208301520161019c565b610277610405366004613f12565b6108a2565b6101926104183660046143d0565b610927565b61019261042b3660046143d0565b61093c565b610371610949565b61035c60405180604001604052806005815260200164312e312e3560d81b81525081565b60006104688383610979565b90505b92915050565b600061046b826109b2565b606061046883836109df565b610499610493610949565b82610a1f565b50565b60006104a6610ad1565b805490915060ff600160401b82041615906001600160401b03166000811580156104cd5750825b90506000826001600160401b031660011480156104e95750303b155b9050811580156104f7575080155b156105155760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561053f57845460ff60401b1916600160401b1785555b61054886610af5565b831561058e57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b60006105a0610b06565b905090565b60006105a06105d560017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b614c25565b5490565b60006105a06105d560017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f1614c25565b610499610614610949565b82610b19565b33301461066c5760405162461bcd60e51b815260206004820152601b60248201527a24b731b932b0b9b2903932bb34b9b4b7b7103337b93134b23232b760291b60448201526064015b60405180910390fd5b600061068a6105d560016000805160206157e0833981519152614c25565b610695906001614c38565b90506106b7816106b460016000805160206157e0833981519152614c25565b55565b6106e6826106b460017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e4614c25565b604080518281526001600160a01b03841660208201527ff27e2ef832a4eb8ed8ec553b875eecd44764cda95b1c24170e281539e0a869c8910160405180910390a15050565b600061046b82610c0d565b600061074b610743610949565b858585610c20565b90505b9392505050565b61075d613c30565b610765613ca2565b610770858585610ca7565b91509150935093915050565b6000610793610789610949565b8787878787610d87565b90505b95945050505050565b6107a7613cc9565b61046b82610e14565b60006105a06105d560016000805160206157e0833981519152614c25565b600061046b82610f67565b6107e1613cf6565b6107f86107ec610949565b89898989898989610fb5565b98975050505050505050565b60006105a06105d560017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e4614c25565b61084761083f610949565b8484846114bf565b505050565b610499610857610949565b8261154e565b6000610867610949565b6001600160a01b0316826001600160a01b0316149050919050565b600061046b8261162f565b60008061089983611674565b91509150915091565b6000816001600160a01b03166108b6610949565b6001600160a01b0316635aa6e6756040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108f3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109179190614c56565b6001600160a01b03161492915050565b600061074b610934610949565b858585611682565b600061074b848484611835565b60006105a06105d560017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c3618614c25565b6000610983611867565b600701600061099b6001600160a01b0386168561188b565b815260200190815260200160002054905092915050565b60006109bc611867565b63ffffffff909216600090815260059290920160205250604090205461ffff1690565b60606104686109ec611867565b6001016000610a0c86866024811115610a0757610a07614412565b6118da565b81526020019081526020016000206118ef565b610a28826118fc565b6000610a32611867565b63ffffffff8316600090815260209190915260409020549050610a53611867565b63ffffffff831660008181526020929092526040822091909155610a9190610a79611867565b60008481526001919091016020526040902090611988565b5060405163ffffffff831681527f93afafd4d79cc89e4afba48bcd8cac7b8a35b45dfb55afe60cccae721124cd0f906020015b60405180910390a1505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b610afd611994565b610499816119bb565b6000610b10611867565b60080154905090565b610b22826118fc565b610b36816101800151826101a00151611ace565b6000610b4f826000015183604001518460200151611c07565b90507322e2625f9d8c28cb4bce944e9d64efb4388ea9916330f53b2c83610b74611867565b63ffffffff85166000908152600491820160205260409081902090516001600160e01b031960e086901b168152610bac939201614cf3565b60006040518083038186803b158015610bc457600080fd5b505af4158015610bd8573d6000803e3d6000fd5b505050507f8ee0b45ff34ecba58a4014e0e0a9ebb97960cbfcae1e413072685b6b56b724fe8183604051610ac4929190614e87565b600061046b60ff8316633b9aca00614fe8565b600080610c2c83611674565b915050610c9d866001600160a01b031663524a562f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c949190614c56565b84838888611c88565b9695505050505050565b610caf613c30565b610cb7613ca2565b6000610cc1611867565b6003016000610cda6001600160a01b0389168888611da7565b8152602001908152602001600020549050610d14610cf6611867565b63ffffffff8616600090815260069190910160205260409020611dda565b9250610d7c610d21611867565b63ffffffff8087166000908152600692909201602052604082206004019190610d57906001600160a01b038b16908a9061188b16565b8152602001908152602001600020600083815260200190815260200160002054611ffb565b915050935093915050565b6000610d9287612049565b610e09610d9d611867565b886001600160a01b031663524a562f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ddb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dff9190614c56565b88888888886120de565b979650505050505050565b610e1c613cc9565b610e24611867565b63ffffffff80841660009081526004929092016020908152604092839020835160a081018552815490931683526001810180548551818502810185019096528086529394919385840193830182828015610e9d57602002820191906000526020600020905b815481526020019060010190808311610e89575b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015610ef557602002820191906000526020600020905b815481526020019060010190808311610ee1575b505050505081526020016003820154815260200160048201805480602002602001604051908101604052809291908181526020018280548015610f5757602002820191906000526020600020905b815481526020019060010190808311610f43575b5050505050815250509050919050565b600080610f95610f75611867565b63ffffffff85166000908152602091909152604090205490600882901c90565b91505061074e8160ff166024811115610fb057610fb0614412565b612211565b610fbd613cf6565b610fc689612049565b604080516101a0810182526000918101829052606081018290526080810182905260c081018290526101008101829052610140810182905261016081019190915263ffffffff871660e08201526001600160a01b03898116825286166020820152610120810185905260ff841660a0820152610180810183905261104987611674565b60ff90811660808401521660608201526040805163016dff5d60e01b815290516001600160a01b038c169163016dff5d9160048083019260209291908290030181865afa15801561109e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110c29190614c56565b6001600160a01b031663bcd418b087876040518363ffffffff1660e01b81526004016110ef929190614fff565b60a060405180830381865afa15801561110c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111309190615028565b6020015160ff1660c08201526001600160401b038816610100820152611154611867565b600301600061116488888b612613565b815260208101919091526040908101600020546101608301526001600160a01b038b1690820152611193613cf6565b60006111b1836080015160ff166024811115610fb057610fb0614412565b60048111156111c2576111c2614412565b4361014085015290506201b2531946016112405760646001600160a01b031663a3b1b31d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611215573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061123991906150cb565b6101408401525b60001960ff8216016112f3577322e2625f9d8c28cb4bce944e9d64efb4388ea99163bd9fe9db8461126f611867565b63ffffffff8d166000908152600491820160205260409081902090516001600160e01b031960e086901b1681526112a79392016151d4565b600060405180830381865af41580156112c4573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526112ec919081019061541a565b9150611417565b60011960ff8216016113be576113098888612629565b73df837f0327bbf85b066c400f17b2b2727f94cb2f63307800a28461132c611867565b60060160008d63ffffffff1663ffffffff1681526020019081526020016000206040518363ffffffff1660e01b81526004016113699291906151d4565b600060405180830381865af4158015611386573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526113ae919081019061544e565b60ff166101408501529150611417565b60021960ff8216016113f9576112ec836113d6611867565b63ffffffff8c166000908152600591909101602052604090205461ffff166126a6565b604051634d07af3360e01b815260ff82166004820152602401610663565b60e083015163ffffffff166101208301526001600160a01b03881660408301526101608083018890528301516101808301527f67af56937d4d46082b2327381733f95fd39ed3df103ec0f3761eb315b463f5648a8a60ff8416600481111561148157611481614412565b8b8b8b8961016001518c8a8c61014001516040516114a89a9998979695949392919061549f565b60405180910390a1509a9950505050505050505050565b6114c8846118fc565b60006114d5838386611c07565b9050836114e0611867565b63ffffffff831660008181526005929092016020908152604092839020805461ffff191661ffff9586161790558251918252928716928101929092527f9df2729723fcb63a4f46ffc16905a683f7eb0e8dd3ec260515e66b4d4cd8c0ad910160405180910390a15050505050565b611557826118fc565b61156b816101800151826101a00151611ace565b6000611584826020015183604001518460000151611c07565b905061158e611867565b63ffffffff8216600090815260069190910160205260408120906115b28282613d6c565b600182016000905560028201600090556003820160006115d29190613d6c565b50506115fe826115e0611867565b63ffffffff8416600090815260069190910160205260409020612988565b7fe9039f79f66d71725ba719a022b5729bbfce163887322d74e690772fe247fa4b8183604051610ac4929190615530565b60008061163d610f75611867565b91506002905061165b8260ff166024811115610fb057610fb0614412565b600481111561166c5761166c614412565b149392505050565b600080610899610f75611867565b600061168d85612049565b611698848484612b2c565b905060006116a583611674565b9150506000866001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156116e8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061170c9190614c56565b6001600160a01b031663bcd418b087876040518363ffffffff1660e01b8152600401611739929190614fff565b60a060405180830381865afa158015611756573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061177a9190615028565b905060006117968360ff166024811115610fb057610fb0614412565b60048111156117a7576117a7614412565b905060001960ff8216011561182a5760011960ff8216016117ff576117fa6117cd611867565b60060160008763ffffffff1663ffffffff1681526020019081526020016000208888878660200151612b81565b61182a565b60021960ff8216011561182a5760405163cafe050760e01b815260ff82166004820152602401610663565b505050949350505050565b600061183f611867565b600301600061184f868686612613565b81526020019081526020016000205490509392505050565b7ffa9e067a92ca4a9057b7b4465a8f29d633e1758238bd3a4a8ec5d0f904f6b90090565b60006001600160401b038211156118b857604051633995b34160e01b815260048101839052602401610663565b50600160a01b600160e01b0360a09190911b166001600160a01b039091161790565b600060ff8316600883901b61ff001617610468565b6060600061074e83612b91565b604051631430d62960e21b81526001600160a01b038216906350c358a490611928903390600401614a84565b602060405180830381865afa158015611945573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611969919061554f565b610499573360405163451cea1760e11b81526004016106639190614a84565b60006104688383612bed565b61199c612ce7565b6119b957604051631afcd79f60e31b815260040160405180910390fd5b565b6001600160a01b038116611a035760405162461bcd60e51b815260206004820152600f60248201526e2d32b9379031b7b73a3937b63632b960891b6044820152606401610663565b611a32816106b460017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c3618614c25565b611a61426106b460017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b614c25565b611a90436106b460017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f1614c25565b7f1a2dd071001ebf6e03174e3df5b305795a4ad5d41d8fdb9ba41dbbe236713426814243604051611ac39392919061556a565b60405180910390a150565b815181518114611af15760405163586cb9e160e01b815260040160405180910390fd5b60005b81811015611c015760006001600160a01b0316848281518110611b1957611b1961558b565b60200260200101516001600160a01b031603611b485760405163d92e233d60e01b815260040160405180910390fd5b828181518110611b5a57611b5a61558b565b602002602001015163ffffffff16600003611b885760405163eb79f4eb60e01b815260040160405180910390fd5b633b9aca0063ffffffff16838281518110611ba557611ba561558b565b602002602001015163ffffffff161115611bf957828181518110611bcb57611bcb61558b565b60200260200101516040516304470bfd60e21b8152600401610663919063ffffffff91909116815260200190565b600101611af4565b50505050565b600080611c12611867565b9050611c3085856024811115611c2a57611c2a614412565b85612d01565b91506000611c4a86866024811115610a0757610a07614412565b63ffffffff808516600081815260208681526040808320869055858352600188019091529020929350611c7e9291612df616565b5050509392505050565b600080611ca38560ff166024811115610fb057610fb0614412565b90506001816004811115611cb957611cb9614412565b03611cc8576001915050610796565b6002816004811115611cdc57611cdc614412565b03611ceb576001915050610796565b6003816004811115611cff57611cff614412565b03611d8957604051630152f74f60e41b815263ffffffff871660048201526001600160a01b0385811660248301526044820185905288169063152f74f090606401602060405180830381865afa158015611d5d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d81919061554f565b915050610796565b604051630dad26cb60e31b815260ff86166004820152602401610663565b6001600160a01b039290921660a09190911b600160a01b600160e01b03161760e09190911b6001600160e01b0319161790565b611de2613c30565b611dea613c30565b611e4583600001805480602002602001604051908101604052809291908181526020018280548015611e3b57602002820191906000526020600020905b815481526020019060010190808311611e27575b5050505050612e02565b606083015260808201526001830154600881901c601082901c603083901c60ff90811661010086015263ffffffff90911660e085015290811660c08401521660a08201526002830154611e9f60a082901c9060e083901c90565b60ff166101608401526001600160401b039081166101408401526001600160a01b039091166101208301526003840154908190811115611ee157611ee1613f2f565b604051908082528060200260200182016040528015611f0a578160200160208202803683370190505b50610180830152806001600160401b03811115611f2957611f29613f2f565b604051908082528060200260200182016040528015611f52578160200160208202803683370190505b506101a083015260005b81811015611ff257611f90856003018281548110611f7c57611f7c61558b565b90600052602060002001549060a082901c90565b8461018001518381518110611fa757611fa761558b565b60200260200101856101a001518481518110611fc557611fc561558b565b63ffffffff909316602093840291909101909201919091526001600160a01b039091169052600101611f5c565b50909392505050565b612003613ca2565b506040805160808101825260ff8381166001148252604884901c166020820152602883901c60030b9181019190915263ffffffff60089290921c91909116606082015290565b336001600160a01b0316816001600160a01b031663683fedf76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612091573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120b59190614c56565b6001600160a01b031614610499573360405163cf219bef60e01b81526004016106639190614a84565b6000806120ee87876130f361319c565b9050600089600101600061210288856118da565b81526020019081526020016000209050600061211d826133a5565b9050806000036121405760405163296a76c760e21b815260040160405180910390fd5b6000816001146121625761215d612158600184614c25565b6130f3565b612165565b60005b9050600060028d018160ff60e81b60e88c901b1660ff60e01b60e089901b16600160a01b600160e01b0360a08c901b166001600160a01b038d16171717815260200190815260200160002090506121c48c8486848660008e8e8d6133af565b95508563ffffffff166000036121ed57604051639cb04d5d60e01b815260040160405180910390fd5b6122008163ffffffff80891690612df616565b505050505050979650505050505050565b6000600482602481111561222757612227614412565b14806122445750600582602481111561224257612242614412565b145b806122605750600882602481111561225e5761225e614412565b145b1561226d57506001919050565b600182602481111561228157612281614412565b148061229e5750600282602481111561229c5761229c614412565b145b806122ba575060038260248111156122b8576122b8614412565b145b806122d65750600a8260248111156122d4576122d4614412565b145b806122f2575060208260248111156122f0576122f0614412565b145b8061230e5750602182602481111561230c5761230c614412565b145b8061232a5750602282602481111561232857612328614412565b145b806123465750602382602481111561234457612344614412565b145b1561235357506002919050565b600682602481111561236757612367614412565b14806123845750600782602481111561238257612382614412565b145b806123a05750600b82602481111561239e5761239e614412565b145b806123bc5750600c8260248111156123ba576123ba614412565b145b806123d85750600d8260248111156123d6576123d6614412565b145b806123f45750600e8260248111156123f2576123f2614412565b145b806124105750600f82602481111561240e5761240e614412565b145b8061242c5750601082602481111561242a5761242a614412565b145b806124485750601182602481111561244657612446614412565b145b806124645750601282602481111561246257612462614412565b145b806124805750601382602481111561247e5761247e614412565b145b8061249c5750601482602481111561249a5761249a614412565b145b806124b8575060158260248111156124b6576124b6614412565b145b806124d4575060168260248111156124d2576124d2614412565b145b806124f0575060178260248111156124ee576124ee614412565b145b8061250c5750601882602481111561250a5761250a614412565b145b806125285750601982602481111561252657612526614412565b145b806125445750601a82602481111561254257612542614412565b145b806125605750601b82602481111561255e5761255e614412565b145b8061257c5750601c82602481111561257a5761257a614412565b145b806125985750601d82602481111561259657612596614412565b145b806125b45750601e8260248111156125b2576125b2614412565b145b806125d05750601f8260248111156125ce576125ce614412565b145b156125dd57506003919050565b8160248111156125ef576125ef614412565b6040516302f13cad60e41b815260ff9091166004820152602401610663565b919050565b600061074b6001600160a01b0385168484611da7565b6000612633611867565b9050600061264a6001600160a01b0385168461188b565b60088301546000828152600785016020526040902054919250429161266f9190614c38565b111561268e5760405163051211b760e11b815260040160405180910390fd5b60009081526007909101602052604090204290555050565b6126ae613cf6565b8161ffff166000036126d357604051635c5e6bb360e11b815260040160405180910390fd5b82604001516001600160a01b031663524a562f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612715573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127399190614c56565b6001600160a01b0316631c1b0daa84600001518561010001518660e001518760a0015188602001518961012001518a606001518b61016001518c61018001516040518a63ffffffff1660e01b815260040161279c999897969594939291906155a1565b6000604051808303816000875af11580156127bb573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526127e3919081019061541a565b90508060200151806127f3575080515b1561046b57600083604001516001600160a01b0316628e96916040518163ffffffff1660e01b8152600401602060405180830381865afa15801561283b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061285f9190614c56565b9050600061286c8461347e565b6020860151610120870151604051631c2aafe760e01b81529293506000926001600160a01b03861692631c2aafe7926128a992879060040161556a565b602060405180830381865afa1580156128c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128ea91906150cb565b9050826001600160a01b031663184f61438760200151886101200151858560016129149190614c38565b6040516001600160e01b031960e087901b1681526001600160a01b039094166004850152602484019290925260448301526064820152608401600060405180830381600087803b15801561296757600080fd5b505af115801561297b573d6000803e3d6000fd5b5050505050505092915050565b6060820151608083015161299b916134b5565b80516129ae918391602090910190613d8a565b5060a082015160c083015160e084015161010085015160ff90931660089290921b61ff00169190911760109190911b65ffffffff0000161760309190911b60ff60301b161760018201556101208201516101408301516101608401516001600160a01b0390921660a09190911b600160a01b600160e01b03161760e09190911b60ff60e01b16176002820155610180820151516000816001600160401b03811115612a5b57612a5b613f2f565b604051908082528060200260200182016040528015612a84578160200160208202803683370190505b50905060005b82811015612b0f57612aea856101a001518281518110612aac57612aac61558b565b60200260200101518661018001518381518110612acb57612acb61558b565b60200260200101516001600160a01b031661371390919063ffffffff16565b828281518110612afc57612afc61558b565b6020908102919091010152600101612a8a565b508051612b259060038501906020840190613d8a565b5050505050565b600080612b37611867565b90506000612b46868686612613565b6000818152600384016020526040902054909150612b65906001614c38565b6000918252600390920160205260409020819055949350505050565b612b25858585856137318661373c565b606081600001805480602002602001604051908101604052809291908181526020018280548015612be157602002820191906000526020600020905b815481526020019060010190808311612bcd575b50505050509050919050565b60008181526001830160205260408120548015612cd6576000612c11600183614c25565b8554909150600090612c2590600190614c25565b9050808214612c8a576000866000018281548110612c4557612c4561558b565b9060005260206000200154905080876000018481548110612c6857612c6861558b565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080612c9b57612c9b61561a565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505061046b565b600091505061046b565b5092915050565b6000612cf1610ad1565b54600160401b900460ff16919050565b600060ff84161580612d14575060ff8316155b80612d21575061ffff8216155b15612d3f5760405163273e150360e21b815260040160405180910390fd5b60648460ff1610612d68576040516340eff0e560e01b815260ff85166004820152602401610663565b60648360ff1610612d915760405163017c4c5960e01b815260ff84166004820152602401610663565b6127108261ffff161115612dbe57604051637dc12d1d60e01b815261ffff83166004820152602401610663565b61ffff8216612dd260ff8516612710615630565b612de260ff8716620f4240615630565b612dec9190615658565b61074b9190615658565b60006104688383613825565b805160609081906000612e16826008614fe8565b90506000816001600160401b03811115612e3257612e32613f2f565b604051908082528060200260200182016040528015612e5b578160200160208202803683370190505b5090506000826001600160401b03811115612e7857612e78613f2f565b604051908082528060200260200182016040528015612ea1578160200160208202803683370190505b5090506000805b85811015612fce5760005b6008811015612fc557600081612eca846008614fe8565b612ed49190614c38565b9050612ee1826020614fe8565b8b8481518110612ef357612ef361558b565b602002602001015160001c901c60020b868281518110612f1557612f1561558b565b60039290920b602092830291909101820152612f32908390614fe8565b612f3d906018614c38565b8b8481518110612f4f57612f4f61558b565b602002602001015160001c901c858281518110612f6e57612f6e61558b565b602002602001019060ff16908160ff1681525050858181518110612f9457612f9461558b565b602002602001015160030b600003612fac5750612fc5565b83612fb681615675565b94505050806001019050612eb3565b50600101612ea8565b50806001600160401b03811115612fe757612fe7613f2f565b604051908082528060200260200182016040528015613010578160200160208202803683370190505b509650806001600160401b0381111561302b5761302b613f2f565b604051908082528060200260200182016040528015613054578160200160208202803683370190505b50955060005b818110156130e8578381815181106130745761307461558b565b602002602001015188828151811061308e5761308e61558b565b602002602001019060030b908160030b815250508281815181106130b4576130b461558b565b60200260200101518782815181106130ce576130ce61558b565b60ff9092166020928302919091019091015260010161305a565b505050505050915091565b60008160000361310557506000919050565b600061310f613874565b905061311c836001614c38565b4340414443423a5a8860405160200161317998979695949392919097885260609690961b6001600160601b0319166020880152603487019490945260548601929092526074850152609484015260b483015260d482015260f40190565b6040516020818303038152906040528051906020012060001c61074e91906156a4565b82516000908015806131af575083518114155b156131cd5760405163360ee06360e21b815260040160405180910390fd5b806001036131f757846000815181106131e8576131e861558b565b60200260200101519150613376565b600061320a633b9aca0063ffffffff8616565b9050600061321d633b9aca006001615658565b63ffffffff16905060005b83811015613372578781815181106132425761324261558b565b602002602001015160ff166000031561336a57633b9aca0063ffffffff168782815181106132725761327261558b565b602002602001015163ffffffff1611156132cf578681815181106132985761329861558b565b6020908102919091010151604051631b4fdad960e11b815263ffffffff9091166004820152633b9aca006024820152604401610663565b828782815181106132e2576132e261558b565b6020026020010151633b9aca006132f991906156b8565b63ffffffff161161336a57818782815181106133175761331761558b565b602002602001015163ffffffff16101561336a5786818151811061333d5761333d61558b565b602002602001015163ffffffff16915087818151811061335f5761335f61558b565b602002602001015194505b600101613228565b5050505b8160ff1660000361339d5760405163b3cea44d60e01b815260006004820152602401610663565b509392505050565b600061046b825490565b6000886133bb886133a5565b106133cd57600194506133cd876138ac565b6000805b8a811015613440578a88106133e557600097505b60006133f18b8a6138fa565b90506134008d82878a8a611c88565b80156134235750878061342357506134218a63ffffffff8084169061390616565b155b1561343357925060019150613440565b50600197880197016133d1565b508015801561344d575085155b156134705761345b886138ac565b61346d8b8b8b8b8b60018b8b8b6133af565b91505b509998505050505050505050565b600061348d8261ffff1661391e565b60405160200161349d91906156d5565b60405160208183030381529060405261046b90615703565b606081518351146134d95760405163586cb9e160e01b815260040160405180910390fd5b6000600884516134e9919061572a565b6134f4906001614c38565b90506000816001600160401b0381111561351057613510613f2f565b604051908082528060200260200182016040528015613539578160200160208202803683370190505b50905060005b8281101561370a5760005b600881101561370157600081613561846008614fe8565b61356b9190614c38565b90508751811061357b5750613701565b627fffff60020b8882815181106135945761359461558b565b602002602001015160030b13806135cd5750627fffff1960020b8882815181106135c0576135c061558b565b602002602001015160030b125b1561360b578781815181106135e4576135e461558b565b602002602001015160030b604051632eb1d06960e01b815260040161066391815260200190565b87818151811061361d5761361d61558b565b602002602001015160030b60000361364857604051637c946ed760e01b815260040160405180910390fd5b613653826020614fe8565b8882815181106136655761366561558b565b602002602001015162ffffff1660001b901b8484815181106136895761368961558b565b6020026020010181815117915081815250508160206136a89190614fe8565b6136b3906018614c38565b8782815181106136c5576136c561558b565b602002602001015160ff1660001b901b8484815181106136e7576136e761558b565b60209081029190910101805190911790525060010161354a565b5060010161353f565b50949350505050565b63ffffffff60a01b60a09190911b166001600160a01b039091161790565b600061046b826130f3565b60408051608081018252600181526000602082018190529181018290526060810161376e633b9aca0063ffffffff8716565b63ffffffff1690529050600061378388611dda565b905060006137ac8260600151836080015185606001516137a288610c0d565b8660e00151613a26565b509050806009815181106137c2576137c261558b565b602090810291909101015160030b60408401526137de83613aec565b60048a0160006137f76001600160a01b038c168b61188b565b8152602001908152602001600020600088815260200190815260200160002081905550505050505050505050565b600081815260018301602052604081205461386c5750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561046b565b50600061046b565b60006350877ed646148061388b5750630235ddd046145b156138a9576040518060208160008060185afa6138a457fe5b505190505b90565b60006138b7826118ef565b905060005b8151811015610847576138f18282815181106138da576138da61558b565b60200260200101518461198890919063ffffffff16565b506001016138bc565b60006104688383613b0a565b60008181526001830160205260408120541515610468565b6060816000036139455750506040805180820190915260018152600360fc1b602082015290565b8160005b811561396f578061395981615675565b91506139689050600a8361572a565b9150613949565b6000816001600160401b0381111561398957613989613f2f565b6040519080825280601f01601f1916602001820160405280156139b3576020820181803683370190505b5090505b8415613a1e576139c8600183614c25565b91506139d5600a866156a4565b6139e0906030614c38565b60f81b8183815181106139f5576139f561558b565b60200101906001600160f81b031916908160001a905350613a17600a8661572a565b94506139b7565b949350505050565b60606000613a3560048661573e565b60408051602b8082526105808201909252919650602082016105608036833701905050915060005b8751811015613ad357613a8a878281518110613a7b57613a7b61558b565b60200260200101518787613b34565b83898381518110613a9d57613a9d61558b565b602002602001015160ff1681518110613ab857613ab861558b565b60039290920b60209283029190910190910152600101613a5d565b50613ae083866000613b34565b90509550959350505050565b600061046b8260000151836060015184604001518560200151613bdc565b6000826000018281548110613b2157613b2161558b565b9060005260206000200154905092915050565b60008360030b600003613b495750600061074e565b6000633b9aca00613b5e84600388900b615761565b613b689190615791565b633b9aca00613b8163ffffffff8716600389900b615761565b613b8b9190615791565b8660030b613b9991906157bf565b613ba391906157bf565b9050637fffffff811380613bbb5750637fffffff1981125b1561074b576040516344dc334160e01b815260048101829052602401610663565b600084613bea576000613bed565b60015b60ff1660001b905060088463ffffffff1660001b901b8117905060288363ffffffff1660001b901b8117905060488260ff1660001b901b81179050949350505050565b604080516101c081018252600080825260208201819052909182019081526060602082018190526040820181905260008183018190526080830181905260a0830181905260c0830181905260e08301819052610100830181905261012083015261014082018190526101609091015290565b60408051608081018252600080825260208201819052918101829052606081019190915290565b6040805160a081018252600080825260606020830181905292820183905282820152608081019190915290565b604080516101c08101825260008082526020820181905291810182905260608082018190526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201839052610140820183905261016082018390526101808201929092526101a081019190915290565b50805460008255906000526020600020908101906104999190613dd5565b828054828255906000526020600020908101928215613dc5579160200282015b82811115613dc5578251825591602001919060010190613daa565b50613dd1929150613dd5565b5090565b5b80821115613dd15760008155600101613dd6565b6001600160a01b038116811461049957600080fd5b803561260e81613dea565b60008060408385031215613e1d57600080fd5b8235613e2881613dea565b946020939093013593505050565b63ffffffff8116811461049957600080fd5b803561260e81613e36565b600060208284031215613e6557600080fd5b813561074e81613e36565b60ff8116811461049957600080fd5b803561260e81613e70565b80356025811061260e57600080fd5b60008060408385031215613eac57600080fd5b8235613eb781613e70565b9150613ec560208401613e8a565b90509250929050565b6020808252825182820181905260009190848201906040850190845b81811015613f0657835183529284019291840191600101613eea565b50909695505050505050565b600060208284031215613f2457600080fd5b813561074e81613dea565b634e487b7160e01b600052604160045260246000fd5b6040516101c081016001600160401b0381118282101715613f6857613f68613f2f565b60405290565b604051601f8201601f191681016001600160401b0381118282101715613f9657613f96613f2f565b604052919050565b803561ffff8116811461260e57600080fd5b60006001600160401b03821115613fc957613fc9613f2f565b5060051b60200190565b600082601f830112613fe457600080fd5b81356020613ff9613ff483613fb0565b613f6e565b8083825260208201915060208460051b87010193508684111561401b57600080fd5b602086015b8481101561404057803561403381613e70565b8352918301918301614020565b509695505050505050565b8060030b811461049957600080fd5b803561260e8161404b565b600082601f83011261407657600080fd5b81356020614086613ff483613fb0565b8083825260208201915060208460051b8701019350868411156140a857600080fd5b602086015b848110156140405780356140c08161404b565b83529183019183016140ad565b6000604082840312156140df57600080fd5b604051604081016001600160401b03828210818311171561410257614102613f2f565b81604052829350843591508082111561411a57600080fd5b61412686838701613fd3565b8352602085013591508082111561413c57600080fd5b5061414985828601614065565b6020830152505092915050565b600082601f83011261416757600080fd5b81356020614177613ff483613fb0565b8083825260208201915060208460051b87010193508684111561419957600080fd5b602086015b848110156140405780356141b181613dea565b835291830191830161419e565b600082601f8301126141cf57600080fd5b813560206141df613ff483613fb0565b8083825260208201915060208460051b87010193508684111561420157600080fd5b602086015b8481101561404057803561421981613e36565b8352918301918301614206565b60006020828403121561423857600080fd5b81356001600160401b038082111561424f57600080fd5b908301906101c0828603121561426457600080fd5b61426c613f45565b61427583613e7f565b815261428360208401613f9e565b602082015261429460408401613e8a565b60408201526142a560608401613e48565b60608201526080830135828111156142bc57600080fd5b6142c8878286016140cd565b60808301525060a0830135828111156142e057600080fd5b6142ec878286016140cd565b60a0830152506142fe60c08401613e48565b60c082015261430f60e0840161405a565b60e082015261010061432281850161405a565b9082015261012061433484820161405a565b9082015261014061434684820161405a565b9082015261016061435884820161405a565b90820152610180838101358381111561437057600080fd5b61437c88828701614156565b8284015250506101a0808401358381111561439657600080fd5b6143a2888287016141be565b918301919091525095945050505050565b6000602082840312156143c557600080fd5b813561074e81613e70565b6000806000606084860312156143e557600080fd5b83356143f081613dea565b925060208401359150604084013561440781613e36565b809150509250925092565b634e487b7160e01b600052602160045260246000fd5b6025811061443857614438614412565b9052565b60008151808452602080850194506020840160005b8381101561447057815160ff1687529582019590820190600101614451565b509495945050505050565b60008151808452602080850194506020840160005b8381101561447057815160030b87529582019590820190600101614490565b6001600160a01b03169052565b60008151808452602080850194506020840160005b838110156144705781516001600160a01b0316875295820195908201906001016144d1565b60008151808452602080850194506020840160005b8381101561447057815163ffffffff168752958201959082019060010161450b565b805161ffff16825260006101c0602083015161454e602086018260ff169052565b5060408301516145616040860182614428565b5060608301518160608601526145798286018261443c565b91505060808301518482036080860152614593828261447b565b91505060a08301516145aa60a086018260ff169052565b5060c08301516145bf60c086018260ff169052565b5060e08301516145d760e086018263ffffffff169052565b506101008381015160ff1690850152610120808401516145f9828701826144af565b5050610140838101516001600160401b0316908501526101608084015160ff1690850152610180808401518583038287015261463583826144bc565b925050506101a08084015185830382870152610c9d83826144f6565b60a08152600061466460a083018561452d565b905082511515602083015260ff6020840151166040830152604083015160030b606083015263ffffffff60608401511660808301529392505050565b600080600080600060a086880312156146b857600080fd5b85356001600160401b03808211156146cf57600080fd5b6146db89838a01613fd3565b965060208801359150808211156146f157600080fd5b506146fe888289016141be565b945050604086013561470f81613e70565b9250606086013561471f81613dea565b949793965091946080013592915050565b60008151808452602080850194506020840160005b8381101561447057815187529582019590820190600101614745565b6020815263ffffffff82511660208201526000602083015160a0604084015261478d60c0840182614730565b90506040840151601f19808584030160608601526147ab8383614730565b92506060860151608086015260808601519150808584030160a0860152506107968282614730565b6005811061443857614438614412565b6020810161046b82846147d3565b80356001600160401b038116811461260e57600080fd5b600080600080600080600060e0888a03121561482357600080fd5b873561482e81613dea565b9650602061483d8982016147f1565b9650604089013561484d81613e36565b9550606089013561485d81613dea565b94506080890135935060a089013561487481613e70565b925060c08901356001600160401b038082111561489057600080fd5b818b0191508b601f8301126148a457600080fd5b8135818111156148b6576148b6613f2f565b6148c8601f8201601f19168501613f6e565b91508082528c848285010111156148de57600080fd5b808484018584013760008482840101525080935050505092959891949750929550565b80511515825260006101c0602083015161491f602086018215159052565b50604083015161493260408601826144af565b50606083015181606086015261494a828601826144bc565b9150506080830151614961608086018260030b9052565b5060a083015161497660a086018260030b9052565b5060c083015161498b60c086018260030b9052565b5060e08301516149a060e086018260030b9052565b50610100808401516149b68287018260030b9052565b50506101208381015163ffffffff81168683015250506101408381015163ffffffff8116868301525050610160838101519085015261018080840151908501526101a08084015185830382870152610c9d83826144f6565b6020815260006104686020830184614901565b60005b83811015614a3c578181015183820152602001614a24565b50506000910152565b60008151808452614a5d816020860160208601614a21565b601f01601f19169290920160200192915050565b6020815260006104686020830184614a45565b6001600160a01b0391909116815260200190565b600080600060608486031215614aad57600080fd5b614ab684613f9e565b92506020840135614ac681613e70565b9150614ad460408501613e8a565b90509250925092565b600060208284031215614aef57600080fd5b81356001600160401b0380821115614b0657600080fd5b908301906101c08286031215614b1b57600080fd5b614b23613f45565b614b2c83613f9e565b8152614b3a60208401613e7f565b6020820152614b4b60408401613e8a565b6040820152606083013582811115614b6257600080fd5b614b6e87828601613fd3565b606083015250608083013582811115614b8657600080fd5b614b9287828601614065565b608083015250614ba460a08401613e7f565b60a0820152614bb560c08401613e7f565b60c0820152614bc660e08401613e48565b60e0820152610100614bd9818501613e7f565b90820152610120614beb848201613dff565b90820152610140614bfd8482016147f1565b90820152610160614358848201613e7f565b634e487b7160e01b600052601160045260246000fd5b8181038181111561046b5761046b614c0f565b8082018082111561046b5761046b614c0f565b805161260e81613dea565b600060208284031215614c6857600080fd5b815161074e81613dea565b805160408084528151908401819052600091602091908201906060860190845b81811015614cb257835160ff1683529284019291840191600101614c93565b50508483015186820387850152805180835290840192506000918401905b8083101561404057835160030b8252928401926001929092019190840190614cd0565b60408152614d0760408201845160ff169052565b60006020840151614d1e606084018261ffff169052565b506040840151614d316080840182614428565b50606084015163ffffffff1660a083015260808401516101c060c08401819052614d5f610200850183614c73565b915060a0860151603f19808685030160e0870152614d7d8483614c73565b935060c08801519150610100614d9a8188018463ffffffff169052565b60e08901519250610120614db28189018560030b9052565b90890151925061014090614dca8883018560030b9052565b8901519250610160614de08882018560030b9052565b90890151925061018090614df88883018560030b9052565b89015192506101a0614e0e8882018560030b9052565b818a01519350828887030185890152614e2786856144bc565b9550808a01519450505080868503016101e08701525050614e4882826144f6565b925050508260208301529392505050565b6000815160408452614e6e604085018261443c565b905060208301518482036020860152610796828261447b565b63ffffffff8316815260406020820152614ea760408201835160ff169052565b60006020830151614ebe606084018261ffff169052565b506040830151614ed16080840182614428565b50606083015163ffffffff1660a083015260808301516101c060c08401819052614eff610200850183614e59565b915060a0850151603f19808685030160e0870152614f1d8483614e59565b935060c08701519150610100614f3a8188018463ffffffff169052565b60e08801519250610120614f528189018560030b9052565b90880151925061014090614f6a8883018560030b9052565b8801519250610160614f808882018560030b9052565b90880151925061018090614f988883018560030b9052565b88015192506101a0614fae8882018560030b9052565b818901519350828887030185890152614fc786856144bc565b9550808901519450505080868503016101e08701525050610c9d82826144f6565b808202811582820484141761046b5761046b614c0f565b6001600160a01b03929092168252602082015260400190565b8051801515811461260e57600080fd5b600060a0828403121561503a57600080fd5b60405160a081018181106001600160401b038211171561505c5761505c613f2f565b604052825161506a81613e70565b8152602083015161507a81613e70565b602082015261508b60408401615018565b6040820152606083015168ffffffffffffffffff811681146150ac57600080fd5b606082015260808301516150bf81613dea565b60808201529392505050565b6000602082840312156150dd57600080fd5b5051919050565b60006101a06150f48484516144af565b602083015161510660208601826144af565b50604083015161511960408601826144af565b50606083015161512e606086018260ff169052565b506080830151615143608086018260ff169052565b5060a083015161515860a086018260ff169052565b5060c083015161516d60c086018260ff169052565b5060e083015161518560e086018263ffffffff169052565b50610100838101516001600160401b03169085015261012080840151908501526101408084015190850152610160808401519085015261018080840151818601839052610c9d83870182614a45565b6040815260006151e760408301856150e4565b90508260208301529392505050565b600082601f83011261520757600080fd5b81516020615217613ff483613fb0565b8083825260208201915060208460051b87010193508684111561523957600080fd5b602086015b8481101561404057805161525181613dea565b835291830191830161523e565b805161260e8161404b565b805161260e81613e36565b600082601f83011261528557600080fd5b81516020615295613ff483613fb0565b8083825260208201915060208460051b8701019350868411156152b757600080fd5b602086015b848110156140405780516152cf81613e36565b83529183019183016152bc565b60006101c082840312156152ef57600080fd5b6152f7613f45565b905061530282615018565b815261531060208301615018565b602082015261532160408301614c4b565b604082015260608201516001600160401b038082111561534057600080fd5b61534c858386016151f6565b606084015261535d6080850161525e565b608084015261536e60a0850161525e565b60a084015261537f60c0850161525e565b60c084015261539060e0850161525e565b60e084015261010091506153a582850161525e565b8284015261012091506153b9828501615269565b8284015261014091506153cd828501615269565b8284015261016091508184015182840152610180915081840151828401526101a09150818401518181111561540157600080fd5b61540d86828701615274565b8385015250505092915050565b60006020828403121561542c57600080fd5b81516001600160401b0381111561544257600080fd5b613a1e848285016152dc565b6000806040838503121561546157600080fd5b82516001600160401b0381111561547757600080fd5b615483858286016152dc565b925050602083015161549481613e70565b809150509250929050565b60006101406001600160401b038d16835263ffffffff8c1660208401526154c9604084018c6147d3565b6001600160a01b038a1660608401526080830189905260ff881660a084015260c0830187905260e0830181905261550281840187614a45565b90508281036101008401526155178186614901565b915050826101208301529b9a5050505050505050505050565b63ffffffff8316815260406020820152600061074b604083018461452d565b60006020828403121561556157600080fd5b61046882615018565b6001600160a01b039390931683526020830191909152604082015260600190565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b038a811682526001600160401b038a16602083015263ffffffff8916604083015260ff8881166060840152908716608083015260a08201869052841660c082015260e08101839052610120610100820181905260009061560a83820185614a45565b9c9b505050505050505050505050565b634e487b7160e01b600052603160045260246000fd5b63ffffffff81811683821602808216919082811461565057615650614c0f565b505092915050565b63ffffffff818116838216019080821115612ce057612ce0614c0f565b60006001820161568757615687614c0f565b5060010190565b634e487b7160e01b600052601260045260246000fd5b6000826156b3576156b361568e565b500690565b63ffffffff828116828216039080821115612ce057612ce0614c0f565b6553544f52595f60d01b8152600082516156f6816006850160208701614a21565b9190910160060192915050565b80516020808301519190811015615724576000198160200360031b1b821691505b50919050565b6000826157395761573961568e565b500490565b600063ffffffff808416806157555761575561568e565b92169190910492915050565b80820260008212600160ff1b8414161561577d5761577d614c0f565b818105831482151761046b5761046b614c0f565b6000826157a0576157a061568e565b600160ff1b8214600019841416156157ba576157ba614c0f565b500590565b808201828112600083128015821682158216171561565057615650614c0f56fe22573091f17911fb166032a3d9e0554aa73d31b7b7ddea4a4dd2995650af84bda2646970667358221220aca579955b2f59434338862fed1191ebdd799a24002827ba3706a52746c6e85b64736f6c63430008170033
Deployed Bytecode
0x608060405234801561001057600080fd5b506004361061017a5760003560e01c80637cc96380116100d45780637cc96380146102f05780638154916e146102f8578063909aa53914610318578063936725ec146103385780639d0bcca014610369578063a8ca89ff1461037e578063b414c55314610391578063b429afeb146103a4578063d447d4df146103b7578063d5e46dcd146103ca578063dee1f0e4146103f7578063f2c6c6fb1461040a578063f3deb3911461041d578063f77c479114610430578063ffa1ad741461043857600080fd5b806306efb7851461017f5780630e2ecde2146101a55780630e91f15e146101cb578063172ae49a146101eb57806319ab453c146102005780631c10968c14610213578063325a19f11461021b5780634593144c146102235780634675bfff1461022b5780634fac6ccd1461023e5780635f1d20a1146102515780636742e2d61461026457806369c3d472146102875780636f383c0f146102a857806375a8e7da146102d0575b600080fd5b61019261018d366004613e0a565b61045c565b6040519081526020015b60405180910390f35b6101b86101b3366004613e53565b610471565b60405161ffff909116815260200161019c565b6101de6101d9366004613e99565b61047c565b60405161019c9190613ece565b6101fe6101f9366004613e53565b610488565b005b6101fe61020e366004613f12565b61049c565b610192610596565b6101926105a5565b6101926105d9565b6101fe610239366004614226565b610609565b6101fe61024c366004613f12565b61061a565b61019261025f3660046143b3565b61072b565b6102776102723660046143d0565b610736565b604051901515815260200161019c565b61029a6102953660046143d0565b610755565b60405161019c929190614651565b6102bb6102b63660046146a0565b61077c565b60405163ffffffff909116815260200161019c565b6102e36102de366004613e53565b61079f565b60405161019c9190614761565b6101926107b0565b61030b610306366004613e53565b6107ce565b60405161019c91906147e3565b61032b610326366004614808565b6107d9565b60405161019c9190614a0e565b61035c604051806040016040528060058152602001640312e302e360dc1b81525081565b60405161019c9190614a71565b610371610804565b60405161019c9190614a84565b6101fe61038c366004614a98565b610834565b6101fe61039f366004614add565b61084c565b6102776103b2366004613f12565b61085d565b6102776103c5366004613e53565b610882565b6103dd6103d8366004613e53565b61088d565b6040805160ff93841681529290911660208301520161019c565b610277610405366004613f12565b6108a2565b6101926104183660046143d0565b610927565b61019261042b3660046143d0565b61093c565b610371610949565b61035c60405180604001604052806005815260200164312e312e3560d81b81525081565b60006104688383610979565b90505b92915050565b600061046b826109b2565b606061046883836109df565b610499610493610949565b82610a1f565b50565b60006104a6610ad1565b805490915060ff600160401b82041615906001600160401b03166000811580156104cd5750825b90506000826001600160401b031660011480156104e95750303b155b9050811580156104f7575080155b156105155760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561053f57845460ff60401b1916600160401b1785555b61054886610af5565b831561058e57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b60006105a0610b06565b905090565b60006105a06105d560017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b614c25565b5490565b60006105a06105d560017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f1614c25565b610499610614610949565b82610b19565b33301461066c5760405162461bcd60e51b815260206004820152601b60248201527a24b731b932b0b9b2903932bb34b9b4b7b7103337b93134b23232b760291b60448201526064015b60405180910390fd5b600061068a6105d560016000805160206157e0833981519152614c25565b610695906001614c38565b90506106b7816106b460016000805160206157e0833981519152614c25565b55565b6106e6826106b460017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e4614c25565b604080518281526001600160a01b03841660208201527ff27e2ef832a4eb8ed8ec553b875eecd44764cda95b1c24170e281539e0a869c8910160405180910390a15050565b600061046b82610c0d565b600061074b610743610949565b858585610c20565b90505b9392505050565b61075d613c30565b610765613ca2565b610770858585610ca7565b91509150935093915050565b6000610793610789610949565b8787878787610d87565b90505b95945050505050565b6107a7613cc9565b61046b82610e14565b60006105a06105d560016000805160206157e0833981519152614c25565b600061046b82610f67565b6107e1613cf6565b6107f86107ec610949565b89898989898989610fb5565b98975050505050505050565b60006105a06105d560017fbfaaa2fb63266ff27c2da975f5894955056f50419af651a81f6c5060581857e4614c25565b61084761083f610949565b8484846114bf565b505050565b610499610857610949565b8261154e565b6000610867610949565b6001600160a01b0316826001600160a01b0316149050919050565b600061046b8261162f565b60008061089983611674565b91509150915091565b6000816001600160a01b03166108b6610949565b6001600160a01b0316635aa6e6756040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108f3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109179190614c56565b6001600160a01b03161492915050565b600061074b610934610949565b858585611682565b600061074b848484611835565b60006105a06105d560017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c3618614c25565b6000610983611867565b600701600061099b6001600160a01b0386168561188b565b815260200190815260200160002054905092915050565b60006109bc611867565b63ffffffff909216600090815260059290920160205250604090205461ffff1690565b60606104686109ec611867565b6001016000610a0c86866024811115610a0757610a07614412565b6118da565b81526020019081526020016000206118ef565b610a28826118fc565b6000610a32611867565b63ffffffff8316600090815260209190915260409020549050610a53611867565b63ffffffff831660008181526020929092526040822091909155610a9190610a79611867565b60008481526001919091016020526040902090611988565b5060405163ffffffff831681527f93afafd4d79cc89e4afba48bcd8cac7b8a35b45dfb55afe60cccae721124cd0f906020015b60405180910390a1505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b610afd611994565b610499816119bb565b6000610b10611867565b60080154905090565b610b22826118fc565b610b36816101800151826101a00151611ace565b6000610b4f826000015183604001518460200151611c07565b90507322e2625f9d8c28cb4bce944e9d64efb4388ea9916330f53b2c83610b74611867565b63ffffffff85166000908152600491820160205260409081902090516001600160e01b031960e086901b168152610bac939201614cf3565b60006040518083038186803b158015610bc457600080fd5b505af4158015610bd8573d6000803e3d6000fd5b505050507f8ee0b45ff34ecba58a4014e0e0a9ebb97960cbfcae1e413072685b6b56b724fe8183604051610ac4929190614e87565b600061046b60ff8316633b9aca00614fe8565b600080610c2c83611674565b915050610c9d866001600160a01b031663524a562f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c949190614c56565b84838888611c88565b9695505050505050565b610caf613c30565b610cb7613ca2565b6000610cc1611867565b6003016000610cda6001600160a01b0389168888611da7565b8152602001908152602001600020549050610d14610cf6611867565b63ffffffff8616600090815260069190910160205260409020611dda565b9250610d7c610d21611867565b63ffffffff8087166000908152600692909201602052604082206004019190610d57906001600160a01b038b16908a9061188b16565b8152602001908152602001600020600083815260200190815260200160002054611ffb565b915050935093915050565b6000610d9287612049565b610e09610d9d611867565b886001600160a01b031663524a562f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ddb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dff9190614c56565b88888888886120de565b979650505050505050565b610e1c613cc9565b610e24611867565b63ffffffff80841660009081526004929092016020908152604092839020835160a081018552815490931683526001810180548551818502810185019096528086529394919385840193830182828015610e9d57602002820191906000526020600020905b815481526020019060010190808311610e89575b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015610ef557602002820191906000526020600020905b815481526020019060010190808311610ee1575b505050505081526020016003820154815260200160048201805480602002602001604051908101604052809291908181526020018280548015610f5757602002820191906000526020600020905b815481526020019060010190808311610f43575b5050505050815250509050919050565b600080610f95610f75611867565b63ffffffff85166000908152602091909152604090205490600882901c90565b91505061074e8160ff166024811115610fb057610fb0614412565b612211565b610fbd613cf6565b610fc689612049565b604080516101a0810182526000918101829052606081018290526080810182905260c081018290526101008101829052610140810182905261016081019190915263ffffffff871660e08201526001600160a01b03898116825286166020820152610120810185905260ff841660a0820152610180810183905261104987611674565b60ff90811660808401521660608201526040805163016dff5d60e01b815290516001600160a01b038c169163016dff5d9160048083019260209291908290030181865afa15801561109e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110c29190614c56565b6001600160a01b031663bcd418b087876040518363ffffffff1660e01b81526004016110ef929190614fff565b60a060405180830381865afa15801561110c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111309190615028565b6020015160ff1660c08201526001600160401b038816610100820152611154611867565b600301600061116488888b612613565b815260208101919091526040908101600020546101608301526001600160a01b038b1690820152611193613cf6565b60006111b1836080015160ff166024811115610fb057610fb0614412565b60048111156111c2576111c2614412565b4361014085015290506201b2531946016112405760646001600160a01b031663a3b1b31d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611215573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061123991906150cb565b6101408401525b60001960ff8216016112f3577322e2625f9d8c28cb4bce944e9d64efb4388ea99163bd9fe9db8461126f611867565b63ffffffff8d166000908152600491820160205260409081902090516001600160e01b031960e086901b1681526112a79392016151d4565b600060405180830381865af41580156112c4573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526112ec919081019061541a565b9150611417565b60011960ff8216016113be576113098888612629565b73df837f0327bbf85b066c400f17b2b2727f94cb2f63307800a28461132c611867565b60060160008d63ffffffff1663ffffffff1681526020019081526020016000206040518363ffffffff1660e01b81526004016113699291906151d4565b600060405180830381865af4158015611386573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526113ae919081019061544e565b60ff166101408501529150611417565b60021960ff8216016113f9576112ec836113d6611867565b63ffffffff8c166000908152600591909101602052604090205461ffff166126a6565b604051634d07af3360e01b815260ff82166004820152602401610663565b60e083015163ffffffff166101208301526001600160a01b03881660408301526101608083018890528301516101808301527f67af56937d4d46082b2327381733f95fd39ed3df103ec0f3761eb315b463f5648a8a60ff8416600481111561148157611481614412565b8b8b8b8961016001518c8a8c61014001516040516114a89a9998979695949392919061549f565b60405180910390a1509a9950505050505050505050565b6114c8846118fc565b60006114d5838386611c07565b9050836114e0611867565b63ffffffff831660008181526005929092016020908152604092839020805461ffff191661ffff9586161790558251918252928716928101929092527f9df2729723fcb63a4f46ffc16905a683f7eb0e8dd3ec260515e66b4d4cd8c0ad910160405180910390a15050505050565b611557826118fc565b61156b816101800151826101a00151611ace565b6000611584826020015183604001518460000151611c07565b905061158e611867565b63ffffffff8216600090815260069190910160205260408120906115b28282613d6c565b600182016000905560028201600090556003820160006115d29190613d6c565b50506115fe826115e0611867565b63ffffffff8416600090815260069190910160205260409020612988565b7fe9039f79f66d71725ba719a022b5729bbfce163887322d74e690772fe247fa4b8183604051610ac4929190615530565b60008061163d610f75611867565b91506002905061165b8260ff166024811115610fb057610fb0614412565b600481111561166c5761166c614412565b149392505050565b600080610899610f75611867565b600061168d85612049565b611698848484612b2c565b905060006116a583611674565b9150506000866001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156116e8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061170c9190614c56565b6001600160a01b031663bcd418b087876040518363ffffffff1660e01b8152600401611739929190614fff565b60a060405180830381865afa158015611756573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061177a9190615028565b905060006117968360ff166024811115610fb057610fb0614412565b60048111156117a7576117a7614412565b905060001960ff8216011561182a5760011960ff8216016117ff576117fa6117cd611867565b60060160008763ffffffff1663ffffffff1681526020019081526020016000208888878660200151612b81565b61182a565b60021960ff8216011561182a5760405163cafe050760e01b815260ff82166004820152602401610663565b505050949350505050565b600061183f611867565b600301600061184f868686612613565b81526020019081526020016000205490509392505050565b7ffa9e067a92ca4a9057b7b4465a8f29d633e1758238bd3a4a8ec5d0f904f6b90090565b60006001600160401b038211156118b857604051633995b34160e01b815260048101839052602401610663565b50600160a01b600160e01b0360a09190911b166001600160a01b039091161790565b600060ff8316600883901b61ff001617610468565b6060600061074e83612b91565b604051631430d62960e21b81526001600160a01b038216906350c358a490611928903390600401614a84565b602060405180830381865afa158015611945573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611969919061554f565b610499573360405163451cea1760e11b81526004016106639190614a84565b60006104688383612bed565b61199c612ce7565b6119b957604051631afcd79f60e31b815260040160405180910390fd5b565b6001600160a01b038116611a035760405162461bcd60e51b815260206004820152600f60248201526e2d32b9379031b7b73a3937b63632b960891b6044820152606401610663565b611a32816106b460017f5165972ef41194f06c5007493031d0b927c20741adcb74403b954009fd2c3618614c25565b611a61426106b460017f6f55f470bdc9cb5f04223fd822021061668e4dccb43e8727b295106dc9769c8b614c25565b611a90436106b460017f812a673dfca07956350df10f8a654925f561d7a0da09bdbe79e653939a14d9f1614c25565b7f1a2dd071001ebf6e03174e3df5b305795a4ad5d41d8fdb9ba41dbbe236713426814243604051611ac39392919061556a565b60405180910390a150565b815181518114611af15760405163586cb9e160e01b815260040160405180910390fd5b60005b81811015611c015760006001600160a01b0316848281518110611b1957611b1961558b565b60200260200101516001600160a01b031603611b485760405163d92e233d60e01b815260040160405180910390fd5b828181518110611b5a57611b5a61558b565b602002602001015163ffffffff16600003611b885760405163eb79f4eb60e01b815260040160405180910390fd5b633b9aca0063ffffffff16838281518110611ba557611ba561558b565b602002602001015163ffffffff161115611bf957828181518110611bcb57611bcb61558b565b60200260200101516040516304470bfd60e21b8152600401610663919063ffffffff91909116815260200190565b600101611af4565b50505050565b600080611c12611867565b9050611c3085856024811115611c2a57611c2a614412565b85612d01565b91506000611c4a86866024811115610a0757610a07614412565b63ffffffff808516600081815260208681526040808320869055858352600188019091529020929350611c7e9291612df616565b5050509392505050565b600080611ca38560ff166024811115610fb057610fb0614412565b90506001816004811115611cb957611cb9614412565b03611cc8576001915050610796565b6002816004811115611cdc57611cdc614412565b03611ceb576001915050610796565b6003816004811115611cff57611cff614412565b03611d8957604051630152f74f60e41b815263ffffffff871660048201526001600160a01b0385811660248301526044820185905288169063152f74f090606401602060405180830381865afa158015611d5d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d81919061554f565b915050610796565b604051630dad26cb60e31b815260ff86166004820152602401610663565b6001600160a01b039290921660a09190911b600160a01b600160e01b03161760e09190911b6001600160e01b0319161790565b611de2613c30565b611dea613c30565b611e4583600001805480602002602001604051908101604052809291908181526020018280548015611e3b57602002820191906000526020600020905b815481526020019060010190808311611e27575b5050505050612e02565b606083015260808201526001830154600881901c601082901c603083901c60ff90811661010086015263ffffffff90911660e085015290811660c08401521660a08201526002830154611e9f60a082901c9060e083901c90565b60ff166101608401526001600160401b039081166101408401526001600160a01b039091166101208301526003840154908190811115611ee157611ee1613f2f565b604051908082528060200260200182016040528015611f0a578160200160208202803683370190505b50610180830152806001600160401b03811115611f2957611f29613f2f565b604051908082528060200260200182016040528015611f52578160200160208202803683370190505b506101a083015260005b81811015611ff257611f90856003018281548110611f7c57611f7c61558b565b90600052602060002001549060a082901c90565b8461018001518381518110611fa757611fa761558b565b60200260200101856101a001518481518110611fc557611fc561558b565b63ffffffff909316602093840291909101909201919091526001600160a01b039091169052600101611f5c565b50909392505050565b612003613ca2565b506040805160808101825260ff8381166001148252604884901c166020820152602883901c60030b9181019190915263ffffffff60089290921c91909116606082015290565b336001600160a01b0316816001600160a01b031663683fedf76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612091573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120b59190614c56565b6001600160a01b031614610499573360405163cf219bef60e01b81526004016106639190614a84565b6000806120ee87876130f361319c565b9050600089600101600061210288856118da565b81526020019081526020016000209050600061211d826133a5565b9050806000036121405760405163296a76c760e21b815260040160405180910390fd5b6000816001146121625761215d612158600184614c25565b6130f3565b612165565b60005b9050600060028d018160ff60e81b60e88c901b1660ff60e01b60e089901b16600160a01b600160e01b0360a08c901b166001600160a01b038d16171717815260200190815260200160002090506121c48c8486848660008e8e8d6133af565b95508563ffffffff166000036121ed57604051639cb04d5d60e01b815260040160405180910390fd5b6122008163ffffffff80891690612df616565b505050505050979650505050505050565b6000600482602481111561222757612227614412565b14806122445750600582602481111561224257612242614412565b145b806122605750600882602481111561225e5761225e614412565b145b1561226d57506001919050565b600182602481111561228157612281614412565b148061229e5750600282602481111561229c5761229c614412565b145b806122ba575060038260248111156122b8576122b8614412565b145b806122d65750600a8260248111156122d4576122d4614412565b145b806122f2575060208260248111156122f0576122f0614412565b145b8061230e5750602182602481111561230c5761230c614412565b145b8061232a5750602282602481111561232857612328614412565b145b806123465750602382602481111561234457612344614412565b145b1561235357506002919050565b600682602481111561236757612367614412565b14806123845750600782602481111561238257612382614412565b145b806123a05750600b82602481111561239e5761239e614412565b145b806123bc5750600c8260248111156123ba576123ba614412565b145b806123d85750600d8260248111156123d6576123d6614412565b145b806123f45750600e8260248111156123f2576123f2614412565b145b806124105750600f82602481111561240e5761240e614412565b145b8061242c5750601082602481111561242a5761242a614412565b145b806124485750601182602481111561244657612446614412565b145b806124645750601282602481111561246257612462614412565b145b806124805750601382602481111561247e5761247e614412565b145b8061249c5750601482602481111561249a5761249a614412565b145b806124b8575060158260248111156124b6576124b6614412565b145b806124d4575060168260248111156124d2576124d2614412565b145b806124f0575060178260248111156124ee576124ee614412565b145b8061250c5750601882602481111561250a5761250a614412565b145b806125285750601982602481111561252657612526614412565b145b806125445750601a82602481111561254257612542614412565b145b806125605750601b82602481111561255e5761255e614412565b145b8061257c5750601c82602481111561257a5761257a614412565b145b806125985750601d82602481111561259657612596614412565b145b806125b45750601e8260248111156125b2576125b2614412565b145b806125d05750601f8260248111156125ce576125ce614412565b145b156125dd57506003919050565b8160248111156125ef576125ef614412565b6040516302f13cad60e41b815260ff9091166004820152602401610663565b919050565b600061074b6001600160a01b0385168484611da7565b6000612633611867565b9050600061264a6001600160a01b0385168461188b565b60088301546000828152600785016020526040902054919250429161266f9190614c38565b111561268e5760405163051211b760e11b815260040160405180910390fd5b60009081526007909101602052604090204290555050565b6126ae613cf6565b8161ffff166000036126d357604051635c5e6bb360e11b815260040160405180910390fd5b82604001516001600160a01b031663524a562f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612715573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127399190614c56565b6001600160a01b0316631c1b0daa84600001518561010001518660e001518760a0015188602001518961012001518a606001518b61016001518c61018001516040518a63ffffffff1660e01b815260040161279c999897969594939291906155a1565b6000604051808303816000875af11580156127bb573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526127e3919081019061541a565b90508060200151806127f3575080515b1561046b57600083604001516001600160a01b0316628e96916040518163ffffffff1660e01b8152600401602060405180830381865afa15801561283b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061285f9190614c56565b9050600061286c8461347e565b6020860151610120870151604051631c2aafe760e01b81529293506000926001600160a01b03861692631c2aafe7926128a992879060040161556a565b602060405180830381865afa1580156128c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128ea91906150cb565b9050826001600160a01b031663184f61438760200151886101200151858560016129149190614c38565b6040516001600160e01b031960e087901b1681526001600160a01b039094166004850152602484019290925260448301526064820152608401600060405180830381600087803b15801561296757600080fd5b505af115801561297b573d6000803e3d6000fd5b5050505050505092915050565b6060820151608083015161299b916134b5565b80516129ae918391602090910190613d8a565b5060a082015160c083015160e084015161010085015160ff90931660089290921b61ff00169190911760109190911b65ffffffff0000161760309190911b60ff60301b161760018201556101208201516101408301516101608401516001600160a01b0390921660a09190911b600160a01b600160e01b03161760e09190911b60ff60e01b16176002820155610180820151516000816001600160401b03811115612a5b57612a5b613f2f565b604051908082528060200260200182016040528015612a84578160200160208202803683370190505b50905060005b82811015612b0f57612aea856101a001518281518110612aac57612aac61558b565b60200260200101518661018001518381518110612acb57612acb61558b565b60200260200101516001600160a01b031661371390919063ffffffff16565b828281518110612afc57612afc61558b565b6020908102919091010152600101612a8a565b508051612b259060038501906020840190613d8a565b5050505050565b600080612b37611867565b90506000612b46868686612613565b6000818152600384016020526040902054909150612b65906001614c38565b6000918252600390920160205260409020819055949350505050565b612b25858585856137318661373c565b606081600001805480602002602001604051908101604052809291908181526020018280548015612be157602002820191906000526020600020905b815481526020019060010190808311612bcd575b50505050509050919050565b60008181526001830160205260408120548015612cd6576000612c11600183614c25565b8554909150600090612c2590600190614c25565b9050808214612c8a576000866000018281548110612c4557612c4561558b565b9060005260206000200154905080876000018481548110612c6857612c6861558b565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080612c9b57612c9b61561a565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505061046b565b600091505061046b565b5092915050565b6000612cf1610ad1565b54600160401b900460ff16919050565b600060ff84161580612d14575060ff8316155b80612d21575061ffff8216155b15612d3f5760405163273e150360e21b815260040160405180910390fd5b60648460ff1610612d68576040516340eff0e560e01b815260ff85166004820152602401610663565b60648360ff1610612d915760405163017c4c5960e01b815260ff84166004820152602401610663565b6127108261ffff161115612dbe57604051637dc12d1d60e01b815261ffff83166004820152602401610663565b61ffff8216612dd260ff8516612710615630565b612de260ff8716620f4240615630565b612dec9190615658565b61074b9190615658565b60006104688383613825565b805160609081906000612e16826008614fe8565b90506000816001600160401b03811115612e3257612e32613f2f565b604051908082528060200260200182016040528015612e5b578160200160208202803683370190505b5090506000826001600160401b03811115612e7857612e78613f2f565b604051908082528060200260200182016040528015612ea1578160200160208202803683370190505b5090506000805b85811015612fce5760005b6008811015612fc557600081612eca846008614fe8565b612ed49190614c38565b9050612ee1826020614fe8565b8b8481518110612ef357612ef361558b565b602002602001015160001c901c60020b868281518110612f1557612f1561558b565b60039290920b602092830291909101820152612f32908390614fe8565b612f3d906018614c38565b8b8481518110612f4f57612f4f61558b565b602002602001015160001c901c858281518110612f6e57612f6e61558b565b602002602001019060ff16908160ff1681525050858181518110612f9457612f9461558b565b602002602001015160030b600003612fac5750612fc5565b83612fb681615675565b94505050806001019050612eb3565b50600101612ea8565b50806001600160401b03811115612fe757612fe7613f2f565b604051908082528060200260200182016040528015613010578160200160208202803683370190505b509650806001600160401b0381111561302b5761302b613f2f565b604051908082528060200260200182016040528015613054578160200160208202803683370190505b50955060005b818110156130e8578381815181106130745761307461558b565b602002602001015188828151811061308e5761308e61558b565b602002602001019060030b908160030b815250508281815181106130b4576130b461558b565b60200260200101518782815181106130ce576130ce61558b565b60ff9092166020928302919091019091015260010161305a565b505050505050915091565b60008160000361310557506000919050565b600061310f613874565b905061311c836001614c38565b4340414443423a5a8860405160200161317998979695949392919097885260609690961b6001600160601b0319166020880152603487019490945260548601929092526074850152609484015260b483015260d482015260f40190565b6040516020818303038152906040528051906020012060001c61074e91906156a4565b82516000908015806131af575083518114155b156131cd5760405163360ee06360e21b815260040160405180910390fd5b806001036131f757846000815181106131e8576131e861558b565b60200260200101519150613376565b600061320a633b9aca0063ffffffff8616565b9050600061321d633b9aca006001615658565b63ffffffff16905060005b83811015613372578781815181106132425761324261558b565b602002602001015160ff166000031561336a57633b9aca0063ffffffff168782815181106132725761327261558b565b602002602001015163ffffffff1611156132cf578681815181106132985761329861558b565b6020908102919091010151604051631b4fdad960e11b815263ffffffff9091166004820152633b9aca006024820152604401610663565b828782815181106132e2576132e261558b565b6020026020010151633b9aca006132f991906156b8565b63ffffffff161161336a57818782815181106133175761331761558b565b602002602001015163ffffffff16101561336a5786818151811061333d5761333d61558b565b602002602001015163ffffffff16915087818151811061335f5761335f61558b565b602002602001015194505b600101613228565b5050505b8160ff1660000361339d5760405163b3cea44d60e01b815260006004820152602401610663565b509392505050565b600061046b825490565b6000886133bb886133a5565b106133cd57600194506133cd876138ac565b6000805b8a811015613440578a88106133e557600097505b60006133f18b8a6138fa565b90506134008d82878a8a611c88565b80156134235750878061342357506134218a63ffffffff8084169061390616565b155b1561343357925060019150613440565b50600197880197016133d1565b508015801561344d575085155b156134705761345b886138ac565b61346d8b8b8b8b8b60018b8b8b6133af565b91505b509998505050505050505050565b600061348d8261ffff1661391e565b60405160200161349d91906156d5565b60405160208183030381529060405261046b90615703565b606081518351146134d95760405163586cb9e160e01b815260040160405180910390fd5b6000600884516134e9919061572a565b6134f4906001614c38565b90506000816001600160401b0381111561351057613510613f2f565b604051908082528060200260200182016040528015613539578160200160208202803683370190505b50905060005b8281101561370a5760005b600881101561370157600081613561846008614fe8565b61356b9190614c38565b90508751811061357b5750613701565b627fffff60020b8882815181106135945761359461558b565b602002602001015160030b13806135cd5750627fffff1960020b8882815181106135c0576135c061558b565b602002602001015160030b125b1561360b578781815181106135e4576135e461558b565b602002602001015160030b604051632eb1d06960e01b815260040161066391815260200190565b87818151811061361d5761361d61558b565b602002602001015160030b60000361364857604051637c946ed760e01b815260040160405180910390fd5b613653826020614fe8565b8882815181106136655761366561558b565b602002602001015162ffffff1660001b901b8484815181106136895761368961558b565b6020026020010181815117915081815250508160206136a89190614fe8565b6136b3906018614c38565b8782815181106136c5576136c561558b565b602002602001015160ff1660001b901b8484815181106136e7576136e761558b565b60209081029190910101805190911790525060010161354a565b5060010161353f565b50949350505050565b63ffffffff60a01b60a09190911b166001600160a01b039091161790565b600061046b826130f3565b60408051608081018252600181526000602082018190529181018290526060810161376e633b9aca0063ffffffff8716565b63ffffffff1690529050600061378388611dda565b905060006137ac8260600151836080015185606001516137a288610c0d565b8660e00151613a26565b509050806009815181106137c2576137c261558b565b602090810291909101015160030b60408401526137de83613aec565b60048a0160006137f76001600160a01b038c168b61188b565b8152602001908152602001600020600088815260200190815260200160002081905550505050505050505050565b600081815260018301602052604081205461386c5750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561046b565b50600061046b565b60006350877ed646148061388b5750630235ddd046145b156138a9576040518060208160008060185afa6138a457fe5b505190505b90565b60006138b7826118ef565b905060005b8151811015610847576138f18282815181106138da576138da61558b565b60200260200101518461198890919063ffffffff16565b506001016138bc565b60006104688383613b0a565b60008181526001830160205260408120541515610468565b6060816000036139455750506040805180820190915260018152600360fc1b602082015290565b8160005b811561396f578061395981615675565b91506139689050600a8361572a565b9150613949565b6000816001600160401b0381111561398957613989613f2f565b6040519080825280601f01601f1916602001820160405280156139b3576020820181803683370190505b5090505b8415613a1e576139c8600183614c25565b91506139d5600a866156a4565b6139e0906030614c38565b60f81b8183815181106139f5576139f561558b565b60200101906001600160f81b031916908160001a905350613a17600a8661572a565b94506139b7565b949350505050565b60606000613a3560048661573e565b60408051602b8082526105808201909252919650602082016105608036833701905050915060005b8751811015613ad357613a8a878281518110613a7b57613a7b61558b565b60200260200101518787613b34565b83898381518110613a9d57613a9d61558b565b602002602001015160ff1681518110613ab857613ab861558b565b60039290920b60209283029190910190910152600101613a5d565b50613ae083866000613b34565b90509550959350505050565b600061046b8260000151836060015184604001518560200151613bdc565b6000826000018281548110613b2157613b2161558b565b9060005260206000200154905092915050565b60008360030b600003613b495750600061074e565b6000633b9aca00613b5e84600388900b615761565b613b689190615791565b633b9aca00613b8163ffffffff8716600389900b615761565b613b8b9190615791565b8660030b613b9991906157bf565b613ba391906157bf565b9050637fffffff811380613bbb5750637fffffff1981125b1561074b576040516344dc334160e01b815260048101829052602401610663565b600084613bea576000613bed565b60015b60ff1660001b905060088463ffffffff1660001b901b8117905060288363ffffffff1660001b901b8117905060488260ff1660001b901b81179050949350505050565b604080516101c081018252600080825260208201819052909182019081526060602082018190526040820181905260008183018190526080830181905260a0830181905260c0830181905260e08301819052610100830181905261012083015261014082018190526101609091015290565b60408051608081018252600080825260208201819052918101829052606081019190915290565b6040805160a081018252600080825260606020830181905292820183905282820152608081019190915290565b604080516101c08101825260008082526020820181905291810182905260608082018190526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201839052610140820183905261016082018390526101808201929092526101a081019190915290565b50805460008255906000526020600020908101906104999190613dd5565b828054828255906000526020600020908101928215613dc5579160200282015b82811115613dc5578251825591602001919060010190613daa565b50613dd1929150613dd5565b5090565b5b80821115613dd15760008155600101613dd6565b6001600160a01b038116811461049957600080fd5b803561260e81613dea565b60008060408385031215613e1d57600080fd5b8235613e2881613dea565b946020939093013593505050565b63ffffffff8116811461049957600080fd5b803561260e81613e36565b600060208284031215613e6557600080fd5b813561074e81613e36565b60ff8116811461049957600080fd5b803561260e81613e70565b80356025811061260e57600080fd5b60008060408385031215613eac57600080fd5b8235613eb781613e70565b9150613ec560208401613e8a565b90509250929050565b6020808252825182820181905260009190848201906040850190845b81811015613f0657835183529284019291840191600101613eea565b50909695505050505050565b600060208284031215613f2457600080fd5b813561074e81613dea565b634e487b7160e01b600052604160045260246000fd5b6040516101c081016001600160401b0381118282101715613f6857613f68613f2f565b60405290565b604051601f8201601f191681016001600160401b0381118282101715613f9657613f96613f2f565b604052919050565b803561ffff8116811461260e57600080fd5b60006001600160401b03821115613fc957613fc9613f2f565b5060051b60200190565b600082601f830112613fe457600080fd5b81356020613ff9613ff483613fb0565b613f6e565b8083825260208201915060208460051b87010193508684111561401b57600080fd5b602086015b8481101561404057803561403381613e70565b8352918301918301614020565b509695505050505050565b8060030b811461049957600080fd5b803561260e8161404b565b600082601f83011261407657600080fd5b81356020614086613ff483613fb0565b8083825260208201915060208460051b8701019350868411156140a857600080fd5b602086015b848110156140405780356140c08161404b565b83529183019183016140ad565b6000604082840312156140df57600080fd5b604051604081016001600160401b03828210818311171561410257614102613f2f565b81604052829350843591508082111561411a57600080fd5b61412686838701613fd3565b8352602085013591508082111561413c57600080fd5b5061414985828601614065565b6020830152505092915050565b600082601f83011261416757600080fd5b81356020614177613ff483613fb0565b8083825260208201915060208460051b87010193508684111561419957600080fd5b602086015b848110156140405780356141b181613dea565b835291830191830161419e565b600082601f8301126141cf57600080fd5b813560206141df613ff483613fb0565b8083825260208201915060208460051b87010193508684111561420157600080fd5b602086015b8481101561404057803561421981613e36565b8352918301918301614206565b60006020828403121561423857600080fd5b81356001600160401b038082111561424f57600080fd5b908301906101c0828603121561426457600080fd5b61426c613f45565b61427583613e7f565b815261428360208401613f9e565b602082015261429460408401613e8a565b60408201526142a560608401613e48565b60608201526080830135828111156142bc57600080fd5b6142c8878286016140cd565b60808301525060a0830135828111156142e057600080fd5b6142ec878286016140cd565b60a0830152506142fe60c08401613e48565b60c082015261430f60e0840161405a565b60e082015261010061432281850161405a565b9082015261012061433484820161405a565b9082015261014061434684820161405a565b9082015261016061435884820161405a565b90820152610180838101358381111561437057600080fd5b61437c88828701614156565b8284015250506101a0808401358381111561439657600080fd5b6143a2888287016141be565b918301919091525095945050505050565b6000602082840312156143c557600080fd5b813561074e81613e70565b6000806000606084860312156143e557600080fd5b83356143f081613dea565b925060208401359150604084013561440781613e36565b809150509250925092565b634e487b7160e01b600052602160045260246000fd5b6025811061443857614438614412565b9052565b60008151808452602080850194506020840160005b8381101561447057815160ff1687529582019590820190600101614451565b509495945050505050565b60008151808452602080850194506020840160005b8381101561447057815160030b87529582019590820190600101614490565b6001600160a01b03169052565b60008151808452602080850194506020840160005b838110156144705781516001600160a01b0316875295820195908201906001016144d1565b60008151808452602080850194506020840160005b8381101561447057815163ffffffff168752958201959082019060010161450b565b805161ffff16825260006101c0602083015161454e602086018260ff169052565b5060408301516145616040860182614428565b5060608301518160608601526145798286018261443c565b91505060808301518482036080860152614593828261447b565b91505060a08301516145aa60a086018260ff169052565b5060c08301516145bf60c086018260ff169052565b5060e08301516145d760e086018263ffffffff169052565b506101008381015160ff1690850152610120808401516145f9828701826144af565b5050610140838101516001600160401b0316908501526101608084015160ff1690850152610180808401518583038287015261463583826144bc565b925050506101a08084015185830382870152610c9d83826144f6565b60a08152600061466460a083018561452d565b905082511515602083015260ff6020840151166040830152604083015160030b606083015263ffffffff60608401511660808301529392505050565b600080600080600060a086880312156146b857600080fd5b85356001600160401b03808211156146cf57600080fd5b6146db89838a01613fd3565b965060208801359150808211156146f157600080fd5b506146fe888289016141be565b945050604086013561470f81613e70565b9250606086013561471f81613dea565b949793965091946080013592915050565b60008151808452602080850194506020840160005b8381101561447057815187529582019590820190600101614745565b6020815263ffffffff82511660208201526000602083015160a0604084015261478d60c0840182614730565b90506040840151601f19808584030160608601526147ab8383614730565b92506060860151608086015260808601519150808584030160a0860152506107968282614730565b6005811061443857614438614412565b6020810161046b82846147d3565b80356001600160401b038116811461260e57600080fd5b600080600080600080600060e0888a03121561482357600080fd5b873561482e81613dea565b9650602061483d8982016147f1565b9650604089013561484d81613e36565b9550606089013561485d81613dea565b94506080890135935060a089013561487481613e70565b925060c08901356001600160401b038082111561489057600080fd5b818b0191508b601f8301126148a457600080fd5b8135818111156148b6576148b6613f2f565b6148c8601f8201601f19168501613f6e565b91508082528c848285010111156148de57600080fd5b808484018584013760008482840101525080935050505092959891949750929550565b80511515825260006101c0602083015161491f602086018215159052565b50604083015161493260408601826144af565b50606083015181606086015261494a828601826144bc565b9150506080830151614961608086018260030b9052565b5060a083015161497660a086018260030b9052565b5060c083015161498b60c086018260030b9052565b5060e08301516149a060e086018260030b9052565b50610100808401516149b68287018260030b9052565b50506101208381015163ffffffff81168683015250506101408381015163ffffffff8116868301525050610160838101519085015261018080840151908501526101a08084015185830382870152610c9d83826144f6565b6020815260006104686020830184614901565b60005b83811015614a3c578181015183820152602001614a24565b50506000910152565b60008151808452614a5d816020860160208601614a21565b601f01601f19169290920160200192915050565b6020815260006104686020830184614a45565b6001600160a01b0391909116815260200190565b600080600060608486031215614aad57600080fd5b614ab684613f9e565b92506020840135614ac681613e70565b9150614ad460408501613e8a565b90509250925092565b600060208284031215614aef57600080fd5b81356001600160401b0380821115614b0657600080fd5b908301906101c08286031215614b1b57600080fd5b614b23613f45565b614b2c83613f9e565b8152614b3a60208401613e7f565b6020820152614b4b60408401613e8a565b6040820152606083013582811115614b6257600080fd5b614b6e87828601613fd3565b606083015250608083013582811115614b8657600080fd5b614b9287828601614065565b608083015250614ba460a08401613e7f565b60a0820152614bb560c08401613e7f565b60c0820152614bc660e08401613e48565b60e0820152610100614bd9818501613e7f565b90820152610120614beb848201613dff565b90820152610140614bfd8482016147f1565b90820152610160614358848201613e7f565b634e487b7160e01b600052601160045260246000fd5b8181038181111561046b5761046b614c0f565b8082018082111561046b5761046b614c0f565b805161260e81613dea565b600060208284031215614c6857600080fd5b815161074e81613dea565b805160408084528151908401819052600091602091908201906060860190845b81811015614cb257835160ff1683529284019291840191600101614c93565b50508483015186820387850152805180835290840192506000918401905b8083101561404057835160030b8252928401926001929092019190840190614cd0565b60408152614d0760408201845160ff169052565b60006020840151614d1e606084018261ffff169052565b506040840151614d316080840182614428565b50606084015163ffffffff1660a083015260808401516101c060c08401819052614d5f610200850183614c73565b915060a0860151603f19808685030160e0870152614d7d8483614c73565b935060c08801519150610100614d9a8188018463ffffffff169052565b60e08901519250610120614db28189018560030b9052565b90890151925061014090614dca8883018560030b9052565b8901519250610160614de08882018560030b9052565b90890151925061018090614df88883018560030b9052565b89015192506101a0614e0e8882018560030b9052565b818a01519350828887030185890152614e2786856144bc565b9550808a01519450505080868503016101e08701525050614e4882826144f6565b925050508260208301529392505050565b6000815160408452614e6e604085018261443c565b905060208301518482036020860152610796828261447b565b63ffffffff8316815260406020820152614ea760408201835160ff169052565b60006020830151614ebe606084018261ffff169052565b506040830151614ed16080840182614428565b50606083015163ffffffff1660a083015260808301516101c060c08401819052614eff610200850183614e59565b915060a0850151603f19808685030160e0870152614f1d8483614e59565b935060c08701519150610100614f3a8188018463ffffffff169052565b60e08801519250610120614f528189018560030b9052565b90880151925061014090614f6a8883018560030b9052565b8801519250610160614f808882018560030b9052565b90880151925061018090614f988883018560030b9052565b88015192506101a0614fae8882018560030b9052565b818901519350828887030185890152614fc786856144bc565b9550808901519450505080868503016101e08701525050610c9d82826144f6565b808202811582820484141761046b5761046b614c0f565b6001600160a01b03929092168252602082015260400190565b8051801515811461260e57600080fd5b600060a0828403121561503a57600080fd5b60405160a081018181106001600160401b038211171561505c5761505c613f2f565b604052825161506a81613e70565b8152602083015161507a81613e70565b602082015261508b60408401615018565b6040820152606083015168ffffffffffffffffff811681146150ac57600080fd5b606082015260808301516150bf81613dea565b60808201529392505050565b6000602082840312156150dd57600080fd5b5051919050565b60006101a06150f48484516144af565b602083015161510660208601826144af565b50604083015161511960408601826144af565b50606083015161512e606086018260ff169052565b506080830151615143608086018260ff169052565b5060a083015161515860a086018260ff169052565b5060c083015161516d60c086018260ff169052565b5060e083015161518560e086018263ffffffff169052565b50610100838101516001600160401b03169085015261012080840151908501526101408084015190850152610160808401519085015261018080840151818601839052610c9d83870182614a45565b6040815260006151e760408301856150e4565b90508260208301529392505050565b600082601f83011261520757600080fd5b81516020615217613ff483613fb0565b8083825260208201915060208460051b87010193508684111561523957600080fd5b602086015b8481101561404057805161525181613dea565b835291830191830161523e565b805161260e8161404b565b805161260e81613e36565b600082601f83011261528557600080fd5b81516020615295613ff483613fb0565b8083825260208201915060208460051b8701019350868411156152b757600080fd5b602086015b848110156140405780516152cf81613e36565b83529183019183016152bc565b60006101c082840312156152ef57600080fd5b6152f7613f45565b905061530282615018565b815261531060208301615018565b602082015261532160408301614c4b565b604082015260608201516001600160401b038082111561534057600080fd5b61534c858386016151f6565b606084015261535d6080850161525e565b608084015261536e60a0850161525e565b60a084015261537f60c0850161525e565b60c084015261539060e0850161525e565b60e084015261010091506153a582850161525e565b8284015261012091506153b9828501615269565b8284015261014091506153cd828501615269565b8284015261016091508184015182840152610180915081840151828401526101a09150818401518181111561540157600080fd5b61540d86828701615274565b8385015250505092915050565b60006020828403121561542c57600080fd5b81516001600160401b0381111561544257600080fd5b613a1e848285016152dc565b6000806040838503121561546157600080fd5b82516001600160401b0381111561547757600080fd5b615483858286016152dc565b925050602083015161549481613e70565b809150509250929050565b60006101406001600160401b038d16835263ffffffff8c1660208401526154c9604084018c6147d3565b6001600160a01b038a1660608401526080830189905260ff881660a084015260c0830187905260e0830181905261550281840187614a45565b90508281036101008401526155178186614901565b915050826101208301529b9a5050505050505050505050565b63ffffffff8316815260406020820152600061074b604083018461452d565b60006020828403121561556157600080fd5b61046882615018565b6001600160a01b039390931683526020830191909152604082015260600190565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b038a811682526001600160401b038a16602083015263ffffffff8916604083015260ff8881166060840152908716608083015260a08201869052841660c082015260e08101839052610120610100820181905260009061560a83820185614a45565b9c9b505050505050505050505050565b634e487b7160e01b600052603160045260246000fd5b63ffffffff81811683821602808216919082811461565057615650614c0f565b505092915050565b63ffffffff818116838216019080821115612ce057612ce0614c0f565b60006001820161568757615687614c0f565b5060010190565b634e487b7160e01b600052601260045260246000fd5b6000826156b3576156b361568e565b500690565b63ffffffff828116828216039080821115612ce057612ce0614c0f565b6553544f52595f60d01b8152600082516156f6816006850160208701614a21565b9190910160060192915050565b80516020808301519190811015615724576000198160200360031b1b821691505b50919050565b6000826157395761573961568e565b500490565b600063ffffffff808416806157555761575561568e565b92169190910492915050565b80820260008212600160ff1b8414161561577d5761577d614c0f565b818105831482151761046b5761046b614c0f565b6000826157a0576157a061568e565b600160ff1b8214600019841416156157ba576157ba614c0f565b500590565b808201828112600083128015821682158216171561565057615650614c0f56fe22573091f17911fb166032a3d9e0554aa73d31b7b7ddea4a4dd2995650af84bda2646970667358221220aca579955b2f59434338862fed1191ebdd799a24002827ba3706a52746c6e85b64736f6c63430008170033
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.