Overview
S Balance
S Value
$0.00More Info
Private Name Tags
ContractCreator
Loading...
Loading
Contract Source Code Verified (Exact Match)
Contract Name:
MonsterLib
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 "../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; /// @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: 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 "../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 "../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/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; 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": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"int256","name":"value","type":"int256"}],"name":"IntValueOutOfRange","type":"error"},{"inputs":[{"internalType":"uint256","name":"atype","type":"uint256"}],"name":"NotAType","type":"error"},{"inputs":[],"name":"NotMagic","type":"error"},{"inputs":[],"name":"NotYourAttackItem","type":"error"},{"inputs":[],"name":"NotYourBuffItem","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":"uint256","name":"x","type":"uint256"}],"name":"TooLowX","type":"error"},{"inputs":[{"internalType":"uint256","name":"attackType","type":"uint256"}],"name":"UnknownAttackType","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"components":[{"components":[{"components":[{"internalType":"int32[]","name":"fighterAttributes","type":"int32[]"},{"components":[{"internalType":"uint32","name":"level","type":"uint32"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"uint32","name":"life","type":"uint32"},{"internalType":"uint32","name":"mana","type":"uint32"},{"internalType":"uint32","name":"lifeChances","type":"uint32"}],"internalType":"struct IStatController.ChangeableStats","name":"fighterStats","type":"tuple"},{"internalType":"enum IFightCalculator.AttackType","name":"attackType","type":"uint8"},{"internalType":"address","name":"attackToken","type":"address"},{"internalType":"uint256","name":"attackTokenId","type":"uint256"},{"internalType":"uint256","name":"race","type":"uint256"}],"internalType":"struct IFightCalculator.FighterInfo","name":"info","type":"tuple"},{"components":[{"internalType":"enum IItemController.AttackType","name":"aType","type":"uint8"},{"internalType":"int32","name":"min","type":"int32"},{"internalType":"int32","name":"max","type":"int32"},{"components":[{"internalType":"int32","name":"strength","type":"int32"},{"internalType":"int32","name":"dexterity","type":"int32"},{"internalType":"int32","name":"vitality","type":"int32"},{"internalType":"int32","name":"energy","type":"int32"}],"internalType":"struct IStatController.CoreAttributes","name":"attributeFactors","type":"tuple"}],"internalType":"struct IItemController.AttackInfo","name":"magicAttack","type":"tuple"},{"internalType":"int32","name":"health","type":"int32"},{"internalType":"int32","name":"manaConsumed","type":"int32"},{"internalType":"int32","name":"damage","type":"int32"},{"internalType":"int32","name":"damagePoison","type":"int32"},{"internalType":"int32","name":"damageReflect","type":"int32"},{"components":[{"internalType":"bool","name":"stun","type":"bool"},{"internalType":"bool","name":"burn","type":"bool"},{"internalType":"bool","name":"freeze","type":"bool"},{"internalType":"bool","name":"confuse","type":"bool"},{"internalType":"bool","name":"curse","type":"bool"},{"internalType":"bool","name":"poison","type":"bool"},{"internalType":"bool","name":"gotCriticalHit","type":"bool"},{"internalType":"bool","name":"missed","type":"bool"},{"internalType":"bool","name":"hitBlocked","type":"bool"}],"internalType":"struct IFightCalculator.Statuses","name":"statuses","type":"tuple"}],"internalType":"struct IFightCalculator.Fighter","name":"fighterA","type":"tuple"},{"components":[{"components":[{"internalType":"int32[]","name":"fighterAttributes","type":"int32[]"},{"components":[{"internalType":"uint32","name":"level","type":"uint32"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"uint32","name":"life","type":"uint32"},{"internalType":"uint32","name":"mana","type":"uint32"},{"internalType":"uint32","name":"lifeChances","type":"uint32"}],"internalType":"struct IStatController.ChangeableStats","name":"fighterStats","type":"tuple"},{"internalType":"enum IFightCalculator.AttackType","name":"attackType","type":"uint8"},{"internalType":"address","name":"attackToken","type":"address"},{"internalType":"uint256","name":"attackTokenId","type":"uint256"},{"internalType":"uint256","name":"race","type":"uint256"}],"internalType":"struct IFightCalculator.FighterInfo","name":"info","type":"tuple"},{"components":[{"internalType":"enum IItemController.AttackType","name":"aType","type":"uint8"},{"internalType":"int32","name":"min","type":"int32"},{"internalType":"int32","name":"max","type":"int32"},{"components":[{"internalType":"int32","name":"strength","type":"int32"},{"internalType":"int32","name":"dexterity","type":"int32"},{"internalType":"int32","name":"vitality","type":"int32"},{"internalType":"int32","name":"energy","type":"int32"}],"internalType":"struct IStatController.CoreAttributes","name":"attributeFactors","type":"tuple"}],"internalType":"struct IItemController.AttackInfo","name":"magicAttack","type":"tuple"},{"internalType":"int32","name":"health","type":"int32"},{"internalType":"int32","name":"manaConsumed","type":"int32"},{"internalType":"int32","name":"damage","type":"int32"},{"internalType":"int32","name":"damagePoison","type":"int32"},{"internalType":"int32","name":"damageReflect","type":"int32"},{"components":[{"internalType":"bool","name":"stun","type":"bool"},{"internalType":"bool","name":"burn","type":"bool"},{"internalType":"bool","name":"freeze","type":"bool"},{"internalType":"bool","name":"confuse","type":"bool"},{"internalType":"bool","name":"curse","type":"bool"},{"internalType":"bool","name":"poison","type":"bool"},{"internalType":"bool","name":"gotCriticalHit","type":"bool"},{"internalType":"bool","name":"missed","type":"bool"},{"internalType":"bool","name":"hitBlocked","type":"bool"}],"internalType":"struct IFightCalculator.Statuses","name":"statuses","type":"tuple"}],"internalType":"struct IFightCalculator.Fighter","name":"fighterB","type":"tuple"}],"indexed":false,"internalType":"struct IFightCalculator.FightInfoInternal","name":"result","type":"tuple"},{"components":[{"components":[{"internalType":"int32[]","name":"fighterAttributes","type":"int32[]"},{"components":[{"internalType":"uint32","name":"level","type":"uint32"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"uint32","name":"life","type":"uint32"},{"internalType":"uint32","name":"mana","type":"uint32"},{"internalType":"uint32","name":"lifeChances","type":"uint32"}],"internalType":"struct IStatController.ChangeableStats","name":"fighterStats","type":"tuple"},{"internalType":"enum IFightCalculator.AttackType","name":"attackType","type":"uint8"},{"internalType":"address","name":"attackToken","type":"address"},{"internalType":"uint256","name":"attackTokenId","type":"uint256"},{"internalType":"uint256","name":"race","type":"uint256"}],"internalType":"struct IFightCalculator.FighterInfo","name":"fighterA","type":"tuple"},{"components":[{"internalType":"int32[]","name":"fighterAttributes","type":"int32[]"},{"components":[{"internalType":"uint32","name":"level","type":"uint32"},{"internalType":"uint32","name":"experience","type":"uint32"},{"internalType":"uint32","name":"life","type":"uint32"},{"internalType":"uint32","name":"mana","type":"uint32"},{"internalType":"uint32","name":"lifeChances","type":"uint32"}],"internalType":"struct IStatController.ChangeableStats","name":"fighterStats","type":"tuple"},{"internalType":"enum IFightCalculator.AttackType","name":"attackType","type":"uint8"},{"internalType":"address","name":"attackToken","type":"address"},{"internalType":"uint256","name":"attackTokenId","type":"uint256"},{"internalType":"uint256","name":"race","type":"uint256"}],"internalType":"struct IFightCalculator.FighterInfo","name":"fighterB","type":"tuple"},{"internalType":"uint64","name":"dungeonId","type":"uint64"},{"internalType":"uint32","name":"objectId","type":"uint32"},{"internalType":"address","name":"heroAdr","type":"address"},{"internalType":"uint256","name":"heroId","type":"uint256"},{"internalType":"uint8","name":"stageId","type":"uint8"},{"internalType":"uint256","name":"iteration","type":"uint256"},{"internalType":"uint8","name":"turn","type":"uint8"}],"indexed":false,"internalType":"struct IFightCalculator.FightCall","name":"callData","type":"tuple"},{"indexed":false,"internalType":"uint256","name":"iteration","type":"uint256"}],"name":"FightResultProcessed","type":"event"}]
Contract Creation Code
615e6a6200003b600b82828239805160001a60731461002e57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106100355760003560e01c8063307800a21461003a575b600080fd5b81801561004657600080fd5b5061005a610055366004614c8e565b610071565b604051610068929190614e4b565b60405180910390f35b6100796147d7565b600061008b84846100966100a7610185565b915091509250929050565b60006100a18261028d565b92915050565b60408051608081018252600080825260208201819052918101829052606081019190915260006100e0868660000151876020015161033d565b90506100ec8184610373565b7f726ea5eedb687a29deab0c52f0dfd5ffc55b2eb8fd292b6fb398a17f895d2b5e8482878860e001516040516101259493929190615235565b60405180910390a1604051806080016040528082600001516040015160030b815260200182602001516040015160030b815260200182600001516060015160030b815260200182602001516060015160030b815250915050949350505050565b61018d6147d7565b600080610199876104d7565b905060006102528760040160006101cb8b61012001518c602001516001600160a01b03166106bf90919063ffffffff16565b8152602080820192909252604090810160009081206101608d015182528352818120548251608080820185528382528186018490528185018490526060918201939093528351928301845260ff8083166001148452604883901c1694830194909452602881901c60030b9282019290925260089190911c63ffffffff169181019190915290565b90508060200151925060008061026c8a8a85878c8c610714565b9150915061027e898b86858588610989565b95505050505094509492505050565b60008160000361029f57506000919050565b60006102a9610d78565b90506102b6836001615367565b4340414443423a5a8860405160200161031398979695949392919097885260609690961b6001600160601b0319166020880152603487019490945260548601929092526074850152609484015260b483015260d482015260f40190565b6040516020818303038152906040528051906020012060001c6103369190615390565b9392505050565b61034561484d565b61034d61484d565b61035c85858360000151610db0565b61036b85848360200151610db0565b949350505050565b600061037e83610e8e565b905061038b838284610f2e565b61039783821584610f2e565b825180515160e090910151602085015151516103b492919061105d565b602083015180515160e090910151845151516103d192919061105d565b60006103df846001856113ad565b905060006103ef856000866113ad565b608080840151875160e09081015160ff928316151560c09182015260a0808801518b51840151908516151590840152818801518b518401519085161515610100918201529486015160208c018051850151918616151591840191909152908601518151840151908516151590840152908501519051909101519116151591015290506104b68361047f5781610481565b825b8461048c578361048e565b825b8561049d5787602001516104a0565b87515b866104ac578851611503565b8860200151611503565b84516104c29083611616565b6104d0856020015182611616565b5050505050565b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c08101919091526040518060e0016040528083600001516001600160a01b0316815260200183602001516001600160a01b0316815260200183604001516001600160a01b0316815260200183604001516001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa158015610596573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105ba91906153af565b6001600160a01b0316815260200183604001516001600160a01b0316628e96916040518163ffffffff1660e01b8152600401602060405180830381865afa158015610609573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061062d91906153af565b6001600160a01b0316815260200183604001516001600160a01b03166331423c266040518163ffffffff1660e01b8152600401602060405180830381865afa15801561067d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106a191906153af565b6001600160a01b031681526020018361012001518152509050919050565b60006001600160401b038211156106f157604051633995b34160e01b8152600481018390526024015b60405180910390fd5b5067ffffffffffffffff60a01b60a09190911b166001600160a01b039091161790565b61071c614872565b6107246148be565b61072c614930565b610734614930565b60006107b98860a0015189604001516001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610780573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107a491906153af565b8d61018001518b602001518c60c00151611668565b90506107c5818961196a565b60030b86526040805180820190915260608d015160ff908116825260c08e0151166020808301919091528201515191945061080d918c908c90859063ffffffff168d8d611b11565b8760a0018197508263ffffffff1663ffffffff168152508294505050505060006108c08860a001516040518061012001604052808681526020018581526020018e61010001516001600160401b031681526020018e60e0015163ffffffff1681526020018b602001516001600160a01b031681526020018b60c0015181526020018e60a0015160ff1681526020018e610160015181526020018c6020015160ff168152508d600001518a8a63ffffffff16565b9050604051806101000160405280826040015187600001516108e291906153cc565b60030b81526020016109008560200151604001518460000151611c92565b60030b815260200184602001516040015160030b836000015160030b1361092857600061093d565b602085015160400151835161093d91906153f3565b60030b8152602001826000015160030b8152602001826020015160030b81526020018660a0015163ffffffff168152602001848152602001838152509450505050965096945050505050565b6109916147d7565b60006109b887610120015188602001516001600160a01b03166106bf90919063ffffffff16565b90506064836020015160ff1611156109d257600060608601525b60808501516060860151600391820b15910b1581156109f5576109f5888a611cbf565b81806109fe5750805b15610aa557845115610a2c57600083815260048b01602090815260408083206101608d015184529091528120555b8115610aa05787608001516001600160a01b0316637ac46f978a602001518b61012001516040518363ffffffff1660e01b8152600401610a6d92919061541a565b600060405180830381600087803b158015610a8757600080fd5b505af1158015610a9b573d6000803e3d6000fd5b505050505b610b36565b845115610ad657608087015160030b60408601526020850151610ac9906001615433565b60ff166020860152610b0b565b60408051608080820183526001808352602083015289015160030b9181019190915260a088015163ffffffff16606082015294505b610b1485611dca565b600084815260048c01602090815260408083206101608e015184529091529020555b8115610c76576000610b4b8a60e00151611de8565b60808a015160208c01516101208d0151604051631c2aafe760e01b81526001600160a01b039283166004820152602481019190915260448101849052929350600092911690631c2aafe790606401602060405180830381865afa158015610bb6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bda919061544c565b905089608001516001600160a01b031663184f61438c602001518d610120015185856001610c089190615367565b6040516001600160e01b031960e087901b1681526001600160a01b039094166004850152602484019290925260448301526064820152608401600060405180830381600087803b158015610c5b57600080fd5b505af1158015610c6f573d6000803e3d6000fd5b5050505050505b801515845281610c87576000610cc9565b610cc98760e0015160200151602001518860a0015163ffffffff168960c0015160200151602001518a60c0015160200151600001518d6060015160ff16611e21565b63ffffffff16610140850152604087015160030b608085015281610cee576000610d0f565b60c087015151602081518110610d0657610d06615465565b60200260200101515b600390810b60a08601526020880151810b60e08601528751900b61010085015281610d4857604080516000815260208101909152610d56565b610d56878a88611e60611e75565b60608501528180610d645750805b151560208501525050509695505050505050565b60006350877ed6461480610d8f5750630235ddd046145b15610dad576040518060208160008060185afa610da857fe5b505190505b90565b818152602082015160409081015160030b9082015260608201516001600160a01b031615610e895760028260400151600a811115610df057610df0614f8f565b14610e0e57604051635e7b518b60e11b815260040160405180910390fd5b6060820151608083015160405163ce11a20960e01b81526001600160a01b0386169263ce11a20992610e429260040161541a565b60e060405180830381865afa158015610e5f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e8391906154ed565b60208201525b505050565b6000600282515160400151600a811115610eaa57610eaa614f8f565b03610eec57600260208301515160400151600a811115610ecc57610ecc614f8f565b03610ee4578151516020830151516100a19190611ffa565b506001919050565b600260208301515160400151600a811115610f0957610f09614f8f565b03610f1657506000919050565b8151516020830151516100a19190611ffa565b919050565b610f388383612029565b610e8957600082610f4e57602084015151610f52565b8351515b9050600083610f6357845151610f6a565b6020850151515b9050600084610f8157856020015160200151610f88565b8551602001515b9050600085610f9c57865160e00151610fa6565b866020015160e001515b90506000610fb98460000151602a61204a565b9050610fc985846022848a612080565b151582528351610feb9086908590602390610fe590600b61204a565b8a612080565b15156020830152835161100a9086908590602490610fe590600c61204a565b1515604083015261101f85846025848a612080565b1515606083015261103485846026848a612080565b1515608083015261104985846029848a612080565b151560a09092019190915250505050505050565b8160200151156111495760038360078151811061107c5761107c615465565b602002602001015161108e9190615553565b836007815181106110a1576110a1615465565b602002602001018181516110b591906153f3565b60030b905250603283600c815181106110d0576110d0615465565b602002602001018181516110e491906153cc565b60030b905250600a816019815181106110ff576110ff615465565b6020026020010181815161111391906153cc565b60030b90525060148160188151811061112e5761112e615465565b6020026020010181815161114291906153cc565b60030b9052505b8160400151156112665760028360048151811061116857611168615465565b6020026020010181815161117c9190615553565b60030b90525060028360058151811061119757611197615465565b602002602001018181516111ab9190615553565b600390810b9091529050836006815181106111c8576111c8615465565b60200260200101516111da9190615553565b836006815181106111ed576111ed615465565b6020026020010181815161120191906153f3565b60030b90525060028360088151811061121c5761121c615465565b602002602001018181516112309190615553565b60030b905250603283600b8151811061124b5761124b615465565b6020026020010181815161125f91906153cc565b60030b9052505b8160600151156112a05760028360068151811061128557611285615465565b602002602001018181516112999190615553565b60030b9052505b81608001511561133857600283600b815181106112bf576112bf615465565b602002602001018181516112d39190615553565b60030b905250600283600c815181106112ee576112ee615465565b602002602001018181516113029190615553565b60030b905250600283600d8151811061131d5761131d615465565b602002602001018181516113319190615553565b60030b9052505b81511561136f57600a8160198151811061135457611354615465565b6020026020010181815161136891906153cc565b60030b9052505b8160a0015115610e895760028360068151811061138e5761138e615465565b602002602001018181516113a29190615553565b60030b905250505050565b6113b5614998565b6000836113c7578451604001516113d1565b8460200151604001515b90506113dd8585612029565b15611424576040805160e08101825260039290920b8252600060208301819052908201819052606082018190526080820181905260a0820181905260c08201529050610336565b6000846114365760208601515161143a565b8551515b905060008561144b57865151611452565b6020870151515b905060018260400151600a81111561146c5761146c614f8f565b036114845761147d828285886120ee565b93506114f9565b60028260400151600a81111561149c5761149c614f8f565b036114c75761147d8282886114b9578960200151602001516114c0565b8951602001515b8689612336565b8160400151600a8111156114dd576114dd614f8f565b604051630d2a62bd60e41b81526004016106e891815260200190565b5050509392505050565b8351600390810b604083019081526020860151820b608085015251900b156115a8578251600390810b60408401908152602085015190910b6080830152606085015190516115519190612543565b60030b60c0820181905260408301805161156c9083906153f3565b60030b905250606083015160408201516115869190612543565b60030b60c083018190526040820180516115a19083906153f3565b60030b9052505b6115ba81604001518260e00151612558565b60030b60a083018190526040820180516115d59083906153f3565b60030b905250604082015160e08301516115ef9190612558565b60030b60a0820181905260408301805161160a9083906153f3565b60030b90525050505050565b604082015160030b156116645760008160400151836040015161163991906153cc565b83515190915060009061164d90600961204a565b90506116598282612543565b60030b604085015250505b5050565b6116706149d4565b6000848060200190518101906116869190615688565b8051909150600a81111561169c5761169c614f8f565b6000036116d2578051600a8111156116b6576116b6614f8f565b60405163f089562b60e01b81526004016106e891815260200190565b60208101516001600160a01b0316156117a457600080886001600160a01b031663aef874fa846020015185604001516040518363ffffffff1660e01b815260040161171e92919061541a565b6040805180830381865afa15801561173a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061175e919061574a565b91509150816001600160a01b0316866001600160a01b03161415806117835750848114155b156117a1576040516376adf5c560e11b815260040160405180910390fd5b50505b600080876001600160a01b03166395f0d29a87876040518363ffffffff1660e01b81526004016117d592919061541a565b6040805180830381865afa1580156117f1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611815919061574a565b9150915060005b83606001515181101561195a576000808b6001600160a01b031663aef874fa8760600151858151811061185157611851615465565b60200260200101518860800151868151811061186f5761186f615465565b60200260200101516040518363ffffffff1660e01b815260040161189492919061541a565b6040805180830381865afa1580156118b0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118d4919061574a565b91509150816001600160a01b0316896001600160a01b03161415806118f95750878114155b801561193257506001600160a01b03851615806119325750816001600160a01b0316856001600160a01b03161415806119325750808414155b1561195057604051631c927f9f60e31b815260040160405180910390fd5b505060010161181c565b5091925050505b95945050505050565b611972614930565b60008083608001516001600160a01b031663d4ac484885602001518660c001516040518363ffffffff1660e01b81526004016119af92919061541a565b60a060405180830381865afa1580156119cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119f09190615783565b9050600080611a0a836000015163ffffffff1688886125ad565b9450915083905060028751600a811115611a2657611a26614f8f565b03611ab15760a0860151602088015160405163415a9c6f60e11b81526001600160a01b0391821660048201529116906382b538de9060240161018060405180830381865afa158015611a7c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611aa09190615829565b60a00151611aae90856153cc565b93505b6040518060c001604052808381526020018481526020018860000151600a811115611ade57611ade614f8f565b81526020898101516001600160a01b0316908201526040808a015190820152606001600181525094505050509250929050565b611b19614930565b6000611b236148be565b611b2b6149d4565b8851611b4757611b42633b9aca0063ffffffff8716565b611b4d565b88606001515b9250611b6e8a84611b618e60200151612677565b8e518b9060ff168a61268a565b6020808a015163ffffffff948516918101919091529390921690925282875293509150611b9c908988612801565b8851611be3578351611bc990600981518110611bba57611bba615465565b602002602001015160016128eb565b602085015163ffffffff9091166040909101819052611be9565b88604001515b602085015163ffffffff9091166040909101528351600a81518110611c1057611c10615465565b6020908102919091018101519085015163ffffffff90911660609091015280516040850190600a811115611c4657611c46614f8f565b9081600a811115611c5957611c59614f8f565b90525060208101516001600160a01b0316606085015260400151608084015260c081015160ff1660a08401529750975097945050505050565b60008160030b600014611cb857611ca98284612543565b611cb390846153f3565b610336565b5090919050565b600360ff16816080015160ff16036116645781604001516001600160a01b031663683fedf76040518163ffffffff1660e01b8152600401602060405180830381865afa158015611d13573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d3791906153af565b60e08201516020830151610120840151606085015160405163057bd05b60e41b815263ffffffff90941660048501526001600160a01b039283166024850152604484019190915260ff16606483015291909116906357bd05b090608401600060405180830381600087803b158015611dae57600080fd5b505af1158015611dc2573d6000803e3d6000fd5b505050505050565b60006100a18260000151836060015184604001518560200151612901565b6000611df98263ffffffff16612955565b604051602001611e0991906158e0565b6040516020818303038152906040526100a190615923565b6000670de0b6b3a7640000611e3c8663ffffffff891661594a565b611e469190615961565b611e569063ffffffff8816615367565b9695505050505050565b60208220808352600090610336908390615390565b60408051610140810190915261010083015160ff16815260c085015151606091611961916020820190601781518110611eb057611eb0615465565b602002602001015160030b81526020018760c00151600001516018602b811115611edc57611edc614f8f565b81518110611eec57611eec615465565b602002602001015160030b8152602001856101a00151815260200186604001516001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f49573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f6d91906153af565b6001600160a01b03168152610180860151602082015260a088015163ffffffff16604082015260006060820152608001600360ff16876080015160ff1614611fe15760c08801516020908101519081015190516060890151611fdc9263ffffffff169160ff9081169116612a55565b611fe4565b60005b8152602001611ff287612aab565b905283612c5a565b600061200b8260000151600661204a565b60030b61201d8460000151600661204a565b60030b12159392505050565b60008161203f57602083015160e0015151610336565b50505160e001515190565b60006103368383602b81111561206257612062614f8f565b8151811061207257612072615465565b60200260200101518361309c565b6000806120938787600001518787613296565b90508060030b6000036120aa576000915050611961565b6064600382900b126120c0576001915050611961565b6120cc8160030b6133fe565b6120e26120d960646133fe565b8563ffffffff16565b10979650505050505050565b6120f6614998565b60408051600180825281830190925290816020015b612113614998565b81526020019060019003908161210b5790505060008151811061213857612138615465565b602002602001015190506000612152866000015184613419565b905061215e8187613440565b905061216f81878760a00151613472565b90506121908661218b61218260646133fe565b8663ffffffff16565b613508565b61219b57600061219e565b60015b60ff1660808301819052156121bd576121b8816002615975565b6121bf565b805b90506121d081868860a00151613525565b90506121dc8186613581565b90506122426121fb6121f38860000151600661204a565b60030b6133fe565b61220d6121f38860000151600761204a565b88602001516000015163ffffffff1688602001516000015163ffffffff1661223d6121f38c60000151601e61204a565b613599565b612257670de0b6b3a76400008563ffffffff16565b11612263576000612266565b60015b60ff1660a0830152845161227f906121f390600861204a565b61228d60648563ffffffff16565b1061229957600061229c565b60015b60ff90811660c084015260a0830151161515806122bf575060c082015160ff1615155b156122c8575060005b60008160030b8560030b13156122e7576122e282866153f3565b6122ea565b60005b90506122f681866153f3565b600382810b855281900b602085015291506123118288613699565b60030b604084015261232382876136c0565b60030b6060840152509095945050505050565b61233e614998565b6000612373878661236e612358896020015160030b6133fe565b6123688a6040015160030b6133fe565b886136d3565b61370a565b9050612384818887600001516137f5565b905061239581888860a00151613472565b905060006123b38861218b6123aa60646133fe565b8763ffffffff16565b9050806123c057816123cb565b6123cb826002615975565b91506123dc82888a60a00151613525565b91506123e88288613581565b91506001865160058111156123ff576123ff614f8f565b03612421576124108288600b61387f565b61241a90836153f3565b915061247a565b60028651600581111561243657612436614f8f565b03612447576124108288600c61387f565b60038651600581111561245c5761245c614f8f565b0361247a5761246d8288600d61387f565b61247790836153f3565b91505b60008260030b8660030b126124985761249383876153f3565b61249b565b60005b90506124a781876153f3565b92506040518060e001604052808260030b81526020018460030b81526020016124d0858c613699565b60030b81526020016124f7898c6124f2670de0b6b3a76400008b63ffffffff16565b6138af565b612501868c6138fa565b61250b91906153cc565b60030b81526020018361251f576000612522565b60015b60ff1681526000602082018190526040909101529998505050505050505050565b60008160030b8360030b12611cb85781610336565b60008160a001518015612576575060016125748460030b6133fe565b115b156125a45761259d600a61258c8560030b6133fe565b6125969190615961565b600161390d565b90506100a1565b50600092915050565b6060600082608001516001600160a01b03166357e9ee7b6040518060a0016040528086602001516001600160a01b031681526020018660c0015181526020018863ffffffff1681526020018760600151815260200187608001518152506040518263ffffffff1660e01b815260040161262691906159c6565b600060405180830381865afa158015612643573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261266b9190810190615acd565b91509150935093915050565b60006100a160ff8316633b9aca0061594a565b60606000806126976149d4565b61269f6148be565b6126a88b61391c565b60a081015160ff16945090506126bf886001615367565b8463ffffffff16101561271957612716600f6126e163ffffffff87168b615b1a565b6126ec90600a61594a565b6126f69190615961565b6127069063ffffffff8716615367565b61271189600561594a565b613b3d565b93505b600260ff1681610160015160ff1603612791576000612743670de0b6b3a76400008863ffffffff16565b9050670a688906bd8b000081111561275e576001835261278b565b600283526101208201516001600160a01b031660208401526101408201516001600160401b031660408401525b506127d5565b80610160015160ff16600a8111156127ab576127ab614f8f565b8290600a8111156127be576127be614f8f565b9081600a8111156127d1576127d1614f8f565b9052505b6127ee816060015182608001518c8c8560e00151613b4c565b9095509250965096509650965096915050565b60608201515160005b818110156104d0576000808460a001516001600160a01b03166324e44dfc8760600151858151811061283e5761283e615465565b60200260200101518860800151868151811061285c5761285c615465565b60200260200101516040518363ffffffff1660e01b815260040161288192919061541a565b600060405180830381865afa15801561289e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526128c69190810190615b2d565b915091506128dd876128d88484613c12565b613cc0565b50505080600101905061280a565b60008160030b8360030b1215611cb85781610336565b60008461290f576000612912565b60015b60ff1660001b905060088463ffffffff1660001b901b8117905060288363ffffffff1660001b901b8117905060488260ff1660001b901b81179050949350505050565b60608160000361297c5750506040805180820190915260018152600360fc1b602082015290565b8160005b81156129a6578061299081615bf0565b915061299f9050600a83615961565b9150612980565b6000816001600160401b038111156129c0576129c0614ad8565b6040519080825280601f01601f1916602001820160405280156129ea576020820181803683370190505b5090505b841561036b576129ff600183615b1a565b9150612a0c600a86615390565b612a17906030615367565b60f81b818381518110612a2c57612a2c615465565b60200101906001600160f81b031916908160001a905350612a4e600a86615961565b94506129ee565b6000806005612a6686866001613d1e565b612a709190615961565b612a7b906001615367565b9050828111612a8b576000611961565b612a958382615b1a565b612aa090600a615367565b611961906002615ced565b60006100a1670de0b6b3a764000061271184604001516001600160a01b031663683fedf76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612afe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b2291906153af565b6001600160a01b031663b2192fdd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b5f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b839190615cf9565b60ff1685604001516001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612bc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612bec91906153af565b6001600160a01b031663df13f4896040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c29573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c4d919061544c565b8660c0015160ff16613d54565b6060826101000151600014612c7e57506040805160008152602081019091526100a1565b60a083015151604080516020810191829052608086015160e08701516337347e0560e11b909352633b9aca006024830152604482019290925260009181906001600160a01b0316636e68fc0a606483016020604051808303816000875af1158015612ced573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d11919061544c565b905290506000826001600160401b03811115612d2f57612d2f614ad8565b604051908082528060200260200182016040528015612d58578160200160208202803683370190505b50905060015b83811015612d8c5780828281518110612d7957612d79615465565b6020908102919091010152600101612d5e565b50612d978282613d9d565b6000836001600160401b03811115612db157612db1614ad8565b604051908082528060200260200182016040528015612dda578160200160208202803683370190505b509050600080612df7612df08a604001516133fe565b6064613b3d565b905060005b86811015612fae57633b9aca0063ffffffff168a60600151868381518110612e2657612e26615465565b602002602001015181518110612e3e57612e3e615465565b602002602001015163ffffffff161115612eaf578960600151858281518110612e6957612e69615465565b602002602001015181518110612e8157612e81615465565b60200260200101516040516304470bfd60e21b81526004016106e8919063ffffffff91909116815260200190565b6000612ef28b60600151878481518110612ecb57612ecb615465565b602002602001015181518110612ee357612ee3615465565b60200260200101518c85613e18565b90506000612f0888633b9aca0063ffffffff8e16565b90508115801590612f265750633b9aca0082101580612f2657508181105b15612fa4578b5160ff16851015612fa4578b60a00151878481518110612f4e57612f4e615465565b602002602001015181518110612f6657612f66615465565b6020026020010151868481518110612f8057612f80615465565b6001600160a01b0390921660209283029190910190910152612fa185615bf0565b94505b5050600101612dfc565b506000826001600160401b03811115612fc957612fc9614ad8565b604051908082528060200260200182016040528015612ff2578160200160208202803683370190505b5090506000805b8881101561308c5760006001600160a01b031686828151811061301e5761301e615465565b60200260200101516001600160a01b0316146130845785818151811061304657613046615465565b602002602001015183838151811061306057613060615465565b6001600160a01b039092166020928302919091019091015261308182615bf0565b91505b600101612ff9565b50909a9950505050505050505050565b6000600882602b8111156130b2576130b2614f8f565b14806130cf5750600b82602b8111156130cd576130cd614f8f565b145b806130eb5750600c82602b8111156130e9576130e9614f8f565b145b806131075750600d82602b81111561310557613105614f8f565b145b806131235750601282602b81111561312157613121614f8f565b145b8061313f5750601382602b81111561313d5761313d614f8f565b145b8061315b5750601482602b81111561315957613159614f8f565b145b806131775750601582602b81111561317557613175614f8f565b145b806131935750602182602b81111561319157613191614f8f565b145b806131af5750602a82602b8111156131ad576131ad614f8f565b145b156131c15761259d83605a6064613ed5565b601982602b8111156131d5576131d5614f8f565b14806131f25750602282602b8111156131f0576131f0614f8f565b145b8061320e5750602382602b81111561320c5761320c614f8f565b145b8061322a5750602482602b81111561322857613228614f8f565b145b806132465750602582602b81111561324457613244614f8f565b145b806132625750602682602b81111561326057613260614f8f565b145b8061327e5750602982602b81111561327c5761327c614f8f565b145b1561328f5761259d83606480613ed5565b50816100a1565b600080856000015184602b8111156132b0576132b0614f8f565b815181106132c0576132c0615465565b602002602001015190506002600a8111156132dd576132dd614f8f565b8660400151600a8111156132f3576132f3614f8f565b036133c757602384602b81111561330c5761330c614f8f565b14801561332a5750600185600581111561332857613328614f8f565b145b1561333d5761333a6014826153cc565b90505b602484602b81111561335157613351614f8f565b14801561336f5750600285600581111561336d5761336d614f8f565b145b156133825761337f6014826153cc565b90505b602584602b81111561339657613396614f8f565b1480156133b4575060038560058111156133b2576133b2614f8f565b145b156133c7576133c46014826153cc565b90505b6133d1818561309c565b915060646133e084605a612543565b6133ea9084615975565b6133f49190615553565b611e5690836153f3565b6000808260030b1361341257506000919050565b5060030b90565b600061033661342c6121f385600461204a565b61343a6121f386600561204a565b846136d3565b60006064836134548460000151601a61204a565b61345e9190615975565b6134689190615553565b61033690846153cc565b6000600182036134b35760648461348e8560000151600e61204a565b6134989190615975565b6134a29190615553565b6134ac90856153cc565b9050610336565b600282036134cd5760648461348e8560000151600f61204a565b600382036134e75760648461348e8560000151601061204a565b600482036135015760648461348e8560000151601161204a565b5082610336565b600061351c6121f38460000151601961204a565b90911092915050565b6000600182036135455761353b8484601261387f565b6134ac90856153f3565b600282036135595761353b8484601361387f565b6003820361356d5761353b8484601461387f565b600482036135015761353b8484601561387f565b600061358f8383602161387f565b61033690846153f3565b600060646135a7838861594a565b6135b19190615961565b6135bb9087615367565b955060006135ca87600161390d565b905060006135db612596888a615367565b90508560006135eb600288615961565b905060008361360286670de0b6b3a764000061594a565b61360c9190615961565b9050600061361a838b615367565b61362c85670de0b6b3a764000061594a565b6136369190615961565b90506000670de0b6b3a76400008261364f85600261594a565b613659919061594a565b6136639190615961565b905061368861367a82670d2f13f7789f0000613b3d565b6702c68af0bb14000061390d565b9d9c50505050505050505050505050565b600060646136ac8360000151601f61204a565b6136b69085615975565b6103369190615553565b600060646136ac8360000151602761204a565b600082841015613702576136f36136ea8585615b1a565b8363ffffffff16565b6136fd9085615367565b61036b565b509092915050565b60008060648460600151600001516137278760000151600061204a565b6137319190615975565b61373b9190615553565b905060648460600151602001516137578760000151600161204a565b6137619190615975565b61376b9190615553565b61377590826153cc565b905060648460600151604001516137918760000151600261204a565b61379b9190615975565b6137a59190615553565b6137af90826153cc565b905060648460600151606001516137cb8760000151600361204a565b6137d59190615975565b6137df9190615553565b6137e990826153cc565b905061196181846153cc565b6000600182600581111561380b5761380b614f8f565b0361382b5760646138218460000151601b61204a565b6134989086615975565b600282600581111561383f5761383f614f8f565b036138555760646138218460000151601c61204a565b600382600581111561386957613869614f8f565b036135015760646138218460000151601d61204a565b6000606461389b61389485600001518561204a565b605a612543565b6138a59086615975565b61036b9190615553565b60006004845160058111156138c6576138c6614f8f565b1480156138da57506706f05b59d3b2000082115b6138e557600061036b565b600283602001516040015161036b9190615553565b600060646136ac8360000151602861204a565b6000818311611cb85781610336565b6139246148be565b61392c6148be565b6139878360000180548060200260200160405190810160405280929190818152602001828054801561397d57602002820191906000526020600020905b815481526020019060010190808311613969575b5050505050613f03565b606083015260808201526001830154600881901c601082901c603083901c60ff90811661010086015263ffffffff90911660e085015290811660c08401521660a082015260028301546139e160a082901c9060e083901c90565b60ff166101608401526001600160401b039081166101408401526001600160a01b039091166101208301526003840154908190811115613a2357613a23614ad8565b604051908082528060200260200182016040528015613a4c578160200160208202803683370190505b50610180830152806001600160401b03811115613a6b57613a6b614ad8565b604051908082528060200260200182016040528015613a94578160200160208202803683370190505b506101a083015260005b81811015613b3457613ad2856003018281548110613abe57613abe615465565b90600052602060002001549060a082901c90565b8461018001518381518110613ae957613ae9615465565b60200260200101856101a001518481518110613b0757613b07615465565b63ffffffff909316602093840291909101909201919091526001600160a01b039091169052600101613a9e565b50909392505050565b6000818310611cb85781610336565b60606000613b5b600486615d16565b60408051602b8082526105808201909252919650602082016105608036833701905050915060005b8751811015613bf957613bb0878281518110613ba157613ba1615465565b602002602001015187876141f4565b83898381518110613bc357613bc3615465565b602002602001015160ff1681518110613bde57613bde615465565b60039290920b60209283029190910190910152600101613b83565b50613c06838660006141f4565b90509550959350505050565b60408051602b8082526105808201909252606091602082016105608036833701905050905060005b8351811015613cb9576000848281518110613c5757613c57615465565b602002602001015190508060030b600014613cb0578083858481518110613c8057613c80615465565b602002602001015160ff1681518110613c9b57613c9b615465565b602002602001019060030b908160030b815250505b50600101613c3a565b5092915050565b606060005b8351811015613d1657828181518110613ce057613ce0615465565b6020026020010151848281518110613cfa57613cfa615465565b60209081029190910101805190910160030b9052600101613cc5565b509192915050565b600080613d2b858561429c565b9050828015613d4457506002613d42826001615367565b115b1561036b57611961600282615b1a565b600081600003613d6d5750670de0b6b3a7640000610336565b828211613d93576136fd8484613d838582615b1a565b613d8e906001615367565b6142cf565b5060009392505050565b80516000196001600160801b0382156104d0576020840193505b6020852080865282840193608082901c0660051b850184613dd95750506104d0565b600585811b8701805183519091529091528385019482841606901b850184613e025750506104d0565b600585901b860180518251909152905250613db7565b600080670de0b6b3a7640000613e3b670de0b6b3a7640000866101200151613b3d565b613e4b9063ffffffff881661594a565b613e559190615961565b9050670de0b6b3a76400008460c0015182613e70919061594a565b613e7a9190615961565b613e849082615367565b90506064613e9585602001516133fe565b613e9f908361594a565b613ea99190615961565b613eb39082615367565b90506064613ec1848361594a565b613ecb9190615961565b6119619082615b1a565b6000808460030b1315613d9357670de0b6b3a7640000613ef9848660030b85614311565b6136fd9190615961565b805160609081906000613f1782600861594a565b90506000816001600160401b03811115613f3357613f33614ad8565b604051908082528060200260200182016040528015613f5c578160200160208202803683370190505b5090506000826001600160401b03811115613f7957613f79614ad8565b604051908082528060200260200182016040528015613fa2578160200160208202803683370190505b5090506000805b858110156140cf5760005b60088110156140c657600081613fcb84600861594a565b613fd59190615367565b9050613fe282602061594a565b8b8481518110613ff457613ff4615465565b602002602001015160001c901c60020b86828151811061401657614016615465565b60039290920b60209283029190910182015261403390839061594a565b61403e906018615367565b8b848151811061405057614050615465565b602002602001015160001c901c85828151811061406f5761406f615465565b602002602001019060ff16908160ff168152505085818151811061409557614095615465565b602002602001015160030b6000036140ad57506140c6565b836140b781615bf0565b94505050806001019050613fb4565b50600101613fa9565b50806001600160401b038111156140e8576140e8614ad8565b604051908082528060200260200182016040528015614111578160200160208202803683370190505b509650806001600160401b0381111561412c5761412c614ad8565b604051908082528060200260200182016040528015614155578160200160208202803683370190505b50955060005b818110156141e95783818151811061417557614175615465565b602002602001015188828151811061418f5761418f615465565b602002602001019060030b908160030b815250508281815181106141b5576141b5615465565b60200260200101518782815181106141cf576141cf615465565b60ff9092166020928302919091019091015260010161415b565b505050505050915091565b60008360030b60000361420957506000610336565b6000633b9aca0061421e84600388900b615d39565b6142289190615d69565b633b9aca0061424163ffffffff8716600389900b615d39565b61424b9190615d69565b8660030b6142599190615d97565b6142639190615d97565b9050637fffffff81138061427b5750637fffffff1981125b1561036b576040516344dc334160e01b8152600481018290526024016106e8565b805b60638110156100a1576142b2836001615367565b6142bb82614379565b63ffffffff1610156100a15760010161429e565b600060015b848160ff1611614309576142ed85858360ff168661441a565b6142f79083615367565b915061430281615dbf565b90506142d4565b509392505050565b6000806143438367099e8db03256ce5d61432a87615dde565b6143349190615d39565b61433e9190615d69565b614481565b90506000811261436e5761435f81670de0b6b3a7640000615b1a565b614369908661594a565b611961565b506000949350505050565b600063ffffffff821615806143955750606363ffffffff831610155b156143a257506000919050565b670de0b6b3a76400006143e16143b9846063615dfa565b6143c4906002615e17565b6143dc9063ffffffff16670de0b6b3a764000061594a565b6145fe565b6143f390675cfb2e807b1e0000615b1a565b614406620186a063ffffffff861661594a565b614410919061594a565b6100a19190615961565b6000614425846146df565b6002614432876001615367565b61443c908861594a565b6144469190615961565b614451846001615367565b61446386670de0b6b3a764000061594a565b61446d919061594a565b6144779190615961565b6119619190615961565b600068023f2fa8f6da5b9d2819821361449957919050565b680755bf798b4a1bf1e582126144b75763a37bfec96000526004601cfd5b6503782dace9d9604e83901b059150600060606bb17217f7d1cf79abc9e3b39884821b056001605f1b01901d6bb17217f7d1cf79abc9e3b39881029093036c240c330e9fb2d9cbaf0fd5aafb1981018102606090811d6d0277594991cfc85f6e2461837cd9018202811d6d1a521255e34f6a5061b25ef1c9c319018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d6e02c72388d9f74f51a9331fed693f1419018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084016d01d3967ed30fc4f89c02bab5708119010290911d6e0587f503bb6ea29d25fcb740196450019091026d360d7aeea093263ecc6e0ecb291760621b010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b6000670de0b6b3a764000082101561462c57604051637046c4a960e01b8152600481018390526024016106e8565b6000614648614643670de0b6b3a764000085615961565b6146f8565b9050600061465e82670de0b6b3a764000061594a565b905083821c670de0b6b3a764000081900361467b57509392505050565b6706f05b59d3b200005b80156146d557670de0b6b3a764000061469e838061594a565b6146a89190615961565b9150671bc16d674ec8000082106146cd576146c38184615367565b9250600182901c91505b60011c614685565b5090949350505050565b600060026146ed8382615367565b614406846001615367565b6000600160801b821061471857608091821c916147159082615367565b90505b600160401b821061473657604091821c916147339082615367565b90505b640100000000821061475557602091821c916147529082615367565b90505b62010000821061477257601091821c9161476f9082615367565b90505b610100821061478e57600891821c9161478b9082615367565b90505b601082106147a957600491821c916147a69082615367565b90505b600482106147c457600291821c916147c19082615367565b90505b60028210610f29576100a1600182615367565b604080516101c08101825260008082526020820181905291810182905260608082018190526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201839052610140820183905261016082018390526101808201929092526101a081019190915290565b6040518060400160405280614860614a0d565b815260200161486d614a0d565b905290565b6040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a081019190915260c081016148b1614930565b815260200161486d614930565b604080516101c081018252600080825260208201819052909182019081526060602082018190526040820181905260008183018190526080830181905260a0830181905260c0830181905260e08301819052610100830181905261012083015261014082018190526101609091015290565b6040805160c0810182526060808252825160a081018452600080825260208281018290529482018190529181018290526080810191909152909182019081526020016000815260200160006001600160a01b0316815260200160008152602001600081525090565b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040805160a08101909152806000815260200160006001600160a01b031681526020016000815260200160608152602001606081525090565b604051806101000160405280614a21614930565b81526040805160808082018352600080835260208381018290528385018290528451928301855281835282810182905293820181905260608083019190915282015291019081526000602080830182905260408084018390526060808501849052608080860185905260a08087018690528351610120810185528681529485018690529284018590529083018490528201839052810182905260c081810183905260e0820183905261010082019290925291015290565b634e487b7160e01b600052604160045260246000fd5b6040516101a081016001600160401b0381118282101715614b1157614b11614ad8565b60405290565b604051608081016001600160401b0381118282101715614b1157614b11614ad8565b60405160a081016001600160401b0381118282101715614b1157614b11614ad8565b60405161012081016001600160401b0381118282101715614b1157614b11614ad8565b604051601f8201601f191681016001600160401b0381118282101715614ba657614ba6614ad8565b604052919050565b6001600160a01b0381168114614bc357600080fd5b50565b8035610f2981614bae565b60ff81168114614bc357600080fd5b8035610f2981614bd1565b63ffffffff81168114614bc357600080fd5b8035610f2981614beb565b80356001600160401b0381168114610f2957600080fd5b600082601f830112614c3057600080fd5b81356001600160401b03811115614c4957614c49614ad8565b614c5c601f8201601f1916602001614b7e565b818152846020838601011115614c7157600080fd5b816020850160208301376000918101602001919091529392505050565b60008060408385031215614ca157600080fd5b82356001600160401b0380821115614cb857600080fd5b908401906101a08287031215614ccd57600080fd5b614cd5614aee565b614cde83614bc6565b8152614cec60208401614bc6565b6020820152614cfd60408401614bc6565b6040820152614d0e60608401614be0565b6060820152614d1f60808401614be0565b6080820152614d3060a08401614be0565b60a0820152614d4160c08401614be0565b60c0820152614d5260e08401614bfd565b60e0820152610100614d65818501614c08565b908201526101208381013590820152610140808401359082015261016080840135908201526101808084013583811115614d9e57600080fd5b614daa89828701614c1f565b91830191909152509660209590950135955050505050565b6001600160a01b03169052565b60008151808452602080850194506020840160005b83811015614e095781516001600160a01b031687529582019590820190600101614de4565b509495945050505050565b60008151808452602080850194506020840160005b83811015614e0957815163ffffffff1687529582019590820190600101614e29565b60408152614e5e60408201845115159052565b60006020840151614e73606084018215159052565b506040840151614e866080840182614dc2565b5060608401516101c08060a0850152614ea3610200850183614dcf565b91506080860151614eb960c086018260030b9052565b5060a0860151614ece60e086018260030b9052565b5060c0860151610100614ee58187018360030b9052565b60e08801519150610120614efd8188018460030b9052565b90880151915061014090614f158783018460030b9052565b8801519150610160614f2e8782018463ffffffff169052565b90880151915061018090614f498783018463ffffffff169052565b8801516101a087810191909152908801519286019290925250850151838203603f19016101e0850152614f7c8282614e14565b92505050610336602083018460ff169052565b634e487b7160e01b600052602160045260246000fd5b600b8110614fb557614fb5614f8f565b9052565b80516101408084528151908401819052600091610160850191602091820190845b81811015614ff957825160030b85529383019391830191600101614fda565b50505050602083015161504a602086018263ffffffff808251168352806020830151166020840152806040830151166040840152806060830151166060840152806080830151166080840152505050565b50604083015161505d60c0860182614fa5565b50606083015161507060e0860182614dc2565b50608083015161010085015260a08301516101208501528091505092915050565b8051600681106150a3576150a3614f8f565b8252602081810151600390810b82850152604080840151820b818601526060938401518051830b8587015292830151820b6080860152820151810b60a0850152910151900b60c090910152565b8051151582526020810151151560208301526040810151615115604084018215159052565b506060810151615129606084018215159052565b50608081015161513d608084018215159052565b5060a081015161515160a084018215159052565b5060c081015161516560c084018215159052565b5060e081015161517960e084018215159052565b50610100908101511515910152565b60006102c0825181855261519e82860182614fb9565b91505060208301516151b36020860182615091565b5060408301516151c961010086018260030b9052565b5060608301516151df61012086018260030b9052565b5060808301516151f561014086018260030b9052565b5060a083015161520b61016086018260030b9052565b5060c083015161522161018086018260030b9052565b5060e08301516143096101a08601826150f0565b60018060a01b038516815260806020820152600084516040608084015261525f60c0840182615188565b90506020860151607f198483030160a085015261527c8282615188565b9150508281036040840152610120855181835261529b82840182614fb9565b915050602086015182820360208401526152b58282614fb9565b91505060408601516152d260408401826001600160401b03169052565b5060608601516152ea606084018263ffffffff169052565b5060808601516152fd6080840182614dc2565b5060a086015160a083015260c086015161531c60c084018260ff169052565b5060e086015160e08301526101008087015161533c8285018260ff169052565b50506060939093019390935250949350505050565b634e487b7160e01b600052601160045260246000fd5b808201808211156100a1576100a1615351565b634e487b7160e01b600052601260045260246000fd5b60008261539f5761539f61537a565b500690565b8051610f2981614bae565b6000602082840312156153c157600080fd5b815161033681614bae565b600381810b9083900b01637fffffff8113637fffffff19821217156100a1576100a1615351565b600382810b9082900b03637fffffff198112637fffffff821317156100a1576100a1615351565b6001600160a01b03929092168252602082015260400190565b60ff81811683821601908111156100a1576100a1615351565b60006020828403121561545e57600080fd5b5051919050565b634e487b7160e01b600052603260045260246000fd5b8051600381900b8114610f2957600080fd5b60006080828403121561549f57600080fd5b6154a7614b17565b90506154b28261547b565b81526154c06020830161547b565b60208201526154d16040830161547b565b60408201526154e26060830161547b565b606082015292915050565b600060e082840312156154ff57600080fd5b615507614b17565b82516006811061551657600080fd5b81526155246020840161547b565b60208201526155356040840161547b565b6040820152615547846060850161548d565b60608201529392505050565b60008160030b8360030b8061556a5761556a61537a565b637fffffff1982146000198214161561558557615585615351565b90059392505050565b60006001600160401b038211156155a7576155a7614ad8565b5060051b60200190565b600082601f8301126155c257600080fd5b815160206155d76155d28361558e565b614b7e565b8083825260208201915060208460051b8701019350868411156155f957600080fd5b602086015b8481101561561e57805161561181614bae565b83529183019183016155fe565b509695505050505050565b600082601f83011261563a57600080fd5b8151602061564a6155d28361558e565b8083825260208201915060208460051b87010193508684111561566c57600080fd5b602086015b8481101561561e5780518352918301918301615671565b60006020828403121561569a57600080fd5b81516001600160401b03808211156156b157600080fd5b9083019060a082860312156156c557600080fd5b6156cd614b39565b8251600b81106156dc57600080fd5b81526156ea602084016153a4565b60208201526040830151604082015260608301518281111561570b57600080fd5b615717878286016155b1565b60608301525060808301518281111561572f57600080fd5b61573b87828601615629565b60808301525095945050505050565b6000806040838503121561575d57600080fd5b825161576881614bae565b6020939093015192949293505050565b8051610f2981614beb565b600060a0828403121561579557600080fd5b61579d614b39565b82516157a881614beb565b815260208301516157b881614beb565b602082015260408301516157cb81614beb565b604082015260608301516157de81614beb565b606082015260808301516157f181614beb565b60808201529392505050565b8051610f2981614bd1565b8051600e8110610f2957600080fd5b805161ffff81168114610f2957600080fd5b6000610180828403121561583c57600080fd5b615844614b5b565b61584d836157fd565b815261585b602084016157fd565b602082015261586c60408401615808565b604082015261587d60608401615817565b606082015261588e608084016157fd565b608082015261589f60a08401615778565b60a08201526158b060c084016157fd565b60c08201526158c160e084016157fd565b60e08201526101006158d58582860161548d565b908201529392505050565b674d4f4e535445525f60c01b81526000825160005b8181101561591257602081860181015160088684010152016158f5565b506000920160080191825250919050565b80516020808301519190811015615944576000198160200360031b1b821691505b50919050565b80820281158282048414176100a1576100a1615351565b6000826159705761597061537a565b500490565b60008260030b8260030b028060030b9150808214613cb957613cb9615351565b60008151808452602080850194506020840160005b83811015614e09578151875295820195908201906001016159aa565b6000602080835260c0830160018060a01b03808651168386015282860151604086015263ffffffff6040870151166060860152606086015160a0608087015282815180855260e0880191508583019450600092505b80831015615a3d57845184168252938501936001929092019190850190615a1b565b506080880151878203601f190160a08901529450615a5b8186615995565b98975050505050505050565b600082601f830112615a7857600080fd5b81516020615a886155d28361558e565b8083825260208201915060208460051b870101935086841115615aaa57600080fd5b602086015b8481101561561e57615ac08161547b565b8352918301918301615aaf565b60008060408385031215615ae057600080fd5b82516001600160401b03811115615af657600080fd5b615b0285828601615a67565b925050615b116020840161547b565b90509250929050565b818103818111156100a1576100a1615351565b60008060408385031215615b4057600080fd5b82516001600160401b0380821115615b5757600080fd5b615b6386838701615a67565b9350602091508185015181811115615b7a57600080fd5b85019050601f81018613615b8d57600080fd5b8051615b9b6155d28261558e565b81815260059190911b82018301908381019088831115615bba57600080fd5b928401925b82841015615be1578351615bd281614bd1565b82529284019290840190615bbf565b80955050505050509250929050565b600060018201615c0257615c02615351565b5060010190565b600181815b80851115615c44578160001904821115615c2a57615c2a615351565b80851615615c3757918102915b93841c9390800290615c0e565b509250929050565b600082615c5b575060016100a1565b81615c68575060006100a1565b8160018114615c7e5760028114615c8857615ca4565b60019150506100a1565b60ff841115615c9957615c99615351565b50506001821b6100a1565b5060208310610133831016604e8410600b8410161715615cc7575081810a6100a1565b615cd18383615c09565b8060001904821115615ce557615ce5615351565b029392505050565b60006103368383615c4c565b600060208284031215615d0b57600080fd5b815161033681614bd1565b600063ffffffff80841680615d2d57615d2d61537a565b92169190910492915050565b80820260008212600160ff1b84141615615d5557615d55615351565b81810583148215176100a1576100a1615351565b600082615d7857615d7861537a565b600160ff1b821460001984141615615d9257615d92615351565b500590565b8082018281126000831280158216821582161715615db757615db7615351565b505092915050565b600060ff821660ff8103615dd557615dd5615351565b60010192915050565b6000600160ff1b8201615df357615df3615351565b5060000390565b63ffffffff828116828216039080821115613cb957613cb9615351565b63ffffffff818116838216019080821115613cb957613cb961535156fea2646970667358221220949c0c73bbc85b49cd6dcc95e9aa549cacf4524d563b28d0011360e04fe92b7864736f6c63430008170033
Deployed Bytecode
0x73df837f0327bbf85b066c400f17b2b2727f94cb2f30146080604052600436106100355760003560e01c8063307800a21461003a575b600080fd5b81801561004657600080fd5b5061005a610055366004614c8e565b610071565b604051610068929190614e4b565b60405180910390f35b6100796147d7565b600061008b84846100966100a7610185565b915091509250929050565b60006100a18261028d565b92915050565b60408051608081018252600080825260208201819052918101829052606081019190915260006100e0868660000151876020015161033d565b90506100ec8184610373565b7f726ea5eedb687a29deab0c52f0dfd5ffc55b2eb8fd292b6fb398a17f895d2b5e8482878860e001516040516101259493929190615235565b60405180910390a1604051806080016040528082600001516040015160030b815260200182602001516040015160030b815260200182600001516060015160030b815260200182602001516060015160030b815250915050949350505050565b61018d6147d7565b600080610199876104d7565b905060006102528760040160006101cb8b61012001518c602001516001600160a01b03166106bf90919063ffffffff16565b8152602080820192909252604090810160009081206101608d015182528352818120548251608080820185528382528186018490528185018490526060918201939093528351928301845260ff8083166001148452604883901c1694830194909452602881901c60030b9282019290925260089190911c63ffffffff169181019190915290565b90508060200151925060008061026c8a8a85878c8c610714565b9150915061027e898b86858588610989565b95505050505094509492505050565b60008160000361029f57506000919050565b60006102a9610d78565b90506102b6836001615367565b4340414443423a5a8860405160200161031398979695949392919097885260609690961b6001600160601b0319166020880152603487019490945260548601929092526074850152609484015260b483015260d482015260f40190565b6040516020818303038152906040528051906020012060001c6103369190615390565b9392505050565b61034561484d565b61034d61484d565b61035c85858360000151610db0565b61036b85848360200151610db0565b949350505050565b600061037e83610e8e565b905061038b838284610f2e565b61039783821584610f2e565b825180515160e090910151602085015151516103b492919061105d565b602083015180515160e090910151845151516103d192919061105d565b60006103df846001856113ad565b905060006103ef856000866113ad565b608080840151875160e09081015160ff928316151560c09182015260a0808801518b51840151908516151590840152818801518b518401519085161515610100918201529486015160208c018051850151918616151591840191909152908601518151840151908516151590840152908501519051909101519116151591015290506104b68361047f5781610481565b825b8461048c578361048e565b825b8561049d5787602001516104a0565b87515b866104ac578851611503565b8860200151611503565b84516104c29083611616565b6104d0856020015182611616565b5050505050565b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c08101919091526040518060e0016040528083600001516001600160a01b0316815260200183602001516001600160a01b0316815260200183604001516001600160a01b0316815260200183604001516001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa158015610596573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105ba91906153af565b6001600160a01b0316815260200183604001516001600160a01b0316628e96916040518163ffffffff1660e01b8152600401602060405180830381865afa158015610609573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061062d91906153af565b6001600160a01b0316815260200183604001516001600160a01b03166331423c266040518163ffffffff1660e01b8152600401602060405180830381865afa15801561067d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106a191906153af565b6001600160a01b031681526020018361012001518152509050919050565b60006001600160401b038211156106f157604051633995b34160e01b8152600481018390526024015b60405180910390fd5b5067ffffffffffffffff60a01b60a09190911b166001600160a01b039091161790565b61071c614872565b6107246148be565b61072c614930565b610734614930565b60006107b98860a0015189604001516001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610780573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107a491906153af565b8d61018001518b602001518c60c00151611668565b90506107c5818961196a565b60030b86526040805180820190915260608d015160ff908116825260c08e0151166020808301919091528201515191945061080d918c908c90859063ffffffff168d8d611b11565b8760a0018197508263ffffffff1663ffffffff168152508294505050505060006108c08860a001516040518061012001604052808681526020018581526020018e61010001516001600160401b031681526020018e60e0015163ffffffff1681526020018b602001516001600160a01b031681526020018b60c0015181526020018e60a0015160ff1681526020018e610160015181526020018c6020015160ff168152508d600001518a8a63ffffffff16565b9050604051806101000160405280826040015187600001516108e291906153cc565b60030b81526020016109008560200151604001518460000151611c92565b60030b815260200184602001516040015160030b836000015160030b1361092857600061093d565b602085015160400151835161093d91906153f3565b60030b8152602001826000015160030b8152602001826020015160030b81526020018660a0015163ffffffff168152602001848152602001838152509450505050965096945050505050565b6109916147d7565b60006109b887610120015188602001516001600160a01b03166106bf90919063ffffffff16565b90506064836020015160ff1611156109d257600060608601525b60808501516060860151600391820b15910b1581156109f5576109f5888a611cbf565b81806109fe5750805b15610aa557845115610a2c57600083815260048b01602090815260408083206101608d015184529091528120555b8115610aa05787608001516001600160a01b0316637ac46f978a602001518b61012001516040518363ffffffff1660e01b8152600401610a6d92919061541a565b600060405180830381600087803b158015610a8757600080fd5b505af1158015610a9b573d6000803e3d6000fd5b505050505b610b36565b845115610ad657608087015160030b60408601526020850151610ac9906001615433565b60ff166020860152610b0b565b60408051608080820183526001808352602083015289015160030b9181019190915260a088015163ffffffff16606082015294505b610b1485611dca565b600084815260048c01602090815260408083206101608e015184529091529020555b8115610c76576000610b4b8a60e00151611de8565b60808a015160208c01516101208d0151604051631c2aafe760e01b81526001600160a01b039283166004820152602481019190915260448101849052929350600092911690631c2aafe790606401602060405180830381865afa158015610bb6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bda919061544c565b905089608001516001600160a01b031663184f61438c602001518d610120015185856001610c089190615367565b6040516001600160e01b031960e087901b1681526001600160a01b039094166004850152602484019290925260448301526064820152608401600060405180830381600087803b158015610c5b57600080fd5b505af1158015610c6f573d6000803e3d6000fd5b5050505050505b801515845281610c87576000610cc9565b610cc98760e0015160200151602001518860a0015163ffffffff168960c0015160200151602001518a60c0015160200151600001518d6060015160ff16611e21565b63ffffffff16610140850152604087015160030b608085015281610cee576000610d0f565b60c087015151602081518110610d0657610d06615465565b60200260200101515b600390810b60a08601526020880151810b60e08601528751900b61010085015281610d4857604080516000815260208101909152610d56565b610d56878a88611e60611e75565b60608501528180610d645750805b151560208501525050509695505050505050565b60006350877ed6461480610d8f5750630235ddd046145b15610dad576040518060208160008060185afa610da857fe5b505190505b90565b818152602082015160409081015160030b9082015260608201516001600160a01b031615610e895760028260400151600a811115610df057610df0614f8f565b14610e0e57604051635e7b518b60e11b815260040160405180910390fd5b6060820151608083015160405163ce11a20960e01b81526001600160a01b0386169263ce11a20992610e429260040161541a565b60e060405180830381865afa158015610e5f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e8391906154ed565b60208201525b505050565b6000600282515160400151600a811115610eaa57610eaa614f8f565b03610eec57600260208301515160400151600a811115610ecc57610ecc614f8f565b03610ee4578151516020830151516100a19190611ffa565b506001919050565b600260208301515160400151600a811115610f0957610f09614f8f565b03610f1657506000919050565b8151516020830151516100a19190611ffa565b919050565b610f388383612029565b610e8957600082610f4e57602084015151610f52565b8351515b9050600083610f6357845151610f6a565b6020850151515b9050600084610f8157856020015160200151610f88565b8551602001515b9050600085610f9c57865160e00151610fa6565b866020015160e001515b90506000610fb98460000151602a61204a565b9050610fc985846022848a612080565b151582528351610feb9086908590602390610fe590600b61204a565b8a612080565b15156020830152835161100a9086908590602490610fe590600c61204a565b1515604083015261101f85846025848a612080565b1515606083015261103485846026848a612080565b1515608083015261104985846029848a612080565b151560a09092019190915250505050505050565b8160200151156111495760038360078151811061107c5761107c615465565b602002602001015161108e9190615553565b836007815181106110a1576110a1615465565b602002602001018181516110b591906153f3565b60030b905250603283600c815181106110d0576110d0615465565b602002602001018181516110e491906153cc565b60030b905250600a816019815181106110ff576110ff615465565b6020026020010181815161111391906153cc565b60030b90525060148160188151811061112e5761112e615465565b6020026020010181815161114291906153cc565b60030b9052505b8160400151156112665760028360048151811061116857611168615465565b6020026020010181815161117c9190615553565b60030b90525060028360058151811061119757611197615465565b602002602001018181516111ab9190615553565b600390810b9091529050836006815181106111c8576111c8615465565b60200260200101516111da9190615553565b836006815181106111ed576111ed615465565b6020026020010181815161120191906153f3565b60030b90525060028360088151811061121c5761121c615465565b602002602001018181516112309190615553565b60030b905250603283600b8151811061124b5761124b615465565b6020026020010181815161125f91906153cc565b60030b9052505b8160600151156112a05760028360068151811061128557611285615465565b602002602001018181516112999190615553565b60030b9052505b81608001511561133857600283600b815181106112bf576112bf615465565b602002602001018181516112d39190615553565b60030b905250600283600c815181106112ee576112ee615465565b602002602001018181516113029190615553565b60030b905250600283600d8151811061131d5761131d615465565b602002602001018181516113319190615553565b60030b9052505b81511561136f57600a8160198151811061135457611354615465565b6020026020010181815161136891906153cc565b60030b9052505b8160a0015115610e895760028360068151811061138e5761138e615465565b602002602001018181516113a29190615553565b60030b905250505050565b6113b5614998565b6000836113c7578451604001516113d1565b8460200151604001515b90506113dd8585612029565b15611424576040805160e08101825260039290920b8252600060208301819052908201819052606082018190526080820181905260a0820181905260c08201529050610336565b6000846114365760208601515161143a565b8551515b905060008561144b57865151611452565b6020870151515b905060018260400151600a81111561146c5761146c614f8f565b036114845761147d828285886120ee565b93506114f9565b60028260400151600a81111561149c5761149c614f8f565b036114c75761147d8282886114b9578960200151602001516114c0565b8951602001515b8689612336565b8160400151600a8111156114dd576114dd614f8f565b604051630d2a62bd60e41b81526004016106e891815260200190565b5050509392505050565b8351600390810b604083019081526020860151820b608085015251900b156115a8578251600390810b60408401908152602085015190910b6080830152606085015190516115519190612543565b60030b60c0820181905260408301805161156c9083906153f3565b60030b905250606083015160408201516115869190612543565b60030b60c083018190526040820180516115a19083906153f3565b60030b9052505b6115ba81604001518260e00151612558565b60030b60a083018190526040820180516115d59083906153f3565b60030b905250604082015160e08301516115ef9190612558565b60030b60a0820181905260408301805161160a9083906153f3565b60030b90525050505050565b604082015160030b156116645760008160400151836040015161163991906153cc565b83515190915060009061164d90600961204a565b90506116598282612543565b60030b604085015250505b5050565b6116706149d4565b6000848060200190518101906116869190615688565b8051909150600a81111561169c5761169c614f8f565b6000036116d2578051600a8111156116b6576116b6614f8f565b60405163f089562b60e01b81526004016106e891815260200190565b60208101516001600160a01b0316156117a457600080886001600160a01b031663aef874fa846020015185604001516040518363ffffffff1660e01b815260040161171e92919061541a565b6040805180830381865afa15801561173a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061175e919061574a565b91509150816001600160a01b0316866001600160a01b03161415806117835750848114155b156117a1576040516376adf5c560e11b815260040160405180910390fd5b50505b600080876001600160a01b03166395f0d29a87876040518363ffffffff1660e01b81526004016117d592919061541a565b6040805180830381865afa1580156117f1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611815919061574a565b9150915060005b83606001515181101561195a576000808b6001600160a01b031663aef874fa8760600151858151811061185157611851615465565b60200260200101518860800151868151811061186f5761186f615465565b60200260200101516040518363ffffffff1660e01b815260040161189492919061541a565b6040805180830381865afa1580156118b0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118d4919061574a565b91509150816001600160a01b0316896001600160a01b03161415806118f95750878114155b801561193257506001600160a01b03851615806119325750816001600160a01b0316856001600160a01b03161415806119325750808414155b1561195057604051631c927f9f60e31b815260040160405180910390fd5b505060010161181c565b5091925050505b95945050505050565b611972614930565b60008083608001516001600160a01b031663d4ac484885602001518660c001516040518363ffffffff1660e01b81526004016119af92919061541a565b60a060405180830381865afa1580156119cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119f09190615783565b9050600080611a0a836000015163ffffffff1688886125ad565b9450915083905060028751600a811115611a2657611a26614f8f565b03611ab15760a0860151602088015160405163415a9c6f60e11b81526001600160a01b0391821660048201529116906382b538de9060240161018060405180830381865afa158015611a7c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611aa09190615829565b60a00151611aae90856153cc565b93505b6040518060c001604052808381526020018481526020018860000151600a811115611ade57611ade614f8f565b81526020898101516001600160a01b0316908201526040808a015190820152606001600181525094505050509250929050565b611b19614930565b6000611b236148be565b611b2b6149d4565b8851611b4757611b42633b9aca0063ffffffff8716565b611b4d565b88606001515b9250611b6e8a84611b618e60200151612677565b8e518b9060ff168a61268a565b6020808a015163ffffffff948516918101919091529390921690925282875293509150611b9c908988612801565b8851611be3578351611bc990600981518110611bba57611bba615465565b602002602001015160016128eb565b602085015163ffffffff9091166040909101819052611be9565b88604001515b602085015163ffffffff9091166040909101528351600a81518110611c1057611c10615465565b6020908102919091018101519085015163ffffffff90911660609091015280516040850190600a811115611c4657611c46614f8f565b9081600a811115611c5957611c59614f8f565b90525060208101516001600160a01b0316606085015260400151608084015260c081015160ff1660a08401529750975097945050505050565b60008160030b600014611cb857611ca98284612543565b611cb390846153f3565b610336565b5090919050565b600360ff16816080015160ff16036116645781604001516001600160a01b031663683fedf76040518163ffffffff1660e01b8152600401602060405180830381865afa158015611d13573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d3791906153af565b60e08201516020830151610120840151606085015160405163057bd05b60e41b815263ffffffff90941660048501526001600160a01b039283166024850152604484019190915260ff16606483015291909116906357bd05b090608401600060405180830381600087803b158015611dae57600080fd5b505af1158015611dc2573d6000803e3d6000fd5b505050505050565b60006100a18260000151836060015184604001518560200151612901565b6000611df98263ffffffff16612955565b604051602001611e0991906158e0565b6040516020818303038152906040526100a190615923565b6000670de0b6b3a7640000611e3c8663ffffffff891661594a565b611e469190615961565b611e569063ffffffff8816615367565b9695505050505050565b60208220808352600090610336908390615390565b60408051610140810190915261010083015160ff16815260c085015151606091611961916020820190601781518110611eb057611eb0615465565b602002602001015160030b81526020018760c00151600001516018602b811115611edc57611edc614f8f565b81518110611eec57611eec615465565b602002602001015160030b8152602001856101a00151815260200186604001516001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f49573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f6d91906153af565b6001600160a01b03168152610180860151602082015260a088015163ffffffff16604082015260006060820152608001600360ff16876080015160ff1614611fe15760c08801516020908101519081015190516060890151611fdc9263ffffffff169160ff9081169116612a55565b611fe4565b60005b8152602001611ff287612aab565b905283612c5a565b600061200b8260000151600661204a565b60030b61201d8460000151600661204a565b60030b12159392505050565b60008161203f57602083015160e0015151610336565b50505160e001515190565b60006103368383602b81111561206257612062614f8f565b8151811061207257612072615465565b60200260200101518361309c565b6000806120938787600001518787613296565b90508060030b6000036120aa576000915050611961565b6064600382900b126120c0576001915050611961565b6120cc8160030b6133fe565b6120e26120d960646133fe565b8563ffffffff16565b10979650505050505050565b6120f6614998565b60408051600180825281830190925290816020015b612113614998565b81526020019060019003908161210b5790505060008151811061213857612138615465565b602002602001015190506000612152866000015184613419565b905061215e8187613440565b905061216f81878760a00151613472565b90506121908661218b61218260646133fe565b8663ffffffff16565b613508565b61219b57600061219e565b60015b60ff1660808301819052156121bd576121b8816002615975565b6121bf565b805b90506121d081868860a00151613525565b90506121dc8186613581565b90506122426121fb6121f38860000151600661204a565b60030b6133fe565b61220d6121f38860000151600761204a565b88602001516000015163ffffffff1688602001516000015163ffffffff1661223d6121f38c60000151601e61204a565b613599565b612257670de0b6b3a76400008563ffffffff16565b11612263576000612266565b60015b60ff1660a0830152845161227f906121f390600861204a565b61228d60648563ffffffff16565b1061229957600061229c565b60015b60ff90811660c084015260a0830151161515806122bf575060c082015160ff1615155b156122c8575060005b60008160030b8560030b13156122e7576122e282866153f3565b6122ea565b60005b90506122f681866153f3565b600382810b855281900b602085015291506123118288613699565b60030b604084015261232382876136c0565b60030b6060840152509095945050505050565b61233e614998565b6000612373878661236e612358896020015160030b6133fe565b6123688a6040015160030b6133fe565b886136d3565b61370a565b9050612384818887600001516137f5565b905061239581888860a00151613472565b905060006123b38861218b6123aa60646133fe565b8763ffffffff16565b9050806123c057816123cb565b6123cb826002615975565b91506123dc82888a60a00151613525565b91506123e88288613581565b91506001865160058111156123ff576123ff614f8f565b03612421576124108288600b61387f565b61241a90836153f3565b915061247a565b60028651600581111561243657612436614f8f565b03612447576124108288600c61387f565b60038651600581111561245c5761245c614f8f565b0361247a5761246d8288600d61387f565b61247790836153f3565b91505b60008260030b8660030b126124985761249383876153f3565b61249b565b60005b90506124a781876153f3565b92506040518060e001604052808260030b81526020018460030b81526020016124d0858c613699565b60030b81526020016124f7898c6124f2670de0b6b3a76400008b63ffffffff16565b6138af565b612501868c6138fa565b61250b91906153cc565b60030b81526020018361251f576000612522565b60015b60ff1681526000602082018190526040909101529998505050505050505050565b60008160030b8360030b12611cb85781610336565b60008160a001518015612576575060016125748460030b6133fe565b115b156125a45761259d600a61258c8560030b6133fe565b6125969190615961565b600161390d565b90506100a1565b50600092915050565b6060600082608001516001600160a01b03166357e9ee7b6040518060a0016040528086602001516001600160a01b031681526020018660c0015181526020018863ffffffff1681526020018760600151815260200187608001518152506040518263ffffffff1660e01b815260040161262691906159c6565b600060405180830381865afa158015612643573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261266b9190810190615acd565b91509150935093915050565b60006100a160ff8316633b9aca0061594a565b60606000806126976149d4565b61269f6148be565b6126a88b61391c565b60a081015160ff16945090506126bf886001615367565b8463ffffffff16101561271957612716600f6126e163ffffffff87168b615b1a565b6126ec90600a61594a565b6126f69190615961565b6127069063ffffffff8716615367565b61271189600561594a565b613b3d565b93505b600260ff1681610160015160ff1603612791576000612743670de0b6b3a76400008863ffffffff16565b9050670a688906bd8b000081111561275e576001835261278b565b600283526101208201516001600160a01b031660208401526101408201516001600160401b031660408401525b506127d5565b80610160015160ff16600a8111156127ab576127ab614f8f565b8290600a8111156127be576127be614f8f565b9081600a8111156127d1576127d1614f8f565b9052505b6127ee816060015182608001518c8c8560e00151613b4c565b9095509250965096509650965096915050565b60608201515160005b818110156104d0576000808460a001516001600160a01b03166324e44dfc8760600151858151811061283e5761283e615465565b60200260200101518860800151868151811061285c5761285c615465565b60200260200101516040518363ffffffff1660e01b815260040161288192919061541a565b600060405180830381865afa15801561289e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526128c69190810190615b2d565b915091506128dd876128d88484613c12565b613cc0565b50505080600101905061280a565b60008160030b8360030b1215611cb85781610336565b60008461290f576000612912565b60015b60ff1660001b905060088463ffffffff1660001b901b8117905060288363ffffffff1660001b901b8117905060488260ff1660001b901b81179050949350505050565b60608160000361297c5750506040805180820190915260018152600360fc1b602082015290565b8160005b81156129a6578061299081615bf0565b915061299f9050600a83615961565b9150612980565b6000816001600160401b038111156129c0576129c0614ad8565b6040519080825280601f01601f1916602001820160405280156129ea576020820181803683370190505b5090505b841561036b576129ff600183615b1a565b9150612a0c600a86615390565b612a17906030615367565b60f81b818381518110612a2c57612a2c615465565b60200101906001600160f81b031916908160001a905350612a4e600a86615961565b94506129ee565b6000806005612a6686866001613d1e565b612a709190615961565b612a7b906001615367565b9050828111612a8b576000611961565b612a958382615b1a565b612aa090600a615367565b611961906002615ced565b60006100a1670de0b6b3a764000061271184604001516001600160a01b031663683fedf76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612afe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b2291906153af565b6001600160a01b031663b2192fdd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b5f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b839190615cf9565b60ff1685604001516001600160a01b031663016dff5d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612bc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612bec91906153af565b6001600160a01b031663df13f4896040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c29573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c4d919061544c565b8660c0015160ff16613d54565b6060826101000151600014612c7e57506040805160008152602081019091526100a1565b60a083015151604080516020810191829052608086015160e08701516337347e0560e11b909352633b9aca006024830152604482019290925260009181906001600160a01b0316636e68fc0a606483016020604051808303816000875af1158015612ced573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d11919061544c565b905290506000826001600160401b03811115612d2f57612d2f614ad8565b604051908082528060200260200182016040528015612d58578160200160208202803683370190505b50905060015b83811015612d8c5780828281518110612d7957612d79615465565b6020908102919091010152600101612d5e565b50612d978282613d9d565b6000836001600160401b03811115612db157612db1614ad8565b604051908082528060200260200182016040528015612dda578160200160208202803683370190505b509050600080612df7612df08a604001516133fe565b6064613b3d565b905060005b86811015612fae57633b9aca0063ffffffff168a60600151868381518110612e2657612e26615465565b602002602001015181518110612e3e57612e3e615465565b602002602001015163ffffffff161115612eaf578960600151858281518110612e6957612e69615465565b602002602001015181518110612e8157612e81615465565b60200260200101516040516304470bfd60e21b81526004016106e8919063ffffffff91909116815260200190565b6000612ef28b60600151878481518110612ecb57612ecb615465565b602002602001015181518110612ee357612ee3615465565b60200260200101518c85613e18565b90506000612f0888633b9aca0063ffffffff8e16565b90508115801590612f265750633b9aca0082101580612f2657508181105b15612fa4578b5160ff16851015612fa4578b60a00151878481518110612f4e57612f4e615465565b602002602001015181518110612f6657612f66615465565b6020026020010151868481518110612f8057612f80615465565b6001600160a01b0390921660209283029190910190910152612fa185615bf0565b94505b5050600101612dfc565b506000826001600160401b03811115612fc957612fc9614ad8565b604051908082528060200260200182016040528015612ff2578160200160208202803683370190505b5090506000805b8881101561308c5760006001600160a01b031686828151811061301e5761301e615465565b60200260200101516001600160a01b0316146130845785818151811061304657613046615465565b602002602001015183838151811061306057613060615465565b6001600160a01b039092166020928302919091019091015261308182615bf0565b91505b600101612ff9565b50909a9950505050505050505050565b6000600882602b8111156130b2576130b2614f8f565b14806130cf5750600b82602b8111156130cd576130cd614f8f565b145b806130eb5750600c82602b8111156130e9576130e9614f8f565b145b806131075750600d82602b81111561310557613105614f8f565b145b806131235750601282602b81111561312157613121614f8f565b145b8061313f5750601382602b81111561313d5761313d614f8f565b145b8061315b5750601482602b81111561315957613159614f8f565b145b806131775750601582602b81111561317557613175614f8f565b145b806131935750602182602b81111561319157613191614f8f565b145b806131af5750602a82602b8111156131ad576131ad614f8f565b145b156131c15761259d83605a6064613ed5565b601982602b8111156131d5576131d5614f8f565b14806131f25750602282602b8111156131f0576131f0614f8f565b145b8061320e5750602382602b81111561320c5761320c614f8f565b145b8061322a5750602482602b81111561322857613228614f8f565b145b806132465750602582602b81111561324457613244614f8f565b145b806132625750602682602b81111561326057613260614f8f565b145b8061327e5750602982602b81111561327c5761327c614f8f565b145b1561328f5761259d83606480613ed5565b50816100a1565b600080856000015184602b8111156132b0576132b0614f8f565b815181106132c0576132c0615465565b602002602001015190506002600a8111156132dd576132dd614f8f565b8660400151600a8111156132f3576132f3614f8f565b036133c757602384602b81111561330c5761330c614f8f565b14801561332a5750600185600581111561332857613328614f8f565b145b1561333d5761333a6014826153cc565b90505b602484602b81111561335157613351614f8f565b14801561336f5750600285600581111561336d5761336d614f8f565b145b156133825761337f6014826153cc565b90505b602584602b81111561339657613396614f8f565b1480156133b4575060038560058111156133b2576133b2614f8f565b145b156133c7576133c46014826153cc565b90505b6133d1818561309c565b915060646133e084605a612543565b6133ea9084615975565b6133f49190615553565b611e5690836153f3565b6000808260030b1361341257506000919050565b5060030b90565b600061033661342c6121f385600461204a565b61343a6121f386600561204a565b846136d3565b60006064836134548460000151601a61204a565b61345e9190615975565b6134689190615553565b61033690846153cc565b6000600182036134b35760648461348e8560000151600e61204a565b6134989190615975565b6134a29190615553565b6134ac90856153cc565b9050610336565b600282036134cd5760648461348e8560000151600f61204a565b600382036134e75760648461348e8560000151601061204a565b600482036135015760648461348e8560000151601161204a565b5082610336565b600061351c6121f38460000151601961204a565b90911092915050565b6000600182036135455761353b8484601261387f565b6134ac90856153f3565b600282036135595761353b8484601361387f565b6003820361356d5761353b8484601461387f565b600482036135015761353b8484601561387f565b600061358f8383602161387f565b61033690846153f3565b600060646135a7838861594a565b6135b19190615961565b6135bb9087615367565b955060006135ca87600161390d565b905060006135db612596888a615367565b90508560006135eb600288615961565b905060008361360286670de0b6b3a764000061594a565b61360c9190615961565b9050600061361a838b615367565b61362c85670de0b6b3a764000061594a565b6136369190615961565b90506000670de0b6b3a76400008261364f85600261594a565b613659919061594a565b6136639190615961565b905061368861367a82670d2f13f7789f0000613b3d565b6702c68af0bb14000061390d565b9d9c50505050505050505050505050565b600060646136ac8360000151601f61204a565b6136b69085615975565b6103369190615553565b600060646136ac8360000151602761204a565b600082841015613702576136f36136ea8585615b1a565b8363ffffffff16565b6136fd9085615367565b61036b565b509092915050565b60008060648460600151600001516137278760000151600061204a565b6137319190615975565b61373b9190615553565b905060648460600151602001516137578760000151600161204a565b6137619190615975565b61376b9190615553565b61377590826153cc565b905060648460600151604001516137918760000151600261204a565b61379b9190615975565b6137a59190615553565b6137af90826153cc565b905060648460600151606001516137cb8760000151600361204a565b6137d59190615975565b6137df9190615553565b6137e990826153cc565b905061196181846153cc565b6000600182600581111561380b5761380b614f8f565b0361382b5760646138218460000151601b61204a565b6134989086615975565b600282600581111561383f5761383f614f8f565b036138555760646138218460000151601c61204a565b600382600581111561386957613869614f8f565b036135015760646138218460000151601d61204a565b6000606461389b61389485600001518561204a565b605a612543565b6138a59086615975565b61036b9190615553565b60006004845160058111156138c6576138c6614f8f565b1480156138da57506706f05b59d3b2000082115b6138e557600061036b565b600283602001516040015161036b9190615553565b600060646136ac8360000151602861204a565b6000818311611cb85781610336565b6139246148be565b61392c6148be565b6139878360000180548060200260200160405190810160405280929190818152602001828054801561397d57602002820191906000526020600020905b815481526020019060010190808311613969575b5050505050613f03565b606083015260808201526001830154600881901c601082901c603083901c60ff90811661010086015263ffffffff90911660e085015290811660c08401521660a082015260028301546139e160a082901c9060e083901c90565b60ff166101608401526001600160401b039081166101408401526001600160a01b039091166101208301526003840154908190811115613a2357613a23614ad8565b604051908082528060200260200182016040528015613a4c578160200160208202803683370190505b50610180830152806001600160401b03811115613a6b57613a6b614ad8565b604051908082528060200260200182016040528015613a94578160200160208202803683370190505b506101a083015260005b81811015613b3457613ad2856003018281548110613abe57613abe615465565b90600052602060002001549060a082901c90565b8461018001518381518110613ae957613ae9615465565b60200260200101856101a001518481518110613b0757613b07615465565b63ffffffff909316602093840291909101909201919091526001600160a01b039091169052600101613a9e565b50909392505050565b6000818310611cb85781610336565b60606000613b5b600486615d16565b60408051602b8082526105808201909252919650602082016105608036833701905050915060005b8751811015613bf957613bb0878281518110613ba157613ba1615465565b602002602001015187876141f4565b83898381518110613bc357613bc3615465565b602002602001015160ff1681518110613bde57613bde615465565b60039290920b60209283029190910190910152600101613b83565b50613c06838660006141f4565b90509550959350505050565b60408051602b8082526105808201909252606091602082016105608036833701905050905060005b8351811015613cb9576000848281518110613c5757613c57615465565b602002602001015190508060030b600014613cb0578083858481518110613c8057613c80615465565b602002602001015160ff1681518110613c9b57613c9b615465565b602002602001019060030b908160030b815250505b50600101613c3a565b5092915050565b606060005b8351811015613d1657828181518110613ce057613ce0615465565b6020026020010151848281518110613cfa57613cfa615465565b60209081029190910101805190910160030b9052600101613cc5565b509192915050565b600080613d2b858561429c565b9050828015613d4457506002613d42826001615367565b115b1561036b57611961600282615b1a565b600081600003613d6d5750670de0b6b3a7640000610336565b828211613d93576136fd8484613d838582615b1a565b613d8e906001615367565b6142cf565b5060009392505050565b80516000196001600160801b0382156104d0576020840193505b6020852080865282840193608082901c0660051b850184613dd95750506104d0565b600585811b8701805183519091529091528385019482841606901b850184613e025750506104d0565b600585901b860180518251909152905250613db7565b600080670de0b6b3a7640000613e3b670de0b6b3a7640000866101200151613b3d565b613e4b9063ffffffff881661594a565b613e559190615961565b9050670de0b6b3a76400008460c0015182613e70919061594a565b613e7a9190615961565b613e849082615367565b90506064613e9585602001516133fe565b613e9f908361594a565b613ea99190615961565b613eb39082615367565b90506064613ec1848361594a565b613ecb9190615961565b6119619082615b1a565b6000808460030b1315613d9357670de0b6b3a7640000613ef9848660030b85614311565b6136fd9190615961565b805160609081906000613f1782600861594a565b90506000816001600160401b03811115613f3357613f33614ad8565b604051908082528060200260200182016040528015613f5c578160200160208202803683370190505b5090506000826001600160401b03811115613f7957613f79614ad8565b604051908082528060200260200182016040528015613fa2578160200160208202803683370190505b5090506000805b858110156140cf5760005b60088110156140c657600081613fcb84600861594a565b613fd59190615367565b9050613fe282602061594a565b8b8481518110613ff457613ff4615465565b602002602001015160001c901c60020b86828151811061401657614016615465565b60039290920b60209283029190910182015261403390839061594a565b61403e906018615367565b8b848151811061405057614050615465565b602002602001015160001c901c85828151811061406f5761406f615465565b602002602001019060ff16908160ff168152505085818151811061409557614095615465565b602002602001015160030b6000036140ad57506140c6565b836140b781615bf0565b94505050806001019050613fb4565b50600101613fa9565b50806001600160401b038111156140e8576140e8614ad8565b604051908082528060200260200182016040528015614111578160200160208202803683370190505b509650806001600160401b0381111561412c5761412c614ad8565b604051908082528060200260200182016040528015614155578160200160208202803683370190505b50955060005b818110156141e95783818151811061417557614175615465565b602002602001015188828151811061418f5761418f615465565b602002602001019060030b908160030b815250508281815181106141b5576141b5615465565b60200260200101518782815181106141cf576141cf615465565b60ff9092166020928302919091019091015260010161415b565b505050505050915091565b60008360030b60000361420957506000610336565b6000633b9aca0061421e84600388900b615d39565b6142289190615d69565b633b9aca0061424163ffffffff8716600389900b615d39565b61424b9190615d69565b8660030b6142599190615d97565b6142639190615d97565b9050637fffffff81138061427b5750637fffffff1981125b1561036b576040516344dc334160e01b8152600481018290526024016106e8565b805b60638110156100a1576142b2836001615367565b6142bb82614379565b63ffffffff1610156100a15760010161429e565b600060015b848160ff1611614309576142ed85858360ff168661441a565b6142f79083615367565b915061430281615dbf565b90506142d4565b509392505050565b6000806143438367099e8db03256ce5d61432a87615dde565b6143349190615d39565b61433e9190615d69565b614481565b90506000811261436e5761435f81670de0b6b3a7640000615b1a565b614369908661594a565b611961565b506000949350505050565b600063ffffffff821615806143955750606363ffffffff831610155b156143a257506000919050565b670de0b6b3a76400006143e16143b9846063615dfa565b6143c4906002615e17565b6143dc9063ffffffff16670de0b6b3a764000061594a565b6145fe565b6143f390675cfb2e807b1e0000615b1a565b614406620186a063ffffffff861661594a565b614410919061594a565b6100a19190615961565b6000614425846146df565b6002614432876001615367565b61443c908861594a565b6144469190615961565b614451846001615367565b61446386670de0b6b3a764000061594a565b61446d919061594a565b6144779190615961565b6119619190615961565b600068023f2fa8f6da5b9d2819821361449957919050565b680755bf798b4a1bf1e582126144b75763a37bfec96000526004601cfd5b6503782dace9d9604e83901b059150600060606bb17217f7d1cf79abc9e3b39884821b056001605f1b01901d6bb17217f7d1cf79abc9e3b39881029093036c240c330e9fb2d9cbaf0fd5aafb1981018102606090811d6d0277594991cfc85f6e2461837cd9018202811d6d1a521255e34f6a5061b25ef1c9c319018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d6e02c72388d9f74f51a9331fed693f1419018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084016d01d3967ed30fc4f89c02bab5708119010290911d6e0587f503bb6ea29d25fcb740196450019091026d360d7aeea093263ecc6e0ecb291760621b010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b6000670de0b6b3a764000082101561462c57604051637046c4a960e01b8152600481018390526024016106e8565b6000614648614643670de0b6b3a764000085615961565b6146f8565b9050600061465e82670de0b6b3a764000061594a565b905083821c670de0b6b3a764000081900361467b57509392505050565b6706f05b59d3b200005b80156146d557670de0b6b3a764000061469e838061594a565b6146a89190615961565b9150671bc16d674ec8000082106146cd576146c38184615367565b9250600182901c91505b60011c614685565b5090949350505050565b600060026146ed8382615367565b614406846001615367565b6000600160801b821061471857608091821c916147159082615367565b90505b600160401b821061473657604091821c916147339082615367565b90505b640100000000821061475557602091821c916147529082615367565b90505b62010000821061477257601091821c9161476f9082615367565b90505b610100821061478e57600891821c9161478b9082615367565b90505b601082106147a957600491821c916147a69082615367565b90505b600482106147c457600291821c916147c19082615367565b90505b60028210610f29576100a1600182615367565b604080516101c08101825260008082526020820181905291810182905260608082018190526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201839052610140820183905261016082018390526101808201929092526101a081019190915290565b6040518060400160405280614860614a0d565b815260200161486d614a0d565b905290565b6040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a081019190915260c081016148b1614930565b815260200161486d614930565b604080516101c081018252600080825260208201819052909182019081526060602082018190526040820181905260008183018190526080830181905260a0830181905260c0830181905260e08301819052610100830181905261012083015261014082018190526101609091015290565b6040805160c0810182526060808252825160a081018452600080825260208281018290529482018190529181018290526080810191909152909182019081526020016000815260200160006001600160a01b0316815260200160008152602001600081525090565b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040805160a08101909152806000815260200160006001600160a01b031681526020016000815260200160608152602001606081525090565b604051806101000160405280614a21614930565b81526040805160808082018352600080835260208381018290528385018290528451928301855281835282810182905293820181905260608083019190915282015291019081526000602080830182905260408084018390526060808501849052608080860185905260a08087018690528351610120810185528681529485018690529284018590529083018490528201839052810182905260c081810183905260e0820183905261010082019290925291015290565b634e487b7160e01b600052604160045260246000fd5b6040516101a081016001600160401b0381118282101715614b1157614b11614ad8565b60405290565b604051608081016001600160401b0381118282101715614b1157614b11614ad8565b60405160a081016001600160401b0381118282101715614b1157614b11614ad8565b60405161012081016001600160401b0381118282101715614b1157614b11614ad8565b604051601f8201601f191681016001600160401b0381118282101715614ba657614ba6614ad8565b604052919050565b6001600160a01b0381168114614bc357600080fd5b50565b8035610f2981614bae565b60ff81168114614bc357600080fd5b8035610f2981614bd1565b63ffffffff81168114614bc357600080fd5b8035610f2981614beb565b80356001600160401b0381168114610f2957600080fd5b600082601f830112614c3057600080fd5b81356001600160401b03811115614c4957614c49614ad8565b614c5c601f8201601f1916602001614b7e565b818152846020838601011115614c7157600080fd5b816020850160208301376000918101602001919091529392505050565b60008060408385031215614ca157600080fd5b82356001600160401b0380821115614cb857600080fd5b908401906101a08287031215614ccd57600080fd5b614cd5614aee565b614cde83614bc6565b8152614cec60208401614bc6565b6020820152614cfd60408401614bc6565b6040820152614d0e60608401614be0565b6060820152614d1f60808401614be0565b6080820152614d3060a08401614be0565b60a0820152614d4160c08401614be0565b60c0820152614d5260e08401614bfd565b60e0820152610100614d65818501614c08565b908201526101208381013590820152610140808401359082015261016080840135908201526101808084013583811115614d9e57600080fd5b614daa89828701614c1f565b91830191909152509660209590950135955050505050565b6001600160a01b03169052565b60008151808452602080850194506020840160005b83811015614e095781516001600160a01b031687529582019590820190600101614de4565b509495945050505050565b60008151808452602080850194506020840160005b83811015614e0957815163ffffffff1687529582019590820190600101614e29565b60408152614e5e60408201845115159052565b60006020840151614e73606084018215159052565b506040840151614e866080840182614dc2565b5060608401516101c08060a0850152614ea3610200850183614dcf565b91506080860151614eb960c086018260030b9052565b5060a0860151614ece60e086018260030b9052565b5060c0860151610100614ee58187018360030b9052565b60e08801519150610120614efd8188018460030b9052565b90880151915061014090614f158783018460030b9052565b8801519150610160614f2e8782018463ffffffff169052565b90880151915061018090614f498783018463ffffffff169052565b8801516101a087810191909152908801519286019290925250850151838203603f19016101e0850152614f7c8282614e14565b92505050610336602083018460ff169052565b634e487b7160e01b600052602160045260246000fd5b600b8110614fb557614fb5614f8f565b9052565b80516101408084528151908401819052600091610160850191602091820190845b81811015614ff957825160030b85529383019391830191600101614fda565b50505050602083015161504a602086018263ffffffff808251168352806020830151166020840152806040830151166040840152806060830151166060840152806080830151166080840152505050565b50604083015161505d60c0860182614fa5565b50606083015161507060e0860182614dc2565b50608083015161010085015260a08301516101208501528091505092915050565b8051600681106150a3576150a3614f8f565b8252602081810151600390810b82850152604080840151820b818601526060938401518051830b8587015292830151820b6080860152820151810b60a0850152910151900b60c090910152565b8051151582526020810151151560208301526040810151615115604084018215159052565b506060810151615129606084018215159052565b50608081015161513d608084018215159052565b5060a081015161515160a084018215159052565b5060c081015161516560c084018215159052565b5060e081015161517960e084018215159052565b50610100908101511515910152565b60006102c0825181855261519e82860182614fb9565b91505060208301516151b36020860182615091565b5060408301516151c961010086018260030b9052565b5060608301516151df61012086018260030b9052565b5060808301516151f561014086018260030b9052565b5060a083015161520b61016086018260030b9052565b5060c083015161522161018086018260030b9052565b5060e08301516143096101a08601826150f0565b60018060a01b038516815260806020820152600084516040608084015261525f60c0840182615188565b90506020860151607f198483030160a085015261527c8282615188565b9150508281036040840152610120855181835261529b82840182614fb9565b915050602086015182820360208401526152b58282614fb9565b91505060408601516152d260408401826001600160401b03169052565b5060608601516152ea606084018263ffffffff169052565b5060808601516152fd6080840182614dc2565b5060a086015160a083015260c086015161531c60c084018260ff169052565b5060e086015160e08301526101008087015161533c8285018260ff169052565b50506060939093019390935250949350505050565b634e487b7160e01b600052601160045260246000fd5b808201808211156100a1576100a1615351565b634e487b7160e01b600052601260045260246000fd5b60008261539f5761539f61537a565b500690565b8051610f2981614bae565b6000602082840312156153c157600080fd5b815161033681614bae565b600381810b9083900b01637fffffff8113637fffffff19821217156100a1576100a1615351565b600382810b9082900b03637fffffff198112637fffffff821317156100a1576100a1615351565b6001600160a01b03929092168252602082015260400190565b60ff81811683821601908111156100a1576100a1615351565b60006020828403121561545e57600080fd5b5051919050565b634e487b7160e01b600052603260045260246000fd5b8051600381900b8114610f2957600080fd5b60006080828403121561549f57600080fd5b6154a7614b17565b90506154b28261547b565b81526154c06020830161547b565b60208201526154d16040830161547b565b60408201526154e26060830161547b565b606082015292915050565b600060e082840312156154ff57600080fd5b615507614b17565b82516006811061551657600080fd5b81526155246020840161547b565b60208201526155356040840161547b565b6040820152615547846060850161548d565b60608201529392505050565b60008160030b8360030b8061556a5761556a61537a565b637fffffff1982146000198214161561558557615585615351565b90059392505050565b60006001600160401b038211156155a7576155a7614ad8565b5060051b60200190565b600082601f8301126155c257600080fd5b815160206155d76155d28361558e565b614b7e565b8083825260208201915060208460051b8701019350868411156155f957600080fd5b602086015b8481101561561e57805161561181614bae565b83529183019183016155fe565b509695505050505050565b600082601f83011261563a57600080fd5b8151602061564a6155d28361558e565b8083825260208201915060208460051b87010193508684111561566c57600080fd5b602086015b8481101561561e5780518352918301918301615671565b60006020828403121561569a57600080fd5b81516001600160401b03808211156156b157600080fd5b9083019060a082860312156156c557600080fd5b6156cd614b39565b8251600b81106156dc57600080fd5b81526156ea602084016153a4565b60208201526040830151604082015260608301518281111561570b57600080fd5b615717878286016155b1565b60608301525060808301518281111561572f57600080fd5b61573b87828601615629565b60808301525095945050505050565b6000806040838503121561575d57600080fd5b825161576881614bae565b6020939093015192949293505050565b8051610f2981614beb565b600060a0828403121561579557600080fd5b61579d614b39565b82516157a881614beb565b815260208301516157b881614beb565b602082015260408301516157cb81614beb565b604082015260608301516157de81614beb565b606082015260808301516157f181614beb565b60808201529392505050565b8051610f2981614bd1565b8051600e8110610f2957600080fd5b805161ffff81168114610f2957600080fd5b6000610180828403121561583c57600080fd5b615844614b5b565b61584d836157fd565b815261585b602084016157fd565b602082015261586c60408401615808565b604082015261587d60608401615817565b606082015261588e608084016157fd565b608082015261589f60a08401615778565b60a08201526158b060c084016157fd565b60c08201526158c160e084016157fd565b60e08201526101006158d58582860161548d565b908201529392505050565b674d4f4e535445525f60c01b81526000825160005b8181101561591257602081860181015160088684010152016158f5565b506000920160080191825250919050565b80516020808301519190811015615944576000198160200360031b1b821691505b50919050565b80820281158282048414176100a1576100a1615351565b6000826159705761597061537a565b500490565b60008260030b8260030b028060030b9150808214613cb957613cb9615351565b60008151808452602080850194506020840160005b83811015614e09578151875295820195908201906001016159aa565b6000602080835260c0830160018060a01b03808651168386015282860151604086015263ffffffff6040870151166060860152606086015160a0608087015282815180855260e0880191508583019450600092505b80831015615a3d57845184168252938501936001929092019190850190615a1b565b506080880151878203601f190160a08901529450615a5b8186615995565b98975050505050505050565b600082601f830112615a7857600080fd5b81516020615a886155d28361558e565b8083825260208201915060208460051b870101935086841115615aaa57600080fd5b602086015b8481101561561e57615ac08161547b565b8352918301918301615aaf565b60008060408385031215615ae057600080fd5b82516001600160401b03811115615af657600080fd5b615b0285828601615a67565b925050615b116020840161547b565b90509250929050565b818103818111156100a1576100a1615351565b60008060408385031215615b4057600080fd5b82516001600160401b0380821115615b5757600080fd5b615b6386838701615a67565b9350602091508185015181811115615b7a57600080fd5b85019050601f81018613615b8d57600080fd5b8051615b9b6155d28261558e565b81815260059190911b82018301908381019088831115615bba57600080fd5b928401925b82841015615be1578351615bd281614bd1565b82529284019290840190615bbf565b80955050505050509250929050565b600060018201615c0257615c02615351565b5060010190565b600181815b80851115615c44578160001904821115615c2a57615c2a615351565b80851615615c3757918102915b93841c9390800290615c0e565b509250929050565b600082615c5b575060016100a1565b81615c68575060006100a1565b8160018114615c7e5760028114615c8857615ca4565b60019150506100a1565b60ff841115615c9957615c99615351565b50506001821b6100a1565b5060208310610133831016604e8410600b8410161715615cc7575081810a6100a1565b615cd18383615c09565b8060001904821115615ce557615ce5615351565b029392505050565b60006103368383615c4c565b600060208284031215615d0b57600080fd5b815161033681614bd1565b600063ffffffff80841680615d2d57615d2d61537a565b92169190910492915050565b80820260008212600160ff1b84141615615d5557615d55615351565b81810583148215176100a1576100a1615351565b600082615d7857615d7861537a565b600160ff1b821460001984141615615d9257615d92615351565b500590565b8082018281126000831280158216821582161715615db757615db7615351565b505092915050565b600060ff821660ff8103615dd557615dd5615351565b60010192915050565b6000600160ff1b8201615df357615df3615351565b5060000390565b63ffffffff828116828216039080821115613cb957613cb9615351565b63ffffffff818116838216019080821115613cb957613cb961535156fea2646970667358221220949c0c73bbc85b49cd6dcc95e9aa549cacf4524d563b28d0011360e04fe92b7864736f6c63430008170033
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 31 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.