ETH Price: $3,468.18 (+4.21%)

Contract Diff Checker

Contract Name:
NodePackV6

Contract Source Code:

// SPDX-License-Identifier: MIT

pragma solidity 0.8.9;

import "./lib/AdminAccess.sol";
import "./interfaces/IERC20.sol";
import "./interfaces/INodePackV3.sol";
import "./interfaces/IStrongPool.sol";
import "./interfaces/IStrongNFTPackBonus.sol";
import "./lib/InternalCalls.sol";
import "./lib/SbMath.sol";

contract NodePackV6 is AdminAccess, INodePackV3, InternalCalls {

  uint constant public PACK_TYPE_NODE_REWARD_LIFETIME = 0;
  uint constant public PACK_TYPE_NODE_REWARD_PER_SECOND = 1;
  uint constant public PACK_TYPE_FEE_STRONG = 2;
  uint constant public PACK_TYPE_FEE_CREATE = 3;
  uint constant public PACK_TYPE_FEE_RECURRING = 4;
  uint constant public PACK_TYPE_FEE_CLAIMING_NUMERATOR = 5;
  uint constant public PACK_TYPE_FEE_CLAIMING_DENOMINATOR = 6;
  uint constant public PACK_TYPE_RECURRING_CYCLE_SECONDS = 7;
  uint constant public PACK_TYPE_GRACE_PERIOD_SECONDS = 8;
  uint constant public PACK_TYPE_PAY_CYCLES_LIMIT = 9;
  uint constant public PACK_TYPE_NODES_LIMIT = 10;

  event Created(address indexed entity, uint packType, uint nodesCount, bool usedCredit, uint timestamp, address migratedFrom);
  event AddedNodes(address indexed entity, uint packType, uint nodesCount, uint totalNodesCount, bool usedCredit, uint timestamp, address migratedFrom);
  event MigratedNodes(address indexed entity, uint packType, uint nodesCount, uint lastPaidAt, uint rewardsDue, uint totalClaimed, address migratedFrom, uint timestamp);
  event MaturedNodes(address indexed entity, uint packType, uint maturedCount);
  event Paid(address indexed entity, uint packType, uint timestamp);
  event Claimed(address indexed entity, uint packType, uint reward);
  event SetNodeFeeCollector(address payable collector);
  event SetFeeCollector(address payable collector);
  event SetTakeStrongBips(uint bips);
  event SetNFTBonusContract(address strongNFTBonus);
  event SetServiceContractEnabled(address service, bool enabled);
  event SetPackTypeActive(uint packType, bool active);
  event SetPackTypeSetting(uint packType, uint settingId, uint value);
  event SetPackTypeHasSettings(uint packType, bool hasSettings);

  IERC20 public strongToken;
  IStrongNFTPackBonus public strongNFTBonus;

  uint public totalNodes;
  uint public totalMaturedNodes;
  uint public totalPacks;
  uint public totalPackTypes;
  uint public takeStrongBips;
  address payable public claimFeeCollector;
  address payable public nodeFeeCollector;

  mapping(address => uint) public entityNodeCount;
  mapping(address => uint) public entityCreditUsed;

  mapping(bytes => uint) public entityPackCreatedAt;
  mapping(bytes => uint) public entityPackLastPaidAt;
  mapping(bytes => uint) public entityPackLastClaimedAt;
  mapping(bytes => uint) public entityPackTotalNodeCount;
  mapping(bytes => uint) public entityPackMaturedNodeCount;
  mapping(bytes => uint) public entityPackRewardDue;
  mapping(bytes => uint) public entityPackClaimedTotal;
  mapping(bytes => uint) public entityPackClaimedMatured;

  mapping(uint => bool) public packTypeActive;
  mapping(uint => bool) public packTypeHasSettings;
  mapping(uint => mapping(uint => uint)) public packTypeSettings;
  mapping(address => bool) private serviceContractEnabled;

  function init(
    IERC20 _strongToken,
    IStrongNFTPackBonus _strongNFTBonus,
    address payable _nodeFeeCollector,
    address payable _claimFeeCollector
  ) external onlyRole(adminControl.SUPER_ADMIN()) {
    require(_claimFeeCollector != address(0), "no address");

    strongToken = _strongToken;
    strongNFTBonus = _strongNFTBonus;
    nodeFeeCollector = _nodeFeeCollector;
    claimFeeCollector = _claimFeeCollector;

    InternalCalls.init();
  }

  //
  // Getters
  // -------------------------------------------------------------------------------------------------------------------

  function canPackBePaid(address _entity, uint _packType) public view returns (bool) {
    return doesPackExist(_entity, _packType) && !hasPackExpired(_entity, _packType) && !hasMaxPayments(_entity, _packType);
  }

  function doesPackExist(address _entity, uint _packType) public view returns (bool) {
    return entityPackLastPaidAt[getPackId(_entity, _packType)] > 0;
  }

  function isPackPastDue(address _entity, uint _packType) public view returns (bool) {
    bytes memory id = getPackId(_entity, _packType);
    uint lastPaidAt = entityPackLastPaidAt[id];

    return block.timestamp > (lastPaidAt + getRecurringPaymentCycle(_packType));
  }

  function hasMaxPayments(address _entity, uint _packType) public view returns (bool) {
    bytes memory id = getPackId(_entity, _packType);
    uint lastPaidAt = entityPackLastPaidAt[id];
    uint recurringPaymentCycle = getRecurringPaymentCycle(_packType);
    uint limit = block.timestamp + recurringPaymentCycle * getPayCyclesLimit(_packType);

    return lastPaidAt + recurringPaymentCycle >= limit;
  }

  function hasPackExpired(address _entity, uint _packType) public view returns (bool) {
    bytes memory id = getPackId(_entity, _packType);
    uint lastPaidAt = entityPackLastPaidAt[id];
    if (lastPaidAt == 0) return true;

    return block.timestamp > (lastPaidAt + getRecurringPaymentCycle(_packType) + getGracePeriod(_packType));
  }

  function getClaimingFee(address _entity, uint _packType, uint _timestamp) public view returns (uint) {
    return getRewardAt(_entity, _packType, _timestamp, true) * getClaimingFeeNumerator(_packType) / getClaimingFeeDenominator(_packType);
  }

  function getPacksClaimingFee(address _entity, uint _timestamp) external view returns (uint) {
    uint fee = 0;

    for (uint packType = 1; packType <= totalPackTypes; packType++) {
      fee = fee + getClaimingFee(_entity, packType, _timestamp);
    }

    return fee;
  }

  function getPackId(address _entity, uint _packType) public pure returns (bytes memory) {
    uint id = _packType != 0 ? _packType : 1;
    return abi.encodePacked(_entity, uint32(id), uint64(1));
  }

  function getEntityPackTotalNodeCount(address _entity, uint _packType) external view returns (uint) {
    return entityPackTotalNodeCount[getPackId(_entity, _packType)];
  }

  function getEntityPackMaturedNodeCount(address _entity, uint _packType) external view returns (uint) {
    return entityPackMaturedNodeCount[getPackId(_entity, _packType)];
  }

  function getEntityPackActiveNodeCount(address _entity, uint _packType) public view returns (uint) {
    bytes memory id = getPackId(_entity, _packType);
    return entityPackTotalNodeCount[id] - entityPackMaturedNodeCount[id];
  }

  function getEntityPackLifetimeRewards(address _entity, uint _packType) public view returns (uint) {
    return getNodeRewardLifetime(_packType) * entityPackTotalNodeCount[getPackId(_entity, _packType)];
  }

  function getEntityPackClaimedMaturedRewards(address _entity, uint _packType) public view returns (uint) {
    return entityPackClaimedMatured[getPackId(_entity, _packType)];
  }

  function getEntityPackClaimedTotalRewards(address _entity, uint _packType) public view returns (uint) {
    return entityPackClaimedTotal[getPackId(_entity, _packType)];
  }

  function getEntityPackAccruedTotalRewards(address _entity, uint _packType) public view returns (uint) {
    return entityPackClaimedTotal[getPackId(_entity, _packType)] + getRewardAt(_entity, _packType, block.timestamp, true);
  }

  function getPackLastPaidAt(address _entity, uint _packType) external view returns (uint) {
    return entityPackLastPaidAt[getPackId(_entity, _packType)];
  }

  function getNodeCreateFee(address _entity, uint _packType) public view returns (uint) {
    uint fee = getCreatingFeeInWei(_packType);
    uint lastPaidAt = entityPackLastPaidAt[getPackId(_entity, _packType)];

    if (lastPaidAt == 0) return fee;
    if (isPackPastDue(_entity, _packType)) return fee;
    if (hasPackExpired(_entity, _packType)) return 0;

    uint payCycleSeconds = getRecurringPaymentCycle(_packType);
    uint dueInSeconds = lastPaidAt + payCycleSeconds - block.timestamp;

    return dueInSeconds * fee / payCycleSeconds;
  }

  function getRecurringFee(address _entity, uint _packType) public view returns (uint) {
    return getRecurringFeeInWei(_packType) * getEntityPackActiveNodeCount(_entity, _packType);
  }

  function getPacksRecurringFee(address _entity) external view returns (uint) {
    uint fee = 0;

    for (uint packType = 1; packType <= totalPackTypes; packType++) {
      if (canPackBePaid(_entity, packType)) fee = fee + getRecurringFee(_entity, packType);
    }

    return fee;
  }

  function getReward(address _entity, uint _packType) external view returns (uint) {
    return getRewardAt(_entity, _packType, block.timestamp, true);
  }

  function getRewardAt(address _entity, uint _packType, uint _timestamp, bool _addBonus) public view returns (uint) {
    bytes memory id = getPackId(_entity, _packType);
    uint lastClaimedAt = entityPackLastClaimedAt[id];
    uint registeredAt = entityPackCreatedAt[id];

    if (!doesPackExist(_entity, _packType)) return 0;
    if (hasPackExpired(_entity, _packType)) return 0;
    if (_timestamp > block.timestamp) return 0;
    if (_timestamp < lastClaimedAt) return 0;
    if (_timestamp <= registeredAt) return 0;

    uint secondsPassed = lastClaimedAt > 0 ? _timestamp - lastClaimedAt : _timestamp - registeredAt;
    uint maxReward = getEntityPackLifetimeRewards(_entity, _packType);
    uint reward = secondsPassed * getNodeRewardPerSecond(_packType) * getEntityPackActiveNodeCount(_entity, _packType);
    uint bonus = _addBonus ? getBonusAt(_entity, _packType, _timestamp) : 0;
    uint totalReward = reward + bonus + entityPackRewardDue[id];

    if (entityPackClaimedTotal[id] >= maxReward) {
      return 0;
    }

    if ((entityPackClaimedTotal[id] + totalReward) >= maxReward) {
      totalReward = maxReward - entityPackClaimedTotal[id];
    }

    return totalReward;
  }

  function getBonusAt(address _entity, uint _packType, uint _timestamp) public view returns (uint) {
    if (address(strongNFTBonus) == address(0)) return 0;

    bytes memory id = getPackId(_entity, _packType);
    uint lastClaimedAt = entityPackLastClaimedAt[id] != 0 ? entityPackLastClaimedAt[id] : entityPackCreatedAt[id];

    return strongNFTBonus.getBonus(_entity, _packType, lastClaimedAt, _timestamp);
  }

  function getEntityRewards(address _entity, uint _timestamp) public view returns (uint) {
    uint reward = 0;

    for (uint packType = 1; packType <= totalPackTypes; packType++) {
      reward = reward + getRewardAt(_entity, packType, _timestamp > 0 ? _timestamp : block.timestamp, true);
    }

    return reward;
  }

  function getEntityCreditAvailable(address _entity, uint _timestamp) public view returns (uint) {
    return getEntityRewards(_entity, _timestamp) - entityCreditUsed[_entity];
  }

  function getRewardBalance() external view returns (uint) {
    return strongToken.balanceOf(address(this));
  }

  //
  // Actions
  // -------------------------------------------------------------------------------------------------------------------

  function create(uint _packType, uint _nodeCount, bool _useCredit) external payable {
    uint fee = getNodeCreateFee(msg.sender, _packType) * _nodeCount;
    uint strongFee = getStrongFeeInWei(_packType) * _nodeCount;
    uint packTypeLimit = getNodesLimit(_packType);
    uint timestamp = block.timestamp;
    bytes memory id = getPackId(msg.sender, _packType);

    require(packTypeActive[_packType], "invalid type");
    require(packTypeLimit == 0 || (entityPackTotalNodeCount[id] + _nodeCount) <= packTypeLimit, "over limit");
    require(_nodeCount >= 1, "invalid node count");
    require(msg.value >= fee, "invalid fee");

    if (address(strongNFTBonus) != address(0)) {
      strongNFTBonus.setEntityPackBonusSaved(msg.sender, _packType);
    }

    totalNodes += _nodeCount;
    entityNodeCount[msg.sender] += _nodeCount;

    if (entityPackTotalNodeCount[id] == 0) {
      entityPackCreatedAt[id] = timestamp;
      entityPackLastPaidAt[id] = timestamp;
      entityPackTotalNodeCount[id] += _nodeCount;
      totalPacks += 1;

      emit Created(msg.sender, _packType, _nodeCount, _useCredit, block.timestamp, address(0));
    }
    else {
      require(!hasPackExpired(msg.sender, _packType), "pack expired");

      updatePackState(msg.sender, _packType, true);
      entityPackTotalNodeCount[id] += _nodeCount;

      emit AddedNodes(msg.sender, _packType, _nodeCount, entityPackTotalNodeCount[id], _useCredit, block.timestamp, address(0));
    }

    if (_useCredit) {
      require(getEntityCreditAvailable(msg.sender, block.timestamp) >= strongFee, "not enough");
      entityCreditUsed[msg.sender] += strongFee;
    } else {
      uint takeStrong = strongFee * takeStrongBips / 10000;
      if (takeStrong > 0) {
        require(strongToken.transferFrom(msg.sender, nodeFeeCollector, takeStrong), "transfer failed");
      }
      if (strongFee > takeStrong) {
        require(strongToken.transferFrom(msg.sender, address(this), strongFee - takeStrong), "transfer failed");
      }
    }

    sendValue(nodeFeeCollector, msg.value);
  }

  function claim(uint _packType, uint _timestamp, address _toStrongPool) public payable returns (uint) {
    address entity = msg.sender == address(strongNFTBonus) ? tx.origin : msg.sender;
    bytes memory id = getPackId(entity, _packType);
    uint lastClaimedAt = entityPackLastClaimedAt[id] != 0 ? entityPackLastClaimedAt[id] : entityPackCreatedAt[id];

    require(doesPackExist(entity, _packType), "doesnt exist");
    require(!hasPackExpired(entity, _packType), "pack expired");
    require(!isPackPastDue(entity, _packType), "past due");
    require(_timestamp <= block.timestamp, "bad timestamp");
    require(lastClaimedAt + 900 < _timestamp, "too soon");

    uint reward = getRewardAt(entity, _packType, _timestamp, true);
    require(reward > 0, "no reward");
    require(strongToken.balanceOf(address(this)) >= reward, "over balance");

    uint fee = reward * getClaimingFeeNumerator(_packType) / getClaimingFeeDenominator(_packType);
    require(msg.value >= fee, "invalid fee");

    entityPackLastClaimedAt[id] = _timestamp;
    entityPackClaimedTotal[id] += reward;
    entityPackRewardDue[id] = 0;

    emit Claimed(entity, _packType, reward);

    if (entityCreditUsed[msg.sender] > 0) {
      if (entityCreditUsed[msg.sender] > reward) {
        entityCreditUsed[msg.sender] = entityCreditUsed[msg.sender] - reward;
        reward = 0;
      } else {
        reward = reward - entityCreditUsed[msg.sender];
        entityCreditUsed[msg.sender] = 0;
      }
    }

    updatePackState(msg.sender, _packType, false);

    if (address(strongNFTBonus) != address(0)) {
      strongNFTBonus.resetEntityPackBonusSaved(id);
    }

    if (reward > 0) {
      if (_toStrongPool != address(0)) IStrongPool(_toStrongPool).mineFor(entity, reward);
      else require(strongToken.transfer(entity, reward), "transfer failed");
    }

    sendValue(claimFeeCollector, fee);
    if (isUserCall() && msg.value > fee) sendValue(payable(msg.sender), msg.value - fee);

    return fee;
  }

  function claimAll(uint _timestamp, address _toStrongPool) external payable makesInternalCalls {
    require(entityNodeCount[msg.sender] > 0, "no nodes");

    uint valueLeft = msg.value;

    for (uint packType = 1; packType <= totalPackTypes; packType++) {
      uint reward = getRewardAt(msg.sender, packType, _timestamp, true);

      if (reward > 0) {
        require(valueLeft >= 0, "not enough");
        uint paid = claim(packType, _timestamp, _toStrongPool);
        valueLeft = valueLeft - paid;
      }
    }

    if (valueLeft > 0) sendValue(payable(msg.sender), valueLeft);
  }

  function pay(uint _packType) public payable returns (uint) {
    require(canPackBePaid(msg.sender, _packType), "cant pay");

    updatePackState(msg.sender, _packType, true);

    bytes memory id = getPackId(msg.sender, _packType);
    uint fee = getRecurringFeeInWei(_packType) * getEntityPackActiveNodeCount(msg.sender, _packType);

    require(msg.value >= fee, "invalid fee");

    entityPackLastPaidAt[id] = entityPackLastPaidAt[id] + getRecurringPaymentCycle(_packType);

    emit Paid(msg.sender, _packType, entityPackLastPaidAt[id]);

    sendValue(nodeFeeCollector, fee);
    if (isUserCall() && msg.value > fee) sendValue(payable(msg.sender), msg.value - fee);

    return fee;
  }

  function payAll() external payable makesInternalCalls {
    require(entityNodeCount[msg.sender] > 0, "no packs");

    uint valueLeft = msg.value;

    for (uint packType = 1; packType <= totalPackTypes; packType++) {
      if (!canPackBePaid(msg.sender, packType)) continue;

      require(valueLeft > 0, "not enough");
      uint paid = pay(packType);
      valueLeft = valueLeft - paid;
    }

    if (valueLeft > 0) sendValue(payable(msg.sender), valueLeft);
  }

  //
  // Admin
  // -------------------------------------------------------------------------------------------------------------------

  function deposit(uint _amount) external onlyRole(adminControl.SUPER_ADMIN()) {
    require(_amount > 0);
    require(strongToken.transferFrom(msg.sender, address(this), _amount), "transfer failed");
  }

  function withdraw(address _destination, uint _amount) external onlyRole(adminControl.SUPER_ADMIN()) {
    require(_amount > 0);
    require(strongToken.balanceOf(address(this)) >= _amount, "over balance");
    require(strongToken.transfer(_destination, _amount), "transfer failed");
  }

  function approveStrongPool(IStrongPool _strongPool, uint _amount) external onlyRole(adminControl.SUPER_ADMIN()) {
    require(strongToken.approve(address(_strongPool), _amount), "approve failed");
  }

  function setNodeFeeCollector(address payable _nodeFeeCollector) external onlyRole(adminControl.SUPER_ADMIN()) {
    require(_nodeFeeCollector != address(0));
    nodeFeeCollector = _nodeFeeCollector;
    emit SetNodeFeeCollector(_nodeFeeCollector);
  }

  function setClaimFeeCollector(address payable _claimFeeCollector) external onlyRole(adminControl.SUPER_ADMIN()) {
    require(_claimFeeCollector != address(0));
    claimFeeCollector = _claimFeeCollector;
    emit SetFeeCollector(_claimFeeCollector);
  }

  function setNFTBonusContract(address _contract) external onlyRole(adminControl.SERVICE_ADMIN()) {
    strongNFTBonus = IStrongNFTPackBonus(_contract);
    emit SetNFTBonusContract(_contract);
  }

  function setTakeStrongBips(uint _bips) external onlyRole(adminControl.SUPER_ADMIN()) {
    require(_bips <= 10000, "invalid value");
    takeStrongBips = _bips;
    emit SetTakeStrongBips(_bips);
  }

  function updateEntityPackLastPaidAt(address _entity, uint _packType, uint _lastPaidAt) external onlyRole(adminControl.SERVICE_ADMIN()) {
    bytes memory id = getPackId(_entity, _packType);
    entityPackLastPaidAt[id] = _lastPaidAt;
  }

  //
  // Settings
  // -------------------------------------------------------------------------------------------------------------------

  function getCustomSettingOrDefaultIfZero(uint _packType, uint _setting) internal view returns (uint) {
    return packTypeHasSettings[_packType] && packTypeSettings[_packType][_setting] > 0
    ? packTypeSettings[_packType][_setting]
    : packTypeSettings[0][_setting];
  }

  function getNodeRewardLifetime(uint _packType) public view returns (uint) {
    return getCustomSettingOrDefaultIfZero(_packType, PACK_TYPE_NODE_REWARD_LIFETIME);
  }

  function getNodeRewardPerSecond(uint _packType) public view returns (uint) {
    return getCustomSettingOrDefaultIfZero(_packType, PACK_TYPE_NODE_REWARD_PER_SECOND);
  }

  function getClaimingFeeNumerator(uint _packType) public view returns (uint) {
    return getCustomSettingOrDefaultIfZero(_packType, PACK_TYPE_FEE_CLAIMING_NUMERATOR);
  }

  function getClaimingFeeDenominator(uint _packType) public view returns (uint) {
    return getCustomSettingOrDefaultIfZero(_packType, PACK_TYPE_FEE_CLAIMING_DENOMINATOR);
  }

  function getCreatingFeeInWei(uint _packType) public view returns (uint) {
    return getCustomSettingOrDefaultIfZero(_packType, PACK_TYPE_FEE_CREATE);
  }

  function getRecurringFeeInWei(uint _packType) public view returns (uint) {
    return getCustomSettingOrDefaultIfZero(_packType, PACK_TYPE_FEE_RECURRING);
  }

  function getStrongFeeInWei(uint _packType) public view returns (uint) {
    return getCustomSettingOrDefaultIfZero(_packType, PACK_TYPE_FEE_STRONG);
  }

  function getRecurringPaymentCycle(uint _packType) public view returns (uint) {
    return getCustomSettingOrDefaultIfZero(_packType, PACK_TYPE_RECURRING_CYCLE_SECONDS);
  }

  function getGracePeriod(uint _packType) public view returns (uint) {
    return getCustomSettingOrDefaultIfZero(_packType, PACK_TYPE_GRACE_PERIOD_SECONDS);
  }

  function getPayCyclesLimit(uint _packType) public view returns (uint) {
    return getCustomSettingOrDefaultIfZero(_packType, PACK_TYPE_PAY_CYCLES_LIMIT);
  }

  function getNodesLimit(uint _packType) public view returns (uint) {
    return getCustomSettingOrDefaultIfZero(_packType, PACK_TYPE_NODES_LIMIT);
  }

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

  function setPackTypeActive(uint _packType, bool _active) external onlyRole(adminControl.SERVICE_ADMIN()) {
    // Pack type 0 is being used as a placeholder for the default settings for pack types that don't have custom ones,
    // So it shouldn't be activated and used to create nodes
    require(_packType > 0, "invalid type");
    packTypeActive[_packType] = _active;
    if (totalPackTypes < _packType && _active) {
      totalPackTypes = _packType;
    }

    emit SetPackTypeActive(_packType, _active);
  }

  function setPackTypeHasSettings(uint _packType, bool _hasSettings) external onlyRole(adminControl.SERVICE_ADMIN()) {
    packTypeHasSettings[_packType] = _hasSettings;
    emit SetPackTypeHasSettings(_packType, _hasSettings);
  }

  function setPackTypeSetting(uint _packType, uint _settingId, uint _value) external onlyRole(adminControl.SERVICE_ADMIN()) {
    packTypeHasSettings[_packType] = true;
    packTypeSettings[_packType][_settingId] = _value;
    emit SetPackTypeSetting(_packType, _settingId, _value);
  }

  function setServiceContractEnabled(address _contract, bool _enabled) external onlyRole(adminControl.SERVICE_ADMIN()) {
    serviceContractEnabled[_contract] = _enabled;
    emit SetServiceContractEnabled(_contract, _enabled);
  }

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

  function sendValue(address payable recipient, uint amount) internal {
    require(address(this).balance >= amount, "insufficient balance");

    // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
    (bool success,) = recipient.call{value : amount}("");
    require(success, "send failed");
  }

  function updatePackState(address _entity, uint _packType) external {
    require(msg.sender == address(strongNFTBonus), "invalid sender");

    updatePackState(_entity, _packType, true);
  }

  function updatePackState(address _entity, uint _packType, bool _saveRewardsDue) internal {
    bytes memory id = getPackId(_entity, _packType);

    uint rewardDue = getRewardAt(_entity, _packType, block.timestamp, false);
    uint accruedTotal = entityPackClaimedTotal[id] + rewardDue;
    uint nodeLifetimeReward = getNodeRewardLifetime(_packType);
    uint maturedNodesTotal = accruedTotal / nodeLifetimeReward;
    uint maturedNodesNew = maturedNodesTotal > entityPackMaturedNodeCount[id] ? maturedNodesTotal - entityPackMaturedNodeCount[id] : 0;

    if (_saveRewardsDue) {
      entityPackRewardDue[id] = rewardDue;
      entityPackLastClaimedAt[id] = block.timestamp;
    }

    if (maturedNodesNew > 0) {
      entityPackMaturedNodeCount[id] += maturedNodesNew;
      entityPackClaimedMatured[id] += maturedNodesNew * nodeLifetimeReward;
      totalMaturedNodes += maturedNodesNew;
      emit MaturedNodes(_entity, _packType, maturedNodesNew);
    }
  }

  //
  // Migration
  // -------------------------------------------------------------------------------------------------------------------

  function migrateNodes(address _entity, uint _packType, uint _nodeCount, uint _lastPaidAt, uint _rewardsDue, uint _totalClaimed) external returns (bool) {
    require(serviceContractEnabled[msg.sender], "no service");
    require(packTypeActive[_packType], "invalid type");
    require(!doesPackExist(_entity, _packType) || !hasPackExpired(_entity, _packType), "pack expired");

    bytes memory id = getPackId(_entity, _packType);

    totalNodes += _nodeCount;
    entityNodeCount[_entity] += _nodeCount;

    if (entityPackCreatedAt[id] == 0) {
      entityPackCreatedAt[id] = block.timestamp;
      entityPackLastPaidAt[id] = _lastPaidAt > 0 ? _lastPaidAt : block.timestamp;
      totalPacks += 1;

      emit Created(_entity, _packType, _nodeCount, false, block.timestamp, msg.sender);
    }
    else {
      updatePackState(_entity, _packType, true);
      if (_lastPaidAt > 0) {
        entityPackLastPaidAt[id] = ((entityPackLastPaidAt[id] * entityPackTotalNodeCount[id]) + (_lastPaidAt * _nodeCount)) / (entityPackTotalNodeCount[id] + _nodeCount);
      }

      emit AddedNodes(_entity, _packType, _nodeCount, entityPackTotalNodeCount[id], false, block.timestamp, msg.sender);
    }

    entityPackTotalNodeCount[id] += _nodeCount;
    entityPackClaimedTotal[id] += _totalClaimed;
    entityPackRewardDue[id] += _rewardsDue;

    if (entityPackTotalNodeCount[id] > _nodeCount) {
      updatePackState(_entity, _packType, true);
    }

    emit MigratedNodes(_entity, _packType, _nodeCount, _lastPaidAt, _rewardsDue, _totalClaimed, msg.sender, block.timestamp);

    return true;
  }

}

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0;

import "../interfaces/IAdminControl.sol";

abstract contract AdminAccess {

  IAdminControl public adminControl;

  modifier onlyRole(uint8 _role) {
    require(address(adminControl) == address(0) || adminControl.hasRole(_role, msg.sender), "no access");
    _;
  }

  function addAdminControlContract(IAdminControl _contract) external onlyRole(0) {
    adminControl = _contract;
  }

}

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.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.6.0;

interface INodePackV3 {
  function doesPackExist(address entity, uint packId) external view returns (bool);

  function hasPackExpired(address entity, uint packId) external view returns (bool);

  function claim(uint packId, uint timestamp, address toStrongPool) external payable returns (uint);

//  function getBonusAt(address _entity, uint _packType, uint _timestamp) external view returns (uint);

  function getPackId(address _entity, uint _packType) external pure returns (bytes memory);

  function getEntityPackTotalNodeCount(address _entity, uint _packType) external view returns (uint);

  function getEntityPackActiveNodeCount(address _entity, uint _packType) external view returns (uint);

  function migrateNodes(address _entity, uint _nodeType, uint _nodeCount, uint _lastPaidAt, uint _rewardsDue, uint _totalClaimed) external returns (bool);

//  function addPackRewardDue(address _entity, uint _packType, uint _rewardDue) external;

  function updatePackState(address _entity, uint _packType) external;
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0;

interface IStrongPool {
  function mineFor(address miner, uint256 amount) external;
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.9;

interface IStrongNFTPackBonus {
  function getBonus(address _entity, uint _packType, uint _from, uint _to) external view returns (uint);

  function setEntityPackBonusSaved(address _entity, uint _packType) external;

  function resetEntityPackBonusSaved(bytes memory _packId) external;
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0;

import "./Context.sol";

abstract contract InternalCalls is Context {

  uint private constant _NOT_MAKING_INTERNAL_CALLS = 1;
  uint private constant _MAKING_INTERNAL_CALLS = 2;

  uint private _internal_calls_status;

  modifier makesInternalCalls() {
    _internal_calls_status = _MAKING_INTERNAL_CALLS;
    _;
    _internal_calls_status = _NOT_MAKING_INTERNAL_CALLS;
  }

  function init() internal {
    _internal_calls_status = _NOT_MAKING_INTERNAL_CALLS;
  }

  function isInternalCall() internal view returns (bool) {
    return _internal_calls_status == _MAKING_INTERNAL_CALLS;
  }

  function isContractCall() internal view returns (bool) {
    return _msgSender() != tx.origin;
  }

  function isUserCall() internal view returns (bool) {
    return !isInternalCall() && !isContractCall();
  }
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

library SbMath {

  uint internal constant DECIMAL_PRECISION = 1e18;

  /*
  * Multiply two decimal numbers and use normal rounding rules:
  * -round product up if 19'th mantissa digit >= 5
  * -round product down if 19'th mantissa digit < 5
  *
  * Used only inside the exponentiation, _decPow().
  */
  function decMul(uint x, uint y) internal pure returns (uint decProd) {
    uint prod_xy = x * y;

    decProd = (prod_xy + (DECIMAL_PRECISION / 2)) / DECIMAL_PRECISION;
  }

  /*
  * _decPow: Exponentiation function for 18-digit decimal base, and integer exponent n.
  *
  * Uses the efficient "exponentiation by squaring" algorithm. O(log(n)) complexity.
  *
  * The exponent is capped to avoid reverting due to overflow. The cap 525600000 equals
  * "minutes in 1000 years": 60 * 24 * 365 * 1000
  */
  function _decPow(uint _base, uint _minutes) internal pure returns (uint) {

    if (_minutes > 525_600_000) _minutes = 525_600_000;  // cap to avoid overflow

    if (_minutes == 0) return DECIMAL_PRECISION;

    uint y = DECIMAL_PRECISION;
    uint x = _base;
    uint n = _minutes;

    // Exponentiation-by-squaring
    while (n > 1) {
      if (n % 2 == 0) {
        x = decMul(x, x);
        n = n / 2;
      } else { // if (n % 2 != 0)
        y = decMul(x, y);
        x = decMul(x, x);
        n = (n - 1) / 2;
      }
    }

    return decMul(x, y);
  }

}

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0;

interface IAdminControl {
  function hasRole(uint8 _role, address _account) external view returns (bool);

  function SUPER_ADMIN() external view returns (uint8);

  function ADMIN() external view returns (uint8);

  function SERVICE_ADMIN() external view returns (uint8);
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with GSN meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):